From 5123ea26e4cada0641035645b751abc660f3f52f Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Wed, 21 Feb 2024 14:42:38 +0100 Subject: [PATCH 01/52] Add syntax for top-level procedures --- .../concepts/generics/genericProcedure.pvl | 4 ++ res/universal/res/include/IntegerChannel.java | 1 - res/universal/res/include/channel.pvl | 49 ------------------- res/universal/res/include/genericChannel.pvl | 40 +++++++++++++++ src/parsers/antlr4/LangPVLParser.g4 | 3 +- .../vct/parsers/transform/PVLToCol.scala | 19 +++++-- .../examples/GenericsExamplesSpec.scala | 7 +++ 7 files changed, 69 insertions(+), 54 deletions(-) create mode 100644 examples/concepts/generics/genericProcedure.pvl delete mode 100644 res/universal/res/include/channel.pvl create mode 100644 res/universal/res/include/genericChannel.pvl create mode 100644 test/main/vct/test/integration/examples/GenericsExamplesSpec.scala diff --git a/examples/concepts/generics/genericProcedure.pvl b/examples/concepts/generics/genericProcedure.pvl new file mode 100644 index 0000000000..64db285f83 --- /dev/null +++ b/examples/concepts/generics/genericProcedure.pvl @@ -0,0 +1,4 @@ +ensures \result == t; +T myProcedure(T t) { + return t; +} \ No newline at end of file diff --git a/res/universal/res/include/IntegerChannel.java b/res/universal/res/include/IntegerChannel.java index 18d125e501..09f0848090 100644 --- a/res/universal/res/include/IntegerChannel.java +++ b/res/universal/res/include/IntegerChannel.java @@ -1,5 +1,4 @@ public final class Channel { - private boolean transfering; private MessageType exchangeValue; diff --git a/res/universal/res/include/channel.pvl b/res/universal/res/include/channel.pvl deleted file mode 100644 index 20f6096d75..0000000000 --- a/res/universal/res/include/channel.pvl +++ /dev/null @@ -1,49 +0,0 @@ -//:: cases SynchronousChannel -//:: suite session-generate -//:: tools silicon -//:: verdict Pass -class IntegerChannel { - - boolean transfering; - - int exchangeValue; - - resource lock_invariant() = - Perm(transfering, 1) - ** Perm(exchangeValue,1) - ; - - IntegerChannel() { - transfering = true; - } - - void writeValue(int v) { - lock this; - loop_invariant Perm(transfering, 1) ** Perm(exchangeValue,1); - loop_invariant held(this); - while (!transfering) { - wait this; - } - transfering = false; - exchangeValue = v; - notify this; - unlock this; - } - - int readValue() { - lock this; - loop_invariant Perm(transfering, 1) ** Perm(exchangeValue,1); - loop_invariant held(this); - while (transfering) { - wait this; - } - int v = exchangeValue; - transfering = true; - notify this; - unlock this; - return v; - } -} - - - diff --git a/res/universal/res/include/genericChannel.pvl b/res/universal/res/include/genericChannel.pvl new file mode 100644 index 0000000000..08587d709e --- /dev/null +++ b/res/universal/res/include/genericChannel.pvl @@ -0,0 +1,40 @@ +lock_invariant Perm(transfering, 1) ** Perm(exchangeValue,1); +class IntegerChannel { + boolean transfering; + T exchangeValue; + + constructor() { + transfering = false; + } + + void writeValue(T v) { + lock this; + + loop_invariant Perm(transfering, 1) ** Perm(exchangeValue,1); + loop_invariant held(this); + while (!transfering) { + unlock this; + lock this; + } + + transfering = false; + exchangeValue = v; + unlock this; + } + + T readValue() { + lock this; + + loop_invariant Perm(transfering, 1) ** Perm(exchangeValue,1); + loop_invariant held(this); + while (transfering) { + wait this; + } + + T m = exchangeValue; + transfering = false; + unlock this; + + return m; + } +} diff --git a/src/parsers/antlr4/LangPVLParser.g4 b/src/parsers/antlr4/LangPVLParser.g4 index 0165d36b9a..dc4e1decb8 100644 --- a/src/parsers/antlr4/LangPVLParser.g4 +++ b/src/parsers/antlr4/LangPVLParser.g4 @@ -31,7 +31,7 @@ classDecl : valClassDeclaration | constructor | method | field | runMethod; finalFlag: 'final'; field : finalFlag? type identifierList ';' ; -method : contract valModifier* type identifier '(' args? ')' methodBody ; +method : contract valModifier* type identifier typeVars? '(' args? ')' methodBody ; methodBody : ';' | block ; constructor : contract 'constructor' '(' args? ')' methodBody ; @@ -266,6 +266,7 @@ quantifiedDim : '[' expr ']' ; anonDim : '[' ']' ; classType : identifier typeArgs?; typeArgs : '<' typeList '>'; +typeVars : '<' identifierList '>'; identifierList : identifier diff --git a/src/parsers/vct/parsers/transform/PVLToCol.scala b/src/parsers/vct/parsers/transform/PVLToCol.scala index 0a8e3eac70..aadc5bd16d 100644 --- a/src/parsers/vct/parsers/transform/PVLToCol.scala +++ b/src/parsers/vct/parsers/transform/PVLToCol.scala @@ -73,13 +73,13 @@ case class PVLToCol[G](override val baseOrigin: Origin, } def convertProcedure(implicit method: MethodContext): Procedure[G] = method match { - case Method0(contract, modifiers, returnType, name, _, args, _, body) => + case Method0(contract, modifiers, returnType, name, typeArgs, _, args, _, body) => withModifiers(modifiers, mods => withContract(contract, contract => { new Procedure( convert(returnType), args.map(convert(_)).getOrElse(Nil), outArgs = Nil, - typeArgs = Nil, + typeArgs = typeArgs.map(convert(_)).getOrElse(Nil), convert(body), contract.consumeApplicableContract(blame(method)), inline = mods.consume(mods.inline), @@ -88,6 +88,18 @@ case class PVLToCol[G](override val baseOrigin: Origin, })) } + def convert(implicit names: TypeVarsContext): Seq[Variable[G]] = names match { + case TypeVars0(_, names, _) => convertVars(names) + } + + def typeVar(identifier: IdentifierContext): Variable[G] = + new Variable(TType(TAnyValue()))(origin(identifier).sourceName(convert(identifier))) + + def convertVars(implicit ids: IdentifierListContext): Seq[Variable[G]] = ids match { + case IdentifierList0(identifier) => Seq(typeVar(identifier)) + case IdentifierList1(identifier, _, identifiers) => typeVar(identifier) +: convertVars(identifiers) + } + def convert(implicit cls: DeclClassContext): GlobalDeclaration[G] = cls match { case DeclClass0(contract, _, name, _, decls, _) => withContract(contract, contract => { @@ -108,7 +120,7 @@ case class PVLToCol[G](override val baseOrigin: Origin, } def convert(implicit method: MethodContext): InstanceMethod[G] = method match { - case Method0(contract, modifiers, returnType, name, _, args, _, body) => + case Method0(contract, modifiers, returnType, name, None, _, args, _, body) => withModifiers(modifiers, mods => withContract(contract, contract => { new InstanceMethod( convert(returnType), @@ -121,6 +133,7 @@ case class PVLToCol[G](override val baseOrigin: Origin, pure = mods.consume(mods.pure), )(blame(method))(origin(method).sourceName(convert(name))) })) + case Method0(contract, modifiers, returnType, name, Some(_), _, args, _, body) => ??(method) } def convert(implicit body: MethodBodyContext): Option[Statement[G]] = body match { diff --git a/test/main/vct/test/integration/examples/GenericsExamplesSpec.scala b/test/main/vct/test/integration/examples/GenericsExamplesSpec.scala new file mode 100644 index 0000000000..81f7855813 --- /dev/null +++ b/test/main/vct/test/integration/examples/GenericsExamplesSpec.scala @@ -0,0 +1,7 @@ +package vct.test.integration.examples + +import vct.test.integration.helper.VercorsSpec + +class GenericsExamplesSpec() extends VercorsSpec { + vercors should verify using silicon example "concepts/generics/genericProcedure.pvl" +} From 05f77d02891f4e08f8e4891ece267f2dfa0a0935 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Wed, 21 Feb 2024 15:24:35 +0100 Subject: [PATCH 02/52] Add typeArgs to class --- examples/concepts/generics/box.pvl | 8 ++++++++ src/col/vct/col/ast/Node.scala | 2 +- src/col/vct/col/typerules/CoercingRewriter.scala | 2 +- src/parsers/antlr4/LangPVLParser.g4 | 2 +- src/parsers/vct/parsers/transform/PVLToCol.scala | 8 ++++---- .../systemctocol/engine/ClassTransformer.java | 6 ++++-- .../systemctocol/engine/KnownTypeTransformer.java | 6 ++++-- .../systemctocol/engine/MainTransformer.java | 4 +++- src/rewrite/vct/rewrite/CheckProcessAlgebra.scala | 1 + src/rewrite/vct/rewrite/exc/EncodeBreakReturn.scala | 4 ++-- src/rewrite/vct/rewrite/lang/LangCPPToCol.scala | 3 ++- src/rewrite/vct/rewrite/lang/LangCToCol.scala | 2 +- src/rewrite/vct/rewrite/lang/LangJavaToCol.scala | 13 +++++++++++-- .../vct/rewrite/lang/LangSpecificToCol.scala | 2 +- .../rewrite/veymont/ParalleliseVeyMontThreads.scala | 2 +- .../integration/examples/GenericsExamplesSpec.scala | 1 + 16 files changed, 46 insertions(+), 20 deletions(-) create mode 100644 examples/concepts/generics/box.pvl diff --git a/examples/concepts/generics/box.pvl b/examples/concepts/generics/box.pvl new file mode 100644 index 0000000000..8c2831fdfc --- /dev/null +++ b/examples/concepts/generics/box.pvl @@ -0,0 +1,8 @@ +class Box { + T t; + context Perm(t, 1\2); + ensures \result == t; + T get() { + return t; + } +} \ No newline at end of file diff --git a/src/col/vct/col/ast/Node.scala b/src/col/vct/col/ast/Node.scala index df74796728..61f6b02e11 100644 --- a/src/col/vct/col/ast/Node.scala +++ b/src/col/vct/col/ast/Node.scala @@ -249,7 +249,7 @@ final case class ModelDo[G](model: Expr[G], perm: Expr[G], after: Expr[G], actio final class HeapVariable[G](val t: Type[G])(implicit val o: Origin) extends GlobalDeclaration[G] with HeapVariableImpl[G] final class SimplificationRule[G](val axiom: Expr[G])(implicit val o: Origin) extends GlobalDeclaration[G] with SimplificationRuleImpl[G] @scopes[Variable] final class AxiomaticDataType[G](val decls: Seq[ADTDeclaration[G]], val typeArgs: Seq[Variable[G]])(implicit val o: Origin) extends GlobalDeclaration[G] with AxiomaticDataTypeImpl[G] -final class Class[G](val declarations: Seq[ClassDeclaration[G]], val supports: Seq[Ref[G, Class[G]]], val intrinsicLockInvariant: Expr[G])(implicit val o: Origin) extends GlobalDeclaration[G] with ClassImpl[G] +final class Class[G](val typeArgs: Seq[Variable[G]], val declarations: Seq[ClassDeclaration[G]], val supports: Seq[Ref[G, Class[G]]], val intrinsicLockInvariant: Expr[G])(implicit val o: Origin) extends GlobalDeclaration[G] with ClassImpl[G] final class Model[G](val declarations: Seq[ModelDeclaration[G]])(implicit val o: Origin) extends GlobalDeclaration[G] with Declarator[G] with ModelImpl[G] @scopes[LabelDecl] final class Function[G](val returnType: Type[G], val args: Seq[Variable[G]], val typeArgs: Seq[Variable[G]], val body: Option[Expr[G]], val contract: ApplicableContract[G], val inline: Boolean = false, val threadLocal: Boolean = false) diff --git a/src/col/vct/col/typerules/CoercingRewriter.scala b/src/col/vct/col/typerules/CoercingRewriter.scala index e3e15c9150..ab8c64b958 100644 --- a/src/col/vct/col/typerules/CoercingRewriter.scala +++ b/src/col/vct/col/typerules/CoercingRewriter.scala @@ -1572,7 +1572,7 @@ abstract class CoercingRewriter[Pre <: Generation]() extends BaseCoercingRewrite case dataType: AxiomaticDataType[Pre] => dataType case clazz: Class[Pre] => - new Class[Pre](clazz.declarations, clazz.supports, res(clazz.intrinsicLockInvariant)) + new Class[Pre](clazz.typeArgs, clazz.declarations, clazz.supports, res(clazz.intrinsicLockInvariant)) case enum: Enum[Pre] => enum case enumConstant: EnumConstant[Pre] => diff --git a/src/parsers/antlr4/LangPVLParser.g4 b/src/parsers/antlr4/LangPVLParser.g4 index dc4e1decb8..7d666eeff1 100644 --- a/src/parsers/antlr4/LangPVLParser.g4 +++ b/src/parsers/antlr4/LangPVLParser.g4 @@ -11,7 +11,7 @@ programDecl : valGlobalDeclaration | declClass | enumDecl | method | declVeyMont enumDecl : 'enum' identifier '{' identifierList? ','? '}' ; declClass - : contract 'class' identifier '{' classDecl* '}' + : contract 'class' identifier typeVars? '{' classDecl* '}' ; declVeyMontSeqProg : contract 'seq_program' identifier '(' args? ')' '{' seqProgDecl* '}'; diff --git a/src/parsers/vct/parsers/transform/PVLToCol.scala b/src/parsers/vct/parsers/transform/PVLToCol.scala index aadc5bd16d..0fc2e1cf4a 100644 --- a/src/parsers/vct/parsers/transform/PVLToCol.scala +++ b/src/parsers/vct/parsers/transform/PVLToCol.scala @@ -101,12 +101,13 @@ case class PVLToCol[G](override val baseOrigin: Origin, } def convert(implicit cls: DeclClassContext): GlobalDeclaration[G] = cls match { - case DeclClass0(contract, _, name, _, decls, _) => + case DeclClass0(contract, _, name, typeArgs, _, decls, _) => withContract(contract, contract => { new Class( declarations = decls.flatMap(convert(_)), supports = Nil, intrinsicLockInvariant = AstBuildHelpers.foldStar(contract.consume(contract.lock_invariant)), + typeArgs = typeArgs.map(convert(_)).getOrElse(Nil) )(origin(cls).sourceName(convert(name))) }) } @@ -120,20 +121,19 @@ case class PVLToCol[G](override val baseOrigin: Origin, } def convert(implicit method: MethodContext): InstanceMethod[G] = method match { - case Method0(contract, modifiers, returnType, name, None, _, args, _, body) => + case Method0(contract, modifiers, returnType, name, typeArgs, _, args, _, body) => withModifiers(modifiers, mods => withContract(contract, contract => { new InstanceMethod( convert(returnType), args.map(convert(_)).getOrElse(Nil), outArgs = Nil, - typeArgs = Nil, + typeArgs = typeArgs.map(convert(_)).getOrElse(Nil), convert(body), contract.consumeApplicableContract(blame(method)), inline = mods.consume(mods.inline), pure = mods.consume(mods.pure), )(blame(method))(origin(method).sourceName(convert(name))) })) - case Method0(contract, modifiers, returnType, name, Some(_), _, args, _, body) => ??(method) } def convert(implicit body: MethodBodyContext): Option[Statement[G]] = body match { diff --git a/src/parsers/vct/parsers/transform/systemctocol/engine/ClassTransformer.java b/src/parsers/vct/parsers/transform/systemctocol/engine/ClassTransformer.java index 009f78a36e..624d2abe3f 100644 --- a/src/parsers/vct/parsers/transform/systemctocol/engine/ClassTransformer.java +++ b/src/parsers/vct/parsers/transform/systemctocol/engine/ClassTransformer.java @@ -74,7 +74,8 @@ public Class create_process_class(ProcessClass process) { // Add all newly generated methods to the declarations as well declarations.addAll(generated_instance_methods); - return new Class<>(List.from(CollectionConverters.asScala(declarations)), col_system.NO_CLS_REFS, col_system.TRUE, + return new Class<>(List.from(CollectionConverters.asScala(new java.util.ArrayList(0))), + List.from(CollectionConverters.asScala(declarations)), col_system.NO_CLS_REFS, col_system.TRUE, OriGen.create(create_name(process.get_generating_instance(), process.get_generating_function()))); } @@ -124,7 +125,8 @@ public Class create_state_class(StateClass state_class) { // Add newly generated methods to declaration list declarations.addAll(generated_instance_methods); - return new Class<>(List.from(CollectionConverters.asScala(declarations)), col_system.NO_CLS_REFS, col_system.TRUE, + return new Class<>(List.from(CollectionConverters.asScala(new java.util.ArrayList(0))), + List.from(CollectionConverters.asScala(declarations)), col_system.NO_CLS_REFS, col_system.TRUE, OriGen.create(create_name(state_class.get_generating_instance()))); } diff --git a/src/parsers/vct/parsers/transform/systemctocol/engine/KnownTypeTransformer.java b/src/parsers/vct/parsers/transform/systemctocol/engine/KnownTypeTransformer.java index b62b7a53d6..29d05f3024 100644 --- a/src/parsers/vct/parsers/transform/systemctocol/engine/KnownTypeTransformer.java +++ b/src/parsers/vct/parsers/transform/systemctocol/engine/KnownTypeTransformer.java @@ -143,7 +143,8 @@ private Class transform_fifo(Origin o, Type t) { // Create the class java.util.List> declarations = java.util.List.of(m, buf, nr_read, written, constructor, fifo_read, fifo_write, fifo_update); - return new Class<>(List.from(CollectionConverters.asScala(declarations)), col_system.NO_CLS_REFS, col_system.TRUE, o); + return new Class<>(List.from(CollectionConverters.asScala(new java.util.ArrayList(0))), + List.from(CollectionConverters.asScala(declarations)), col_system.NO_CLS_REFS, col_system.TRUE, o); } /** @@ -544,7 +545,8 @@ private Class transform_signal(Origin o, Type t) { // Create the class java.util.List> class_content = java.util.List.of(m, val, _val, constructor, signal_read, signal_write, signal_update); - return new Class<>(List.from(CollectionConverters.asScala(class_content)), col_system.NO_CLS_REFS, col_system.TRUE, o); + return new Class<>(List.from(CollectionConverters.asScala(new java.util.ArrayList(0))), + List.from(CollectionConverters.asScala(class_content)), col_system.NO_CLS_REFS, col_system.TRUE, o); } /** diff --git a/src/parsers/vct/parsers/transform/systemctocol/engine/MainTransformer.java b/src/parsers/vct/parsers/transform/systemctocol/engine/MainTransformer.java index 530a416422..b401b913a4 100644 --- a/src/parsers/vct/parsers/transform/systemctocol/engine/MainTransformer.java +++ b/src/parsers/vct/parsers/transform/systemctocol/engine/MainTransformer.java @@ -1234,7 +1234,9 @@ private void assemble_main() { new WritePerm<>(OriGen.create()), OriGen.create()); // Assemble class - Class main_class = new Class<>(List.from(CollectionConverters.asScala(declarations)), col_system.NO_CLS_REFS, + Class main_class = new Class<>( + List.from(CollectionConverters.asScala(new java.util.ArrayList(0))), + List.from(CollectionConverters.asScala(declarations)), col_system.NO_CLS_REFS, lock_invariant, OriGen.create("Main")); // Register Main class in COL system context diff --git a/src/rewrite/vct/rewrite/CheckProcessAlgebra.scala b/src/rewrite/vct/rewrite/CheckProcessAlgebra.scala index adc65e04e7..e27df68b9e 100644 --- a/src/rewrite/vct/rewrite/CheckProcessAlgebra.scala +++ b/src/rewrite/vct/rewrite/CheckProcessAlgebra.scala @@ -77,6 +77,7 @@ case class CheckProcessAlgebra[Pre <: Generation]() extends Rewriter[Pre] with L val newClass = currentModel.having(model) { new Class( + Seq(), classDeclarations.collect { model.declarations.foreach(dispatch(_)) }._1, Nil, tt, diff --git a/src/rewrite/vct/rewrite/exc/EncodeBreakReturn.scala b/src/rewrite/vct/rewrite/exc/EncodeBreakReturn.scala index 9692d5063c..931c763387 100644 --- a/src/rewrite/vct/rewrite/exc/EncodeBreakReturn.scala +++ b/src/rewrite/vct/rewrite/exc/EncodeBreakReturn.scala @@ -146,7 +146,7 @@ case class EncodeBreakReturn[Pre <: Generation]() extends Rewriter[Pre] { case Break(Some(Ref(label))) => val cls = breakLabelException.getOrElseUpdate(label, - globalDeclarations.declare(new Class[Post](Nil, Nil, tt)(BreakException))) + globalDeclarations.declare(new Class[Post](Nil, Nil, Nil, tt)(BreakException))) Throw(NewObject[Post](cls.ref))(PanicBlame("The result of NewObject is never null")) @@ -174,7 +174,7 @@ case class EncodeBreakReturn[Pre <: Generation]() extends Rewriter[Pre] { if(needReturn(method)) { val returnField = new InstanceField[Post](dispatch(method.returnType), Nil)(ReturnField) - val returnClass = new Class[Post](Seq(returnField), Nil, tt)(ReturnClass) + val returnClass = new Class[Post](Nil, Seq(returnField), Nil, tt)(ReturnClass) globalDeclarations.declare(returnClass) val caughtReturn = new Variable[Post](TClass(returnClass.ref)) diff --git a/src/rewrite/vct/rewrite/lang/LangCPPToCol.scala b/src/rewrite/vct/rewrite/lang/LangCPPToCol.scala index 840919245e..9d76f5635f 100644 --- a/src/rewrite/vct/rewrite/lang/LangCPPToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangCPPToCol.scala @@ -827,7 +827,7 @@ case class LangCPPToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) extends L // Create a class that can be used to create a 'this' object // It will be linked to the class made near the end of this method. - val preEventClass: Class[Pre] = new Class(Nil, Nil, tt)(commandGroup.o) + val preEventClass: Class[Pre] = new Class(Nil, Nil, Nil, tt)(commandGroup.o) this.currentThis = Some(rw.dispatch(ThisObject[Pre](preEventClass.ref)(preEventClass.o))) // Generate conditions and accessor objects for each accessor declared in the command group @@ -890,6 +890,7 @@ case class LangCPPToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) extends L // Create the surrounding class val postEventClass = new Class[Post]( + typeArgs = Seq(), declarations = currentKernelType.get.getRangeFields ++ accessors.flatMap(acc => acc.instanceField +: acc.rangeIndexFields) ++ Seq(kernelRunner), supports = Seq(), intrinsicLockInvariant = tt diff --git a/src/rewrite/vct/rewrite/lang/LangCToCol.scala b/src/rewrite/vct/rewrite/lang/LangCToCol.scala index 1ac40efbf6..a89aa98992 100644 --- a/src/rewrite/vct/rewrite/lang/LangCToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangCToCol.scala @@ -689,7 +689,7 @@ case class LangCToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) extends Laz case CDeclaration(_, _, Seq(sdecl@CStructDeclaration(Some(_), decls)), Seq()) => (decls, sdecl) case _ => throw WrongStructType(decl) } - val newStruct = new Class[Post](rw.classDeclarations.collect { + val newStruct = new Class[Post](Seq(), rw.classDeclarations.collect { decls.foreach { fieldDecl => val CStructMemberDeclarator(specs: Seq[CDeclarationSpecifier[Pre]], Seq(x)) = fieldDecl fieldDecl.drop() diff --git a/src/rewrite/vct/rewrite/lang/LangJavaToCol.scala b/src/rewrite/vct/rewrite/lang/LangJavaToCol.scala index b29fedccb9..1bb7ad2e40 100644 --- a/src/rewrite/vct/rewrite/lang/LangJavaToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangJavaToCol.scala @@ -63,6 +63,11 @@ case object LangJavaToCol { override def code: String = decl.o.messageInContext("This declaration is not supported in the java.lang.String class") override def text: String = "notSupportedInStringClass" } + + case class GenericJavaNotSupported(decl: JavaClassOrInterface[_]) extends UserError { + override def code: String = decl.o.messageInContext("Generic Java classes not supported") + override def text: String = "genericJavaClass" + } } case class LangJavaToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) extends LazyLogging { @@ -280,6 +285,10 @@ case class LangJavaToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) extends def rewriteClass(cls: JavaClassOrInterface[Pre]): Unit = { implicit val o: Origin = cls.o + if (cls.typeParams.nonEmpty) { + throw new GenericJavaNotSupported(cls) + } + cls.decls.collect({ case decl: JavaClassDeclaration[Pre] => javaClassDeclToJavaClass(decl) = cls @@ -301,7 +310,7 @@ case class LangJavaToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) extends } val instanceClass = rw.currentThis.having(ThisObject(javaInstanceClassSuccessor.ref(cls))) { - new Class[Post](rw.classDeclarations.collect { + new Class[Post](Seq(), rw.classDeclarations.collect { makeJavaClass(cls.name, instDecls, javaInstanceClassSuccessor.ref(cls), isStaticPart = false) cls match { case cls: JavaClass[Pre] if BipComponent.get(cls).isDefined => @@ -315,7 +324,7 @@ case class LangJavaToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) extends javaInstanceClassSuccessor(cls) = instanceClass if(staticDecls.nonEmpty) { - val staticsClass = new Class[Post](rw.classDeclarations.collect { + val staticsClass = new Class[Post](Seq(), rw.classDeclarations.collect { rw.currentThis.having(ThisObject(javaStaticsClassSuccessor.ref(cls))) { makeJavaClass(cls.name + "Statics", staticDecls, javaStaticsClassSuccessor.ref(cls), isStaticPart = true) } diff --git a/src/rewrite/vct/rewrite/lang/LangSpecificToCol.scala b/src/rewrite/vct/rewrite/lang/LangSpecificToCol.scala index e59d7a5a26..65f1b7a199 100644 --- a/src/rewrite/vct/rewrite/lang/LangSpecificToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangSpecificToCol.scala @@ -132,7 +132,7 @@ case class LangSpecificToCol[Pre <: Generation](veymontGeneratePermissions: Bool pvl.maybeDeclareDefaultConstructor(cls) }._1 - globalDeclarations.succeed(cls, cls.rewrite(decls)) + globalDeclarations.succeed(cls, cls.rewrite(declarations = decls)) } } diff --git a/src/rewrite/vct/rewrite/veymont/ParalleliseVeyMontThreads.scala b/src/rewrite/vct/rewrite/veymont/ParalleliseVeyMontThreads.scala index 383f70836c..c7933a71a0 100644 --- a/src/rewrite/vct/rewrite/veymont/ParalleliseVeyMontThreads.scala +++ b/src/rewrite/vct/rewrite/veymont/ParalleliseVeyMontThreads.scala @@ -170,7 +170,7 @@ case class ParalleliseEndpoints[Pre <: Generation](channelClass: JavaClass[_]) e val threadConstr = createThreadClassConstructor(thread,threadRes.threadField) val threadRun = getThreadRunMethod(threadRes.runMethod) classDeclarations.scope { - val threadClass = new Class[Post]( + val threadClass = new Class[Post](Seq(), (threadRes.threadField +: threadRes.channelFields.values.toSeq) ++ (threadConstr +: threadRun +: threadMethods), Seq(), BooleanValue(true)(thread.o))(ThreadClassOrigin(thread)) globalDeclarations.declare(threadClass) threadClassSucc.update(thread, threadClass) diff --git a/test/main/vct/test/integration/examples/GenericsExamplesSpec.scala b/test/main/vct/test/integration/examples/GenericsExamplesSpec.scala index 81f7855813..d49476b66b 100644 --- a/test/main/vct/test/integration/examples/GenericsExamplesSpec.scala +++ b/test/main/vct/test/integration/examples/GenericsExamplesSpec.scala @@ -4,4 +4,5 @@ import vct.test.integration.helper.VercorsSpec class GenericsExamplesSpec() extends VercorsSpec { vercors should verify using silicon example "concepts/generics/genericProcedure.pvl" + vercors should verify using silicon example "concepts/generics/box.pvl" } From 329d040a8c6474a3d794b61694cf1c1049301f3a Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Wed, 21 Feb 2024 16:17:06 +0100 Subject: [PATCH 03/52] Add type args to Class --- src/col/vct/col/ast/Node.scala | 2 +- .../col/ast/declaration/global/ClassImpl.scala | 4 +++- .../ast/expr/ambiguous/AmbiguousPlusImpl.scala | 4 ++-- src/col/vct/col/resolve/lang/PVL.scala | 2 +- src/col/vct/col/resolve/lang/Spec.scala | 10 +++++----- src/col/vct/col/typerules/CoercingRewriter.scala | 2 +- src/parsers/vct/parsers/transform/PVLToCol.scala | 2 +- src/rewrite/vct/rewrite/ClassToRef.scala | 6 ++++-- src/rewrite/vct/rewrite/EncodeForkJoin.scala | 2 +- .../vct/rewrite/EncodeIntrinsicLock.scala | 4 ++-- src/rewrite/vct/rewrite/InlineApplicables.scala | 2 +- src/rewrite/vct/rewrite/bip/ComputeBipGlue.scala | 16 ++++++++-------- src/rewrite/vct/rewrite/bip/EncodeBip.scala | 12 ++++++------ src/rewrite/vct/rewrite/lang/LangCPPToCol.scala | 2 +- src/rewrite/vct/rewrite/lang/LangPVLToCol.scala | 6 +++--- .../vct/rewrite/lang/LangSpecificToCol.scala | 4 ++-- .../veymont/ParalleliseVeyMontThreads.scala | 2 +- 17 files changed, 43 insertions(+), 39 deletions(-) diff --git a/src/col/vct/col/ast/Node.scala b/src/col/vct/col/ast/Node.scala index 61f6b02e11..1f8ca48520 100644 --- a/src/col/vct/col/ast/Node.scala +++ b/src/col/vct/col/ast/Node.scala @@ -249,7 +249,7 @@ final case class ModelDo[G](model: Expr[G], perm: Expr[G], after: Expr[G], actio final class HeapVariable[G](val t: Type[G])(implicit val o: Origin) extends GlobalDeclaration[G] with HeapVariableImpl[G] final class SimplificationRule[G](val axiom: Expr[G])(implicit val o: Origin) extends GlobalDeclaration[G] with SimplificationRuleImpl[G] @scopes[Variable] final class AxiomaticDataType[G](val decls: Seq[ADTDeclaration[G]], val typeArgs: Seq[Variable[G]])(implicit val o: Origin) extends GlobalDeclaration[G] with AxiomaticDataTypeImpl[G] -final class Class[G](val typeArgs: Seq[Variable[G]], val declarations: Seq[ClassDeclaration[G]], val supports: Seq[Ref[G, Class[G]]], val intrinsicLockInvariant: Expr[G])(implicit val o: Origin) extends GlobalDeclaration[G] with ClassImpl[G] +final class Class[G](val typeArgs: Seq[Variable[G]], val decls: Seq[ClassDeclaration[G]], val supports: Seq[Ref[G, Class[G]]], val intrinsicLockInvariant: Expr[G])(implicit val o: Origin) extends GlobalDeclaration[G] with ClassImpl[G] final class Model[G](val declarations: Seq[ModelDeclaration[G]])(implicit val o: Origin) extends GlobalDeclaration[G] with Declarator[G] with ModelImpl[G] @scopes[LabelDecl] final class Function[G](val returnType: Type[G], val args: Seq[Variable[G]], val typeArgs: Seq[Variable[G]], val body: Option[Expr[G]], val contract: ApplicableContract[G], val inline: Boolean = false, val threadLocal: Boolean = false) diff --git a/src/col/vct/col/ast/declaration/global/ClassImpl.scala b/src/col/vct/col/ast/declaration/global/ClassImpl.scala index 5050c85052..64d5d8d3e6 100644 --- a/src/col/vct/col/ast/declaration/global/ClassImpl.scala +++ b/src/col/vct/col/ast/declaration/global/ClassImpl.scala @@ -1,6 +1,6 @@ package vct.col.ast.declaration.global -import vct.col.ast.Class +import vct.col.ast.{Class, Declaration} import vct.col.ast.util.Declarator import vct.col.print._ import vct.col.util.AstBuildHelpers.tt @@ -15,6 +15,8 @@ trait ClassImpl[G] extends Declarator[G] with ClassOps[G] { this: Class[G] => def transSupportArrows: Seq[(Class[G], Class[G])] = transSupportArrows(Set.empty) + override def declarations: Seq[Declaration[G]] = decls ++ typeArgs + def layoutLockInvariant(implicit ctx: Ctx): Doc = Text("lock_invariant") <+> intrinsicLockInvariant <+/> Empty diff --git a/src/col/vct/col/ast/expr/ambiguous/AmbiguousPlusImpl.scala b/src/col/vct/col/ast/expr/ambiguous/AmbiguousPlusImpl.scala index 19256fa1e5..6e63e373ad 100644 --- a/src/col/vct/col/ast/expr/ambiguous/AmbiguousPlusImpl.scala +++ b/src/col/vct/col/ast/expr/ambiguous/AmbiguousPlusImpl.scala @@ -12,8 +12,8 @@ trait AmbiguousPlusImpl[G] extends AmbiguousPlusOps[G] { this: AmbiguousPlus[G] def getValidOperatorsOf(operator: Operator[G]): Option[Seq[ContractApplicable[G]]] = { val subject = if(operator == OperatorLeftPlus[G]()) left else right val decls = subject.t match { - case TClass(Ref(cls)) => cls.declarations - case JavaTClass(Ref(cls), _) => cls.declarations + case TClass(Ref(cls)) => cls.decls + case JavaTClass(Ref(cls), _) => cls.decls case _ => return None } Some(decls.collect { diff --git a/src/col/vct/col/resolve/lang/PVL.scala b/src/col/vct/col/resolve/lang/PVL.scala index 275c4893e6..41e9465c3d 100644 --- a/src/col/vct/col/resolve/lang/PVL.scala +++ b/src/col/vct/col/resolve/lang/PVL.scala @@ -10,7 +10,7 @@ case object PVL { def findConstructor[G](t: Type[G], args: Seq[Expr[G]]): Option[PVLConstructorTarget[G]] = t match { case TClass(Ref(cls)) => - val resolvedCons = cls.declarations.collectFirst { + val resolvedCons = cls.decls.collectFirst { case cons: PVLConstructor[G] if Util.compat(args, cons.args) => RefPVLConstructor(cons) } diff --git a/src/col/vct/col/resolve/lang/Spec.scala b/src/col/vct/col/resolve/lang/Spec.scala index a875866546..367fa21264 100644 --- a/src/col/vct/col/resolve/lang/Spec.scala +++ b/src/col/vct/col/resolve/lang/Spec.scala @@ -230,7 +230,7 @@ case object Spec { def findMethod[G](obj: Expr[G], name: String): Option[InstanceMethod[G]] = obj.t match { - case TClass(Ref(cls)) => cls.declarations.flatMap(Referrable.from).collectFirst { + case TClass(Ref(cls)) => cls.decls.flatMap(Referrable.from).collectFirst { case ref @ RefInstanceMethod(decl) if ref.name == name => decl } case _ => None @@ -238,7 +238,7 @@ case object Spec { def findInstanceFunction[G](obj: Expr[G], name: String): Option[InstanceFunction[G]] = obj.t match { - case TClass(Ref(cls)) => cls.declarations.flatMap(Referrable.from).collectFirst { + case TClass(Ref(cls)) => cls.decls.flatMap(Referrable.from).collectFirst { case ref @ RefInstanceFunction(decl) if ref.name == name => decl } case _ => None @@ -246,10 +246,10 @@ case object Spec { def findInstancePredicate[G](obj: Expr[G], name: String): Option[InstancePredicate[G]] = obj.t match { - case TClass(Ref(cls)) => cls.declarations.flatMap(Referrable.from).collectFirst { + case TClass(Ref(cls)) => cls.decls.flatMap(Referrable.from).collectFirst { case ref @ RefInstancePredicate(decl) if ref.name == name => decl } - case JavaTClass(Ref(cls), _) => cls.declarations.flatMap(Referrable.from).collectFirst { + case JavaTClass(Ref(cls), _) => cls.decls.flatMap(Referrable.from).collectFirst { case ref @ RefInstancePredicate(decl) if ref.name == name => decl } case _ => None @@ -257,7 +257,7 @@ case object Spec { def findField[G](obj: Expr[G], name: String): Option[InstanceField[G]] = obj.t match { - case TClass(Ref(cls)) => cls.declarations.flatMap(Referrable.from).collectFirst { + case TClass(Ref(cls)) => cls.decls.flatMap(Referrable.from).collectFirst { case ref @ RefField(decl) if ref.name == name => decl } case _ => None diff --git a/src/col/vct/col/typerules/CoercingRewriter.scala b/src/col/vct/col/typerules/CoercingRewriter.scala index ab8c64b958..872fe9efed 100644 --- a/src/col/vct/col/typerules/CoercingRewriter.scala +++ b/src/col/vct/col/typerules/CoercingRewriter.scala @@ -1572,7 +1572,7 @@ abstract class CoercingRewriter[Pre <: Generation]() extends BaseCoercingRewrite case dataType: AxiomaticDataType[Pre] => dataType case clazz: Class[Pre] => - new Class[Pre](clazz.typeArgs, clazz.declarations, clazz.supports, res(clazz.intrinsicLockInvariant)) + new Class[Pre](clazz.typeArgs, clazz.decls, clazz.supports, res(clazz.intrinsicLockInvariant)) case enum: Enum[Pre] => enum case enumConstant: EnumConstant[Pre] => diff --git a/src/parsers/vct/parsers/transform/PVLToCol.scala b/src/parsers/vct/parsers/transform/PVLToCol.scala index 0fc2e1cf4a..21c29946b2 100644 --- a/src/parsers/vct/parsers/transform/PVLToCol.scala +++ b/src/parsers/vct/parsers/transform/PVLToCol.scala @@ -104,7 +104,7 @@ case class PVLToCol[G](override val baseOrigin: Origin, case DeclClass0(contract, _, name, typeArgs, _, decls, _) => withContract(contract, contract => { new Class( - declarations = decls.flatMap(convert(_)), + decls = decls.flatMap(convert(_)), supports = Nil, intrinsicLockInvariant = AstBuildHelpers.foldStar(contract.consume(contract.lock_invariant)), typeArgs = typeArgs.map(convert(_)).getOrElse(Nil) diff --git a/src/rewrite/vct/rewrite/ClassToRef.scala b/src/rewrite/vct/rewrite/ClassToRef.scala index 0229d57aa2..cc8f2744e5 100644 --- a/src/rewrite/vct/rewrite/ClassToRef.scala +++ b/src/rewrite/vct/rewrite/ClassToRef.scala @@ -112,9 +112,11 @@ case class ClassToRef[Pre <: Generation]() extends Rewriter[Pre] { override def dispatch(decl: Declaration[Pre]): Unit = decl match { case cls: Class[Pre] => + if(cls.typeArgs.nonEmpty) throw vct.result.VerificationError.Unreachable("Class type parameters should be encoded using monomorphization earlier") + typeNumber(cls) cls.drop() - cls.declarations.foreach { + cls.decls.foreach { case function: InstanceFunction[Pre] => implicit val o: Origin = function.o val thisVar = new Variable[Post](TRef())(This) @@ -220,7 +222,7 @@ case class ClassToRef[Pre <: Generation]() extends Rewriter[Pre] { def instantiate(cls: Class[Pre], target: Ref[Post, Variable[Post]])(implicit o: Origin): Statement[Post] = { Block(Seq( - SilverNewRef[Post](target, cls.declarations.collect { case field: InstanceField[Pre] => fieldSucc.ref(field) }), + SilverNewRef[Post](target, cls.decls.collect { case field: InstanceField[Pre] => fieldSucc.ref(field) }), Inhale(FunctionInvocation[Post](typeOf.ref(()), Seq(Local(target)), Nil, Nil, Nil)(PanicBlame("typeOf requires nothing.")) === const(typeNumber(cls))), )) } diff --git a/src/rewrite/vct/rewrite/EncodeForkJoin.scala b/src/rewrite/vct/rewrite/EncodeForkJoin.scala index bd1292956d..8601c16a2e 100644 --- a/src/rewrite/vct/rewrite/EncodeForkJoin.scala +++ b/src/rewrite/vct/rewrite/EncodeForkJoin.scala @@ -83,7 +83,7 @@ case class EncodeForkJoin[Pre <: Generation]() extends Rewriter[Pre] { case NewObject(Ref(cls)) => implicit val o: Origin = e.o - cls.declarations.collectFirst { + cls.decls.collectFirst { case run: RunMethod[Pre] => run } match { case Some(_) => diff --git a/src/rewrite/vct/rewrite/EncodeIntrinsicLock.scala b/src/rewrite/vct/rewrite/EncodeIntrinsicLock.scala index f8706cb26e..9cfe7a37ce 100644 --- a/src/rewrite/vct/rewrite/EncodeIntrinsicLock.scala +++ b/src/rewrite/vct/rewrite/EncodeIntrinsicLock.scala @@ -112,7 +112,7 @@ case class EncodeIntrinsicLock[Pre <: Generation]() extends Rewriter[Pre] { override def dispatch(decl: Declaration[Pre]): Unit = decl match { case cls: Class[Pre] => - globalDeclarations.succeed(cls, cls.rewrite(declarations = classDeclarations.collect { + globalDeclarations.succeed(cls, cls.rewrite(decls = classDeclarations.collect { if(needsInvariant(cls)) { invariant(cls) = classDeclarations.declare( new InstancePredicate(Nil, Some(dispatch(cls.intrinsicLockInvariant)))(LockInvariantOrigin(cls))) @@ -127,7 +127,7 @@ case class EncodeIntrinsicLock[Pre <: Generation]() extends Rewriter[Pre] { committed(cls) = classDeclarations.declare(new InstanceFunction(TBool(), Nil, Nil, None, contract(PanicBlame("empty contract"), decreases = Some(DecreasesClauseAssume[Post]())), false)(AbstractApplicable)) } - cls.declarations.foreach(dispatch) + cls.decls.foreach(dispatch) }._1, intrinsicLockInvariant = tt)) case other => rewriteDefault(other) } diff --git a/src/rewrite/vct/rewrite/InlineApplicables.scala b/src/rewrite/vct/rewrite/InlineApplicables.scala index 1989ecefa9..88521bd5d0 100644 --- a/src/rewrite/vct/rewrite/InlineApplicables.scala +++ b/src/rewrite/vct/rewrite/InlineApplicables.scala @@ -142,7 +142,7 @@ case class InlineApplicables[Pre <: Generation]() extends Rewriter[Pre] with Laz override def dispatch(program: Program[Pre]): Program[Post] = { program.declarations.collect { case cls: Class[Pre] => cls }.foreach { cls => - cls.declarations.foreach(classOwner(_) = cls) + cls.decls.foreach(classOwner(_) = cls) } rewriteDefault(program) } diff --git a/src/rewrite/vct/rewrite/bip/ComputeBipGlue.scala b/src/rewrite/vct/rewrite/bip/ComputeBipGlue.scala index b4c7c7f6fc..9e88bc0de4 100644 --- a/src/rewrite/vct/rewrite/bip/ComputeBipGlue.scala +++ b/src/rewrite/vct/rewrite/bip/ComputeBipGlue.scala @@ -182,19 +182,19 @@ case class ComputeBipGlue[Pre <: Generation]() extends Rewriter[Pre] with LazyLo // Collect all relevant components. It is assumed that if a component is not mentioned in the glue, we can ignore it for computing glue val classes = program.collect { - case cls: Class[Pre] if cls.declarations.exists(relevantDecls.contains) => cls + case cls: Class[Pre] if cls.decls.exists(relevantDecls.contains) => cls }.toIndexedSeq - val ports = classes.flatMap { cls => cls.declarations.collect { // TODO: Use common one for ports + val ports = classes.flatMap { cls => cls.decls.collect { // TODO: Use common one for ports case port: BipPort[Pre] => port }} val portEnablesInputsOutputs: Seq[Constraint] = classes.flatMap { cls => - val ports = cls.declarations.collect { case p: BipPort[Pre] => p } + val ports = cls.decls.collect { case p: BipPort[Pre] => p } // There might actually be multiple transitions given a port. However, these should be guaranteed to all have the same input datas. // This is because you can put multiple @Transition annotations on a method given a port, but not @Transition annotations for one // specific port on many different methods. (According to Larisa) - val transitions = cls.declarations.collect { case t: BipTransition[Pre] => t.port.decl -> t }.toMap - val outgoingDatas = cls.declarations.collect { case d: BipOutgoingData[Pre] => d } + val transitions = cls.decls.collect { case t: BipTransition[Pre] => t.port.decl -> t }.toMap + val outgoingDatas = cls.decls.collect { case d: BipOutgoingData[Pre] => d } ports.map { port => val incomingDatas = transitions(port).data.map(_.decl) PortEnablesInputsOutputs(port, outgoingDatas ++ incomingDatas) @@ -202,13 +202,13 @@ case class ComputeBipGlue[Pre <: Generation]() extends Rewriter[Pre] with LazyLo } val outputDataNeedsPort: Seq[Constraint] = classes.flatMap { cls => - val datas = cls.declarations.collect { case d: BipOutgoingData[Pre] => d } - val ports = cls.declarations.collect { case p: BipPort[Pre] => p } + val datas = cls.decls.collect { case d: BipOutgoingData[Pre] => d } + val ports = cls.decls.collect { case p: BipPort[Pre] => p } // TODO: We ignore that some datas can only be output in the case of a few specific ports, instead of all ports in the class datas.map(OutputDataNeedsPort(_, ports)) } val inputDataNeedsPort: Seq[Constraint] = classes.flatMap { cls => - val transitions = cls.declarations.collect { case t: BipTransition[Pre] => t } + val transitions = cls.decls.collect { case t: BipTransition[Pre] => t } val inDatas = transitions.flatMap(_.data.map(_.decl)) inDatas.map { data => val ports = transitions.collect { case t if t.data.map(_.decl).contains(data) => t.port.decl } diff --git a/src/rewrite/vct/rewrite/bip/EncodeBip.scala b/src/rewrite/vct/rewrite/bip/EncodeBip.scala index cf099d2bf4..8a055ea9e2 100644 --- a/src/rewrite/vct/rewrite/bip/EncodeBip.scala +++ b/src/rewrite/vct/rewrite/bip/EncodeBip.scala @@ -22,7 +22,7 @@ case object EncodeBip extends RewriterBuilderArg[VerificationResults] { object ClassBipComponent { def unapply[G](cls: Class[G]): Option[(Class[G], BipComponent[G])] = { - cls.declarations.collectFirst({ + cls.decls.collectFirst({ case bc: BipComponent[G] => (cls, bc) }) } @@ -155,17 +155,17 @@ case class EncodeBip[Pre <: Generation](results: VerificationResults) extends Re lazy val classes = program.transSubnodes.collect { case c: Class[Pre] => c }.toIndexedSeq lazy val components = classes.collect { case ClassBipComponent(cls, component) => component } - lazy val allPorts = classes.flatMap { cls => cls.declarations.collect { case port: BipPort[Pre] => port } } + lazy val allPorts = classes.flatMap { cls => cls.decls.collect { case port: BipPort[Pre] => port } } lazy val transitionToClassComponent: Map[BipTransition[Pre], (Class[Pre], BipComponent[Pre])] = classes.collect { case ClassBipComponent(cls, component) => - cls.declarations.collect { case transition: BipTransition[Pre] => (transition, (cls, component)) } + cls.decls.collect { case transition: BipTransition[Pre] => (transition, (cls, component)) } }.flatten.toMap lazy val dataToClassComponent: Map[BipData[Pre], (Class[Pre], BipComponent[Pre])] = classes.collect { case ClassBipComponent(cls, component) => - cls.declarations.collect { case data: BipData[Pre] => (data, (cls, component)) } + cls.decls.collect { case data: BipData[Pre] => (data, (cls, component)) } }.flatten.toMap lazy val guardToClass: Map[BipGuard[Pre], Class[Pre]] = classes.flatMap { cls => - cls.declarations.collect { case guard: BipGuard[Pre] => (guard, cls) } + cls.decls.collect { case guard: BipGuard[Pre] => (guard, cls) } }.toMap lazy val componentToClass: Map[BipComponent[Pre], Class[Pre]] = classes.collect { case ClassBipComponent(cls, component) => (component, cls) @@ -176,7 +176,7 @@ case class EncodeBip[Pre <: Generation](results: VerificationResults) extends Re } }.flatten.toMap lazy val portToComponent: Map[BipPort[Pre], BipComponent[Pre]] = components.flatMap { component => - classOf(component).declarations.collect { case p: BipPort[Pre] => (p, component) } + classOf(component).decls.collect { case p: BipPort[Pre] => (p, component) } }.toMap def classOf(c: BipComponent[Pre]): Class[Pre] = componentToClass(c) diff --git a/src/rewrite/vct/rewrite/lang/LangCPPToCol.scala b/src/rewrite/vct/rewrite/lang/LangCPPToCol.scala index 9d76f5635f..742453fdb8 100644 --- a/src/rewrite/vct/rewrite/lang/LangCPPToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangCPPToCol.scala @@ -891,7 +891,7 @@ case class LangCPPToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) extends L // Create the surrounding class val postEventClass = new Class[Post]( typeArgs = Seq(), - declarations = currentKernelType.get.getRangeFields ++ accessors.flatMap(acc => acc.instanceField +: acc.rangeIndexFields) ++ Seq(kernelRunner), + decls = currentKernelType.get.getRangeFields ++ accessors.flatMap(acc => acc.instanceField +: acc.rangeIndexFields) ++ Seq(kernelRunner), supports = Seq(), intrinsicLockInvariant = tt )(commandGroup.o.where(name = "SYCL_EVENT_CLASS")) diff --git a/src/rewrite/vct/rewrite/lang/LangPVLToCol.scala b/src/rewrite/vct/rewrite/lang/LangPVLToCol.scala index 65a68db399..1a6652f701 100644 --- a/src/rewrite/vct/rewrite/lang/LangPVLToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangPVLToCol.scala @@ -51,13 +51,13 @@ case class LangPVLToCol[Pre <: Generation](rw: LangSpecificToCol[Pre], veymontGe } def maybeDeclareDefaultConstructor(cls: Class[Pre]): Unit = { - if (cls.declarations.collectFirst { case _: PVLConstructor[Pre] => () }.isEmpty) { + if (cls.decls.collectFirst { case _: PVLConstructor[Pre] => () }.isEmpty) { implicit val o: Origin = cls.o val t = TClass[Post](rw.succ(cls)) val `this` = ThisObject(rw.succ[Class[Post]](cls)) val defaultBlame = PanicBlame("The postcondition of a default constructor cannot fail.") - val checkRunnable = cls.declarations.collectFirst { + val checkRunnable = cls.decls.collectFirst { case _: RunMethod[Pre] => () }.nonEmpty @@ -67,7 +67,7 @@ case class LangPVLToCol[Pre <: Generation](rw: LangSpecificToCol[Pre], veymontGe Some(Scope(Nil, Block(Nil))), ApplicableContract( UnitAccountedPredicate(tt), - UnitAccountedPredicate(AstBuildHelpers.foldStar(cls.declarations.collect { + UnitAccountedPredicate(AstBuildHelpers.foldStar(cls.decls.collect { case field: InstanceField[Pre] if field.flags.collectFirst { case _: Final[Pre] => () }.isEmpty && !veymontGeneratePermissions => fieldPerm[Post](`this`, rw.succ(field), WritePerm()) }) &* (if (checkRunnable) IdleToken(`this`) else tt)), tt, Nil, Nil, Nil, None, diff --git a/src/rewrite/vct/rewrite/lang/LangSpecificToCol.scala b/src/rewrite/vct/rewrite/lang/LangSpecificToCol.scala index 65f1b7a199..df39d564b1 100644 --- a/src/rewrite/vct/rewrite/lang/LangSpecificToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangSpecificToCol.scala @@ -128,11 +128,11 @@ case class LangSpecificToCol[Pre <: Generation](veymontGeneratePermissions: Bool currentClass.having(cls) { currentThis.having(ThisObject[Post](succ(cls))(cls.o)) { val decls = classDeclarations.collect { - cls.declarations.foreach(dispatch) + cls.decls.foreach(dispatch) pvl.maybeDeclareDefaultConstructor(cls) }._1 - globalDeclarations.succeed(cls, cls.rewrite(declarations = decls)) + globalDeclarations.succeed(cls, cls.rewrite(decls = decls)) } } diff --git a/src/rewrite/vct/rewrite/veymont/ParalleliseVeyMontThreads.scala b/src/rewrite/vct/rewrite/veymont/ParalleliseVeyMontThreads.scala index c7933a71a0..893add1c48 100644 --- a/src/rewrite/vct/rewrite/veymont/ParalleliseVeyMontThreads.scala +++ b/src/rewrite/vct/rewrite/veymont/ParalleliseVeyMontThreads.scala @@ -86,7 +86,7 @@ case class ParalleliseEndpoints[Pre <: Generation](channelClass: JavaClass[_]) e private def dispatchGivenClass(c: Class[Pre]): Class[Post] = { val rw = GivenClassRewriter() val gc = c.rewrite( - declarations = classDeclarations.collect { + decls = classDeclarations.collect { (givenClassConstrSucc.get(TClass(c.ref)).get +: c.declarations).foreach(d => rw.dispatch(d)) }._1 )(rw) From 4b9ee6a6cd10a7b05d2ad1ce4db920745ec1671c Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Wed, 21 Feb 2024 16:27:20 +0100 Subject: [PATCH 04/52] Add MonomorphizeClass rewrite --- src/main/vct/main/stages/Transformation.scala | 5 +- .../vct/rewrite/MonomorphizeClass.scala | 69 +++++++++++++++++++ .../MonomorphizeContractApplicables.scala | 4 +- 3 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 src/rewrite/vct/rewrite/MonomorphizeClass.scala diff --git a/src/main/vct/main/stages/Transformation.scala b/src/main/vct/main/stages/Transformation.scala index c95973ca3a..c1d25f77a1 100644 --- a/src/main/vct/main/stages/Transformation.scala +++ b/src/main/vct/main/stages/Transformation.scala @@ -22,9 +22,9 @@ import vct.options.Options import vct.options.types.{Backend, PathOrStd} import vct.resources.Resources import vct.result.VerificationError.SystemError -import vct.rewrite.{EncodeResourceValues, ExplicitResourceValues, HeapVariableToRef, SmtlibToProverTypes} +import vct.rewrite.{EncodeResourceValues, ExplicitResourceValues, HeapVariableToRef, MonomorphizeClass, SmtlibToProverTypes} import vct.rewrite.lang.ReplaceSYCLTypes -import vct.rewrite.veymont.{DeduplicateSeqGuards, EncodeSeqBranchUnanimity, EncodeSeqProg, GenerateSeqProgPermissions, EncodeUnpointedGuard, SplitSeqGuards} +import vct.rewrite.veymont.{DeduplicateSeqGuards, EncodeSeqBranchUnanimity, EncodeSeqProg, EncodeUnpointedGuard, GenerateSeqProgPermissions, SplitSeqGuards} object Transformation { case class TransformationCheckError(pass: RewriterBuilder, errors: Seq[(Program[_], CheckError)]) extends SystemError { @@ -262,6 +262,7 @@ case class SilverTransformation EncodeTryThrowSignals, ResolveScale, + MonomorphizeClass, // No more classes ClassToRef, HeapVariableToRef, diff --git a/src/rewrite/vct/rewrite/MonomorphizeClass.scala b/src/rewrite/vct/rewrite/MonomorphizeClass.scala new file mode 100644 index 0000000000..1f1c53bc9c --- /dev/null +++ b/src/rewrite/vct/rewrite/MonomorphizeClass.scala @@ -0,0 +1,69 @@ +package vct.rewrite + +import hre.util.ScopedStack +import vct.col.ast._ +import vct.col.ref.{LazyRef, Ref} +import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder, Rewritten} +import vct.col.util.AstBuildHelpers.ContractApplicableBuildHelpers + +import scala.collection.mutable + +case object MonomorphizeClass extends RewriterBuilder { + override def key: String = "monomorphizeClasses" + override def desc: String = "Monomorphize generic classes" +} + +case class MonomorphizeClass[Pre <: Generation]() extends Rewriter[Pre] { + val currentSubstitutions: ScopedStack[Map[Variable[Pre], Type[Post]]] = ScopedStack() + + val monomorphizedRef: mutable.Map[(Class[Pre], Seq[Type[Post]]), Ref[Post, Class[Post]]] = mutable.Map() + val monomorphizedImpl: mutable.Map[(Class[Pre], Seq[Type[Post]]), Class[Post]] = mutable.Map() + + def getOrBuild(cls: Class[Pre], typeValues: Seq[Type[Post]]): Ref[Post, Class[Post]] = + monomorphizedRef.get((cls, typeValues)) match { + case Some(ref) => ref + case None => + monomorphizedRef((cls, typeValues)) = new LazyRef(monomorphizedImpl((cls, typeValues))) + monomorphizedImpl((cls, typeValues)) = currentSubstitutions.having(cls.typeArgs.zip(typeValues).toMap) { + globalDeclarations.scope { + classDeclarations.scope { + variables.scope { + allScopes.anyDeclare(allScopes.anySucceedOnly(cls, cls.rewrite(typeArgs = Seq()))) + } + } + } + } + monomorphizedRef((cls, typeValues)) + } + + + override def dispatch(decl: Declaration[Pre]): Unit = decl match { + case cls: Class[Pre] if cls.typeArgs.nonEmpty => ??? +// case app: ContractApplicable[Pre] if app.typeArgs.nonEmpty => +// app.typeArgs.foreach(_.drop()) +// getOrBuild(app, app.typeArgs.map(v => dispatch(v.t.asInstanceOf[TType[Pre]].t))) + case other => other.rewriteDefault() + } + + /* + override def dispatch(e: Expr[Pre]): Expr[Rewritten[Pre]] = e match { + case inv: Invocation[Pre] if inv.ref.decl.typeArgs.nonEmpty => + val typeValues = inv.typeArgs.map(dispatch) + val ref = getOrBuild(inv.ref.decl, typeValues) + + inv match { + case inv: ProcedureInvocation[Pre] => inv.rewrite(ref = ref.asInstanceOf[Ref[Post, Procedure[Post]]], typeArgs = Nil) + case inv: MethodInvocation[Pre] => inv.rewrite(ref = ref.asInstanceOf[Ref[Post, InstanceMethod[Post]]], typeArgs = Nil) + case inv: FunctionInvocation[Pre] => inv.rewrite(ref = ref.asInstanceOf[Ref[Post, Function[Post]]], typeArgs = Nil) + case inv: InstanceFunctionInvocation[Pre] => inv.rewrite(ref = ref.asInstanceOf[Ref[Post, InstanceFunction[Post]]], typeArgs = Nil) + } + case other => rewriteDefault(other) + } + */ + + override def dispatch(t: Type[Pre]): Type[Rewritten[Pre]] = t match { + case TVar(Ref(v)) => + currentSubstitutions.top(v) + case other => other.rewriteDefault() + } +} diff --git a/src/rewrite/vct/rewrite/MonomorphizeContractApplicables.scala b/src/rewrite/vct/rewrite/MonomorphizeContractApplicables.scala index 71d2e4d0ef..30cd8ee9b0 100644 --- a/src/rewrite/vct/rewrite/MonomorphizeContractApplicables.scala +++ b/src/rewrite/vct/rewrite/MonomorphizeContractApplicables.scala @@ -13,8 +13,8 @@ import scala.collection.mutable import scala.reflect.ClassTag case object MonomorphizeContractApplicables extends RewriterBuilder { - override def key: String = "monomorphize" - override def desc: String = "Monomorphize generic declarations with their usages where Silver does not support generics." + override def key: String = "monomorphizeApplicables" + override def desc: String = "Monomorphize generic applicable declarations with their usages where Silver does not support generics." } case class MonomorphizeContractApplicables[Pre <: Generation]() extends Rewriter[Pre] { From bcb43bf1684cd7aac76d5d960fb3c6e6bb54a379 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Wed, 21 Feb 2024 16:59:19 +0100 Subject: [PATCH 05/52] Propagate generics to a whole bunch of places --- src/col/vct/col/ast/Node.scala | 7 ++++--- .../col/ast/declaration/singular/EndpointImpl.scala | 2 +- .../col/ast/expr/ambiguous/AmbiguousPlusImpl.scala | 2 +- .../vct/col/ast/expr/context/AmbiguousThisImpl.scala | 2 +- src/col/vct/col/ast/expr/context/ThisObjectImpl.scala | 4 ++-- .../vct/col/ast/expr/heap/alloc/NewObjectImpl.scala | 2 +- .../col/ast/family/coercion/CoerceNullClassImpl.scala | 2 +- .../col/ast/family/coercion/CoerceSupportsImpl.scala | 5 +++-- src/col/vct/col/ast/lang/pvl/PVLLocalImpl.scala | 2 +- src/col/vct/col/ast/unsorted/ConstructorImpl.scala | 4 ++-- src/col/vct/col/resolve/Resolve.scala | 5 +++-- src/col/vct/col/resolve/lang/Java.scala | 2 +- src/col/vct/col/resolve/lang/PVL.scala | 11 +++++++---- src/col/vct/col/resolve/lang/Spec.scala | 8 ++++---- src/col/vct/col/typerules/CoercingRewriter.scala | 4 ++-- src/col/vct/col/typerules/CoercionUtils.scala | 8 ++++---- src/col/vct/col/typerules/Types.scala | 9 +++++---- src/rewrite/vct/rewrite/ClassToRef.scala | 4 ++-- src/rewrite/vct/rewrite/ConstantifyFinalFields.scala | 2 +- src/rewrite/vct/rewrite/EncodeArrayValues.scala | 2 +- src/rewrite/vct/rewrite/EncodeForkJoin.scala | 2 +- src/rewrite/vct/rewrite/EncodeIntrinsicLock.scala | 2 +- 22 files changed, 49 insertions(+), 42 deletions(-) diff --git a/src/col/vct/col/ast/Node.scala b/src/col/vct/col/ast/Node.scala index 1f8ca48520..ee2d4f7f77 100644 --- a/src/col/vct/col/ast/Node.scala +++ b/src/col/vct/col/ast/Node.scala @@ -157,7 +157,7 @@ final case class TZFraction[G]()(implicit val o: Origin = DiagnosticOrigin) exte sealed trait DeclaredType[G] extends Type[G] with DeclaredTypeImpl[G] final case class TModel[G](model: Ref[G, Model[G]])(implicit val o: Origin = DiagnosticOrigin) extends DeclaredType[G] with TModelImpl[G] -final case class TClass[G](cls: Ref[G, Class[G]])(implicit val o: Origin = DiagnosticOrigin) extends DeclaredType[G] with TClassImpl[G] +final case class TClass[G](cls: Ref[G, Class[G]], typeArgs: Seq[Type[G]])(implicit val o: Origin = DiagnosticOrigin) extends DeclaredType[G] with TClassImpl[G] final case class TAnyClass[G]()(implicit val o: Origin = DiagnosticOrigin) extends DeclaredType[G] with TAnyClassImpl[G] final case class TAxiomatic[G](adt: Ref[G, AxiomaticDataType[G]], args: Seq[Type[G]])(implicit val o: Origin = DiagnosticOrigin) extends DeclaredType[G] with TAxiomaticImpl[G] final case class TEnum[G](enum: Ref[G, Enum[G]])(implicit val o: Origin = DiagnosticOrigin) extends DeclaredType[G] with TEnumImpl[G] @@ -376,7 +376,7 @@ final case class CoerceResourceValResource[G]()(implicit val o: Origin) extends final case class CoerceNullRef[G]()(implicit val o: Origin) extends Coercion[G] with CoerceNullRefImpl[G] final case class CoerceNullArray[G](arrayElementType: Type[G])(implicit val o: Origin) extends Coercion[G] with CoerceNullArrayImpl[G] -final case class CoerceNullClass[G](targetClass: Ref[G, Class[G]])(implicit val o: Origin) extends Coercion[G] with CoerceNullClassImpl[G] +final case class CoerceNullClass[G](targetClass: Ref[G, Class[G]], typeArgs: Seq[Type[G]])(implicit val o: Origin) extends Coercion[G] with CoerceNullClassImpl[G] final case class CoerceNullJavaClass[G](targetClass: Ref[G, JavaClassOrInterface[G]])(implicit val o: Origin) extends Coercion[G] with CoerceNullJavaClassImpl[G] final case class CoerceNullAnyClass[G]()(implicit val o: Origin) extends Coercion[G] with CoerceNullAnyClassImpl[G] final case class CoerceNullPointer[G](pointerElementType: Type[G])(implicit val o: Origin) extends Coercion[G] with CoerceNullPointerImpl[G] @@ -406,7 +406,7 @@ final case class CoerceBoundIntFloat[G](source: Type[G], target: Type[G])(implic final case class CoerceSupports[G](sourceClass: Ref[G, Class[G]], targetClass: Ref[G, Class[G]])(implicit val o: Origin) extends Coercion[G] with CoerceSupportsImpl[G] final case class CoerceJavaSupports[G](sourceClass: Ref[G, JavaClassOrInterface[G]], targetClass: Ref[G, JavaClassOrInterface[G]])(implicit val o: Origin) extends Coercion[G] with CoerceJavaSupportsImpl[G] -final case class CoerceClassAnyClass[G](sourceClass: Ref[G, Class[G]])(implicit val o: Origin) extends Coercion[G] with CoerceClassAnyClassImpl[G] +final case class CoerceClassAnyClass[G](sourceClass: Ref[G, Class[G]], typeArgs: Seq[Type[G]])(implicit val o: Origin) extends Coercion[G] with CoerceClassAnyClassImpl[G] final case class CoerceJavaClassAnyClass[G](sourceClass: Ref[G, JavaClassOrInterface[G]])(implicit val o: Origin) extends Coercion[G] with CoerceJavaClassAnyClassImpl[G] final case class CoerceCPrimitiveToCol[G](source: Type[G], target: Type[G])(implicit val o: Origin) extends Coercion[G] with CoerceCPrimitiveToColImpl[G] @@ -1315,6 +1315,7 @@ final case class TSeqProg[G](cls: Ref[G, SeqProg[G]])(implicit val o: Origin = D final case class TPVLSeqProg[G](cls: Ref[G, PVLSeqProg[G]])(implicit val o: Origin = DiagnosticOrigin) extends DeclaredType[G] with TPVLSeqProgImpl[G] final case class TEndpoint[G](cls: Ref[G, Endpoint[G]])(implicit val o: Origin = DiagnosticOrigin) extends DeclaredType[G] with TEndpointImpl[G] +// TODO (RR): Integrate generics final class PVLEndpoint[G](val name: String, val cls: Ref[G, Class[G]], val args: Seq[Expr[G]])(val blame: Blame[EndpointFailure])(implicit val o: Origin) extends ClassDeclaration[G] with PVLEndpointImpl[G] { var ref: Option[PVLConstructorTarget[G]] = None } diff --git a/src/col/vct/col/ast/declaration/singular/EndpointImpl.scala b/src/col/vct/col/ast/declaration/singular/EndpointImpl.scala index ec8dbae184..7e813e6aa5 100644 --- a/src/col/vct/col/ast/declaration/singular/EndpointImpl.scala +++ b/src/col/vct/col/ast/declaration/singular/EndpointImpl.scala @@ -8,5 +8,5 @@ trait EndpointImpl[G] extends EndpointOps[G] with EndpointFamilyOps[G] { this: E override def layout(implicit ctx: Ctx): Doc = Group(Text("endpoint") <+> ctx.name(this) <+> "=" <>> { Group(t.show <> "(" <> Doc.args(args) <> ");") }) - def t: Type[G] = TClass(cls) + def t: Type[G] = TClass(cls, Seq()) } diff --git a/src/col/vct/col/ast/expr/ambiguous/AmbiguousPlusImpl.scala b/src/col/vct/col/ast/expr/ambiguous/AmbiguousPlusImpl.scala index 6e63e373ad..e8df7857bb 100644 --- a/src/col/vct/col/ast/expr/ambiguous/AmbiguousPlusImpl.scala +++ b/src/col/vct/col/ast/expr/ambiguous/AmbiguousPlusImpl.scala @@ -12,7 +12,7 @@ trait AmbiguousPlusImpl[G] extends AmbiguousPlusOps[G] { this: AmbiguousPlus[G] def getValidOperatorsOf(operator: Operator[G]): Option[Seq[ContractApplicable[G]]] = { val subject = if(operator == OperatorLeftPlus[G]()) left else right val decls = subject.t match { - case TClass(Ref(cls)) => cls.decls + case TClass(Ref(cls), _) => cls.decls case JavaTClass(Ref(cls), _) => cls.decls case _ => return None } diff --git a/src/col/vct/col/ast/expr/context/AmbiguousThisImpl.scala b/src/col/vct/col/ast/expr/context/AmbiguousThisImpl.scala index 4b9a8b7b3b..445936baf5 100644 --- a/src/col/vct/col/ast/expr/context/AmbiguousThisImpl.scala +++ b/src/col/vct/col/ast/expr/context/AmbiguousThisImpl.scala @@ -13,7 +13,7 @@ trait AmbiguousThisImpl[G] extends AmbiguousThisOps[G] { this: AmbiguousThis[G] "'this' encountered, but the surrounding class is not resolved.") ) match { case RefJavaClass(decl) => JavaTClass(decl.ref, Nil) - case RefClass(decl) => TClass(decl.ref) + case RefClass(decl) => TClass(decl.ref, decl.typeArgs.map((v: Variable[G]) => TVar(v.ref[Variable[G]]))) case RefModel(decl) => TModel(decl.ref) case RefPVLSeqProg(decl) => TPVLSeqProg(decl.ref) case RefSeqProg(decl) => TSeqProg(decl.ref) diff --git a/src/col/vct/col/ast/expr/context/ThisObjectImpl.scala b/src/col/vct/col/ast/expr/context/ThisObjectImpl.scala index e196ee1cf9..f12ed0722a 100644 --- a/src/col/vct/col/ast/expr/context/ThisObjectImpl.scala +++ b/src/col/vct/col/ast/expr/context/ThisObjectImpl.scala @@ -1,12 +1,12 @@ package vct.col.ast.expr.context -import vct.col.ast.{Constructor, TClass, ThisObject, Type} +import vct.col.ast.{Constructor, TClass, TVar, ThisObject, Type, Variable} import vct.col.print.{Ctx, Doc, Precedence, Text} import vct.col.ast.ops.ThisObjectOps import vct.col.check.{CheckContext, CheckError, ThisInConstructorPre} trait ThisObjectImpl[G] extends ThisDeclarationImpl[G] with ThisObjectOps[G] { this: ThisObject[G] => - override def t: Type[G] = TClass(cls) + override def t: Type[G] = TClass(cls, cls.decl.typeArgs.map(v => TVar(v.ref[Variable[G]]))) override def check(context: CheckContext[G]): Seq[CheckError] = { val inConstructor = context.declarationStack.collectFirst { case _: Constructor[G] => () }.nonEmpty diff --git a/src/col/vct/col/ast/expr/heap/alloc/NewObjectImpl.scala b/src/col/vct/col/ast/expr/heap/alloc/NewObjectImpl.scala index d1ecc069ea..6de0272e86 100644 --- a/src/col/vct/col/ast/expr/heap/alloc/NewObjectImpl.scala +++ b/src/col/vct/col/ast/expr/heap/alloc/NewObjectImpl.scala @@ -5,7 +5,7 @@ import vct.col.print.{Ctx, Doc, Precedence, Text} import vct.col.ast.ops.NewObjectOps trait NewObjectImpl[G] extends NewObjectOps[G] { this: NewObject[G] => - override def t: Type[G] = TClass(cls) + override def t: Type[G] = TClass(cls, Seq()) override def precedence: Int = Precedence.POSTFIX override def layout(implicit ctx: Ctx): Doc = diff --git a/src/col/vct/col/ast/family/coercion/CoerceNullClassImpl.scala b/src/col/vct/col/ast/family/coercion/CoerceNullClassImpl.scala index a3c6d40800..d52c5635db 100644 --- a/src/col/vct/col/ast/family/coercion/CoerceNullClassImpl.scala +++ b/src/col/vct/col/ast/family/coercion/CoerceNullClassImpl.scala @@ -4,5 +4,5 @@ import vct.col.ast.{CoerceNullClass, TClass} import vct.col.ast.ops.CoerceNullClassOps trait CoerceNullClassImpl[G] extends CoerceNullClassOps[G] { this: CoerceNullClass[G] => - override def target: TClass[G] = TClass(targetClass) + override def target: TClass[G] = TClass(targetClass, typeArgs) } diff --git a/src/col/vct/col/ast/family/coercion/CoerceSupportsImpl.scala b/src/col/vct/col/ast/family/coercion/CoerceSupportsImpl.scala index 316573ed05..3585d774d6 100644 --- a/src/col/vct/col/ast/family/coercion/CoerceSupportsImpl.scala +++ b/src/col/vct/col/ast/family/coercion/CoerceSupportsImpl.scala @@ -3,6 +3,7 @@ package vct.col.ast.family.coercion import vct.col.ast.{CoerceSupports, TClass} import vct.col.ast.ops.CoerceSupportsOps -trait CoerceSupportsImpl[G] extends CoerceSupportsOps[G] { this: CoerceSupports[G] => - override def target: TClass[G] = TClass(targetClass) +trait CoerceSupportsImpl[G] extends CoerceSupportsOps[G] { this: CoerceSupports[G] => + // TODO (RR): Integrate coercions with generics? + override def target: TClass[G] = TClass(targetClass, { assert(sourceClass.decl.typeArgs.isEmpty); Seq() }) } diff --git a/src/col/vct/col/ast/lang/pvl/PVLLocalImpl.scala b/src/col/vct/col/ast/lang/pvl/PVLLocalImpl.scala index 0b0945e825..1ce667cc92 100644 --- a/src/col/vct/col/ast/lang/pvl/PVLLocalImpl.scala +++ b/src/col/vct/col/ast/lang/pvl/PVLLocalImpl.scala @@ -15,7 +15,7 @@ trait PVLLocalImpl[G] extends PVLLocalOps[G] { this: PVLLocal[G] => case ref: RefField[G] => ref.decl.t case ref: RefModelField[G] => ref.decl.t case ref: RefEndpoint[G] => ref.decl.t - case ref: RefPVLEndpoint[G] => TClass[G](ref.decl.cls.decl.ref) + case ref: RefPVLEndpoint[G] => TClass[G](ref.decl.cls.decl.ref, Seq()) case RefEnumConstant(enum, _) => TEnum(enum.get.ref) } diff --git a/src/col/vct/col/ast/unsorted/ConstructorImpl.scala b/src/col/vct/col/ast/unsorted/ConstructorImpl.scala index a7affb3a8a..2197453af4 100644 --- a/src/col/vct/col/ast/unsorted/ConstructorImpl.scala +++ b/src/col/vct/col/ast/unsorted/ConstructorImpl.scala @@ -1,12 +1,12 @@ package vct.col.ast.unsorted -import vct.col.ast.{Constructor, Statement, TClass} +import vct.col.ast.{Constructor, Statement, TClass, TVar, Variable} import vct.col.ast.ops.ConstructorOps import vct.col.print._ trait ConstructorImpl[G] extends ConstructorOps[G] { this: Constructor[G] => override def pure: Boolean = false - override def returnType: TClass[G] = TClass(cls) + override def returnType: TClass[G] = TClass(cls, cls.decl.typeArgs.map((v: Variable[G]) => TVar(v.ref))) // override def layout(implicit ctx: Ctx): Doc = ??? } diff --git a/src/col/vct/col/resolve/Resolve.scala b/src/col/vct/col/resolve/Resolve.scala index 631279eed3..810481a6c2 100644 --- a/src/col/vct/col/resolve/Resolve.scala +++ b/src/col/vct/col/resolve/Resolve.scala @@ -141,7 +141,7 @@ case object ResolveTypes { throw NoSuchNameError("class", name, t))) case t @ TModel(ref) => ref.tryResolve(name => Spec.findModel(name, ctx).getOrElse(throw NoSuchNameError("model", name, t))) - case t @ TClass(ref) => + case t @ TClass(ref, _) => ref.tryResolve(name => Spec.findClass(name, ctx).getOrElse(throw NoSuchNameError("class", name, t))) case t @ TAxiomatic(ref, _) => ref.tryResolve(name => Spec.findAdt(name, ctx).getOrElse(throw NoSuchNameError("adt", name, t))) @@ -411,7 +411,8 @@ case object ResolveReferences extends LazyLogging { case access@PVLAccess(subject, field) => access.ref = Some(PVL.findDerefOfClass(subject.cls, field).getOrElse(throw NoSuchNameError("field", field, access))) case endpoint: PVLEndpoint[G] => - endpoint.ref = Some(PVL.findConstructor(TClass(endpoint.cls.decl.ref[Class[G]]), endpoint.args).getOrElse(throw ConstructorNotFound(endpoint))) + // TODO (RR): Integrate generics + endpoint.ref = Some(PVL.findConstructor(TClass(endpoint.cls.decl.ref[Class[G]], Seq()), endpoint.args).getOrElse(throw ConstructorNotFound(endpoint))) case parAssign: PVLSeqAssign[G] => parAssign.receiver.tryResolve(receiver => PVL.findName(receiver, ctx) match { case Some(RefPVLEndpoint(decl)) => decl diff --git a/src/col/vct/col/resolve/lang/Java.scala b/src/col/vct/col/resolve/lang/Java.scala index bf46b73e1c..ff6c13e93a 100644 --- a/src/col/vct/col/resolve/lang/Java.scala +++ b/src/col/vct/col/resolve/lang/Java.scala @@ -485,7 +485,7 @@ case object Java extends LazyLogging { case t: TFloat[G] => const(0) case TRational() => const(0) case TZFraction() => const(0) - case TClass(_) => Null() + case TClass(_, _) => Null() case JavaTClass(_, _) => Null() case TEnum(_) => Null() case TAnyClass() => Null() diff --git a/src/col/vct/col/resolve/lang/PVL.scala b/src/col/vct/col/resolve/lang/PVL.scala index 41e9465c3d..0b23877798 100644 --- a/src/col/vct/col/resolve/lang/PVL.scala +++ b/src/col/vct/col/resolve/lang/PVL.scala @@ -7,9 +7,11 @@ import vct.col.ref.Ref import vct.col.resolve.ctx._ case object PVL { - def findConstructor[G](t: Type[G], args: Seq[Expr[G]]): Option[PVLConstructorTarget[G]] = + // TODO (RR): Is more logic required here in the case of generic arguments? Probably for all methods here...? See also: Spec.scala + + def findConstructor[G](t: Type[G], args: Seq[Expr[G]]): Option[PVLConstructorTarget[G]] = { t match { - case TClass(Ref(cls)) => + case TClass(Ref(cls), _) => val resolvedCons = cls.decls.collectFirst { case cons: PVLConstructor[G] if Util.compat(args, cons.args) => RefPVLConstructor(cons) } @@ -21,6 +23,7 @@ case object PVL { case TModel(Ref(model)) if args.isEmpty => Some(RefModel(model)) case _ => None } + } def findTypeName[G](name: String, ctx: TypeResolutionContext[G]): Option[PVLTypeNameTarget[G]] = ctx.stack.flatten.collectFirst { @@ -46,7 +49,7 @@ case object PVL { case TModel(ref) => ref.decl.declarations.flatMap(Referrable.from).collectFirst { case ref: RefModelField[G] if ref.name == name => ref } - case TClass(ref) => findDerefOfClass(ref.decl, name) + case TClass(ref, _) => findDerefOfClass(ref.decl, name) case _ => Spec.builtinField(obj, name, blame) } @@ -62,7 +65,7 @@ case object PVL { case ref: RefModelAction[G] if ref.name == method => ref case ref: RefModelProcess[G] if ref.name == method => ref }.orElse(Spec.builtinInstanceMethod(obj, method, blame)) - case TClass(ref) => ref.decl.declarations.flatMap(Referrable.from).collectFirst { + case TClass(ref, _) => ref.decl.declarations.flatMap(Referrable.from).collectFirst { case ref: RefInstanceFunction[G] if ref.name == method && Util.compat(args, typeArgs, ref.decl) => ref case ref: RefInstanceMethod[G] if ref.name == method && Util.compat(args, typeArgs, ref.decl) => ref case ref: RefInstancePredicate[G] if ref.name == method && Util.compat(args, ref.decl.args) => ref diff --git a/src/col/vct/col/resolve/lang/Spec.scala b/src/col/vct/col/resolve/lang/Spec.scala index 367fa21264..e5324bf877 100644 --- a/src/col/vct/col/resolve/lang/Spec.scala +++ b/src/col/vct/col/resolve/lang/Spec.scala @@ -230,7 +230,7 @@ case object Spec { def findMethod[G](obj: Expr[G], name: String): Option[InstanceMethod[G]] = obj.t match { - case TClass(Ref(cls)) => cls.decls.flatMap(Referrable.from).collectFirst { + case TClass(Ref(cls), _) => cls.decls.flatMap(Referrable.from).collectFirst { case ref @ RefInstanceMethod(decl) if ref.name == name => decl } case _ => None @@ -238,7 +238,7 @@ case object Spec { def findInstanceFunction[G](obj: Expr[G], name: String): Option[InstanceFunction[G]] = obj.t match { - case TClass(Ref(cls)) => cls.decls.flatMap(Referrable.from).collectFirst { + case TClass(Ref(cls), _) => cls.decls.flatMap(Referrable.from).collectFirst { case ref @ RefInstanceFunction(decl) if ref.name == name => decl } case _ => None @@ -246,7 +246,7 @@ case object Spec { def findInstancePredicate[G](obj: Expr[G], name: String): Option[InstancePredicate[G]] = obj.t match { - case TClass(Ref(cls)) => cls.decls.flatMap(Referrable.from).collectFirst { + case TClass(Ref(cls), _) => cls.decls.flatMap(Referrable.from).collectFirst { case ref @ RefInstancePredicate(decl) if ref.name == name => decl } case JavaTClass(Ref(cls), _) => cls.decls.flatMap(Referrable.from).collectFirst { @@ -257,7 +257,7 @@ case object Spec { def findField[G](obj: Expr[G], name: String): Option[InstanceField[G]] = obj.t match { - case TClass(Ref(cls)) => cls.decls.flatMap(Referrable.from).collectFirst { + case TClass(Ref(cls), _) => cls.decls.flatMap(Referrable.from).collectFirst { case ref @ RefField(decl) if ref.name == name => decl } case _ => None diff --git a/src/col/vct/col/typerules/CoercingRewriter.scala b/src/col/vct/col/typerules/CoercingRewriter.scala index 872fe9efed..371f467235 100644 --- a/src/col/vct/col/typerules/CoercingRewriter.scala +++ b/src/col/vct/col/typerules/CoercingRewriter.scala @@ -179,7 +179,7 @@ abstract class CoercingRewriter[Pre <: Generation]() extends BaseCoercingRewrite case CoerceSelectUnion(inner, _, _, _) => applyCoercion(e, inner) case CoerceSupports(_, _) => e - case CoerceClassAnyClass(_) => e + case CoerceClassAnyClass(_, _) => e case CoerceJavaSupports(_, _) => e case CoerceJavaClassAnyClass(_) => e case CoerceCPrimitiveToCol(_, _) => e @@ -188,7 +188,7 @@ abstract class CoercingRewriter[Pre <: Generation]() extends BaseCoercingRewrite case CoerceColToCPPPrimitive(_, _) => e case CoerceNullRef() => e case CoerceNullArray(_) => e - case CoerceNullClass(_) => e + case CoerceNullClass(_, _) => e case CoerceNullJavaClass(_) => e case CoerceNullAnyClass() => e case CoerceNullPointer(_) => e diff --git a/src/col/vct/col/typerules/CoercionUtils.scala b/src/col/vct/col/typerules/CoercionUtils.scala index e28cb832b5..0eb72aa475 100644 --- a/src/col/vct/col/typerules/CoercionUtils.scala +++ b/src/col/vct/col/typerules/CoercionUtils.scala @@ -62,7 +62,7 @@ case object CoercionUtils { case (TNull(), TRef()) => CoerceNullRef() case (TNull(), TArray(target)) => CoerceNullArray(target) - case (TNull(), TClass(target)) => CoerceNullClass(target) + case (TNull(), TClass(target, typeArgs)) => CoerceNullClass(target, typeArgs) case (TNull(), JavaTClass(target, _)) => CoerceNullJavaClass(target) case (TNull(), TAnyClass()) => CoerceNullAnyClass() case (TNull(), TPointer(target)) => CoerceNullPointer(target) @@ -118,12 +118,12 @@ case object CoercionUtils { case (source: TBoundedInt[G], TRational()) => CoercionSequence(Seq(CoerceUnboundInt(source, TInt()), CoerceIntRat())) case (_: IntType[G], TRational()) => CoerceIntRat() - case (source @ TClass(sourceClass), target @ TClass(targetClass)) + case (source @ TClass(sourceClass, Seq()), target @ TClass(targetClass, Seq())) if source.transSupportArrows.exists { case (_, supp) => supp == targetClass.decl } => CoerceSupports(sourceClass, targetClass) - case (source @ TClass(sourceClass), TAnyClass()) => - CoerceClassAnyClass(sourceClass) + case (source @ TClass(sourceClass, typeArgs), TAnyClass()) => + CoerceClassAnyClass(sourceClass, typeArgs) case (source @ JavaTClass(sourceClass, Nil), target @ JavaTClass(targetClass, Nil)) if sourceClass.decl.transSupportArrows(Set.empty).exists { case (_, supp) => supp == targetClass.decl } => diff --git a/src/col/vct/col/typerules/Types.scala b/src/col/vct/col/typerules/Types.scala index cd50410071..66c41a0431 100644 --- a/src/col/vct/col/typerules/Types.scala +++ b/src/col/vct/col/typerules/Types.scala @@ -51,7 +51,8 @@ object Types { case (TType(left), TType(right)) => TType(leastCommonSuperType(left, right)) - case (TClass(left), TClass(right)) => + case (TClass(left, Seq()), TClass(right, Seq())) => + // TODO (RR): Not sure how generics factor into this part... See also: next match statement val leftArrows = left.decl.transSupportArrows val rightArrows = right.decl.transSupportArrows // Shared support are classes where there is an incoming left-arrow and right-arrow @@ -63,11 +64,11 @@ object Types { val classes = (shared.toSet -- nonBottom.toSet).toSeq classes match { case Nil => TAnyClass() - case Seq(t) => TClass(t.ref) - case other => TUnion(other.map(cls => TClass(cls.ref))) + case Seq(t) => TClass(t.ref, Seq()) + case other => TUnion(other.map(cls => TClass(cls.ref, Seq()))) } - case (TClass(_), TAnyClass()) | (TAnyClass(), TClass(_)) => + case (TClass(_, _), TAnyClass()) | (TAnyClass(), TClass(_, _)) => TAnyClass() // TODO similar stuff for JavaClass diff --git a/src/rewrite/vct/rewrite/ClassToRef.scala b/src/rewrite/vct/rewrite/ClassToRef.scala index cc8f2744e5..8b6833e73a 100644 --- a/src/rewrite/vct/rewrite/ClassToRef.scala +++ b/src/rewrite/vct/rewrite/ClassToRef.scala @@ -286,7 +286,7 @@ case class ClassToRef[Pre <: Generation]() extends Rewriter[Pre] { case deref @ Deref(obj, Ref(field)) => SilverDeref[Post](dispatch(obj), fieldSucc.ref(field))(deref.blame)(deref.o) case TypeValue(t) => t match { - case TClass(Ref(cls)) => const(typeNumber(cls))(e.o) + case TClass(Ref(cls), Seq()) => const(typeNumber(cls))(e.o) case other => ??? } case TypeOf(value) => FunctionInvocation[Post](typeOf.ref(()), Seq(dispatch(value)), Nil, Nil, Nil)(PanicBlame("typeOf requires nothing"))(e.o) @@ -311,7 +311,7 @@ case class ClassToRef[Pre <: Generation]() extends Rewriter[Pre] { } override def dispatch(t: Type[Pre]): Type[Post] = t match { - case TClass(_) => TRef() + case TClass(_, _) => TRef() case TAnyClass() => TRef() case t => rewriteDefault(t) } diff --git a/src/rewrite/vct/rewrite/ConstantifyFinalFields.scala b/src/rewrite/vct/rewrite/ConstantifyFinalFields.scala index 8e4e1ad82c..155b83d0ac 100644 --- a/src/rewrite/vct/rewrite/ConstantifyFinalFields.scala +++ b/src/rewrite/vct/rewrite/ConstantifyFinalFields.scala @@ -66,7 +66,7 @@ case class ConstantifyFinalFields[Pre <: Generation]() extends Rewriter[Pre] { case field: InstanceField[Pre] => implicit val o: Origin = field.o if(isFinal(field)) { - val `this` = new Variable[Post](TClass(succ(currentClass.top))) + val `this` = new Variable[Post](TClass(succ(currentClass.top), currentClass.top.typeArgs.map { v: Variable[Pre] => TVar(succ(v)) })) fieldFunction(field) = globalDeclarations.declare( withResult((result: Result[Post]) => function[Post]( blame = AbstractApplicable, diff --git a/src/rewrite/vct/rewrite/EncodeArrayValues.scala b/src/rewrite/vct/rewrite/EncodeArrayValues.scala index 728354d3ca..1c944f82a2 100644 --- a/src/rewrite/vct/rewrite/EncodeArrayValues.scala +++ b/src/rewrite/vct/rewrite/EncodeArrayValues.scala @@ -266,7 +266,7 @@ case class EncodeArrayValues[Pre <: Generation]() extends Rewriter[Pre] { implicit val o: Origin = origin val fields = structType match { - case TClass(ref) => ref.decl.declarations.collect { case field: InstanceField[Post] => field } + case TClass(ref, _) => ref.decl.declarations.collect { case field: InstanceField[Post] => field } case _ => Seq() } val newFieldPerms = fields.map(member => { diff --git a/src/rewrite/vct/rewrite/EncodeForkJoin.scala b/src/rewrite/vct/rewrite/EncodeForkJoin.scala index 8601c16a2e..6b17994665 100644 --- a/src/rewrite/vct/rewrite/EncodeForkJoin.scala +++ b/src/rewrite/vct/rewrite/EncodeForkJoin.scala @@ -87,7 +87,7 @@ case class EncodeForkJoin[Pre <: Generation]() extends Rewriter[Pre] { case run: RunMethod[Pre] => run } match { case Some(_) => - val obj = new Variable[Post](TClass(succ(cls))) + val obj = new Variable[Post](TClass(succ(cls), Seq())) ScopedExpr(Seq(obj), With(Block(Seq( assignLocal(obj.get, NewObject(succ(cls))), Inhale(InstancePredicateApply[Post](obj.get, idleToken.ref(cls), Nil, WritePerm())) diff --git a/src/rewrite/vct/rewrite/EncodeIntrinsicLock.scala b/src/rewrite/vct/rewrite/EncodeIntrinsicLock.scala index 9cfe7a37ce..04a587f139 100644 --- a/src/rewrite/vct/rewrite/EncodeIntrinsicLock.scala +++ b/src/rewrite/vct/rewrite/EncodeIntrinsicLock.scala @@ -70,7 +70,7 @@ case class EncodeIntrinsicLock[Pre <: Generation]() extends Rewriter[Pre] { val needsCommitted: mutable.Set[Class[Pre]] = mutable.Set() def getClass(obj: Expr[Pre]): Class[Pre] = obj.t match { - case TClass(Ref(cls)) => cls + case TClass(Ref(cls), Seq()) => cls case _ => throw UnreachableAfterTypeCheck("This argument is not a class type.", obj) } From f4a63d2b0f59d628e92c4ee6312e5cdf37c1d96a Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Thu, 22 Feb 2024 10:27:55 +0100 Subject: [PATCH 06/52] Add generics EVERYWHERE --- src/col/vct/col/util/AstBuildHelpers.scala | 1 + src/main/vct/main/stages/Transformation.scala | 2 +- .../systemctocol/engine/ClassTransformer.java | 9 ++++---- .../engine/KnownTypeTransformer.java | 7 +++--- .../systemctocol/engine/MainTransformer.java | 4 ++-- .../vct/rewrite/EncodeResourceValues.scala | 22 +++++++++++++++---- .../ResolveExpressionSideEffects.scala | 8 +++---- src/rewrite/vct/rewrite/adt/ImportADT.scala | 2 +- src/rewrite/vct/rewrite/bip/EncodeBip.scala | 4 ++-- .../vct/rewrite/exc/EncodeBreakReturn.scala | 6 ++--- .../vct/rewrite/lang/LangCPPToCol.scala | 4 ++-- src/rewrite/vct/rewrite/lang/LangCToCol.scala | 6 ++--- .../vct/rewrite/lang/LangJavaToCol.scala | 8 +++---- .../vct/rewrite/lang/LangPVLToCol.scala | 3 --- .../vct/rewrite/lang/LangTypesToCol.scala | 2 +- src/rewrite/vct/rewrite/util/Extract.scala | 9 +++++++- .../vct/rewrite/veymont/EncodeSeqProg.scala | 6 ++--- .../veymont/GenerateSeqProgPermissions.scala | 8 +++---- .../veymont/ParalleliseVeyMontThreads.scala | 8 +++---- .../vct/helper/SimpleProgramGenerator.scala | 2 +- 20 files changed, 71 insertions(+), 50 deletions(-) diff --git a/src/col/vct/col/util/AstBuildHelpers.scala b/src/col/vct/col/util/AstBuildHelpers.scala index e4bdc079c6..3c59165e75 100644 --- a/src/col/vct/col/util/AstBuildHelpers.scala +++ b/src/col/vct/col/util/AstBuildHelpers.scala @@ -5,6 +5,7 @@ import vct.col.ast._ import vct.col.ast.expr.apply.FunctionInvocationImpl import vct.col.origin._ import vct.col.ref.{DirectRef, Ref} +import vct.col.rewrite.Rewritten import vct.result.VerificationError.UserError /** diff --git a/src/main/vct/main/stages/Transformation.scala b/src/main/vct/main/stages/Transformation.scala index c1d25f77a1..f7e5416ffc 100644 --- a/src/main/vct/main/stages/Transformation.scala +++ b/src/main/vct/main/stages/Transformation.scala @@ -262,7 +262,7 @@ case class SilverTransformation EncodeTryThrowSignals, ResolveScale, - MonomorphizeClass, +// MonomorphizeClass, // No more classes ClassToRef, HeapVariableToRef, diff --git a/src/parsers/vct/parsers/transform/systemctocol/engine/ClassTransformer.java b/src/parsers/vct/parsers/transform/systemctocol/engine/ClassTransformer.java index 624d2abe3f..99a6619b33 100644 --- a/src/parsers/vct/parsers/transform/systemctocol/engine/ClassTransformer.java +++ b/src/parsers/vct/parsers/transform/systemctocol/engine/ClassTransformer.java @@ -15,6 +15,7 @@ import vct.parsers.transform.systemctocol.colmodel.ProcessClass; import vct.parsers.transform.systemctocol.colmodel.StateClass; import vct.parsers.transform.systemctocol.util.OriGen; +import vct.parsers.transform.systemctocol.util.Seqs; /** * Transforms a SystemC class into one or more COL classes. @@ -49,7 +50,7 @@ public Class create_process_class(ProcessClass process) { // Transform class attributes Ref> main_cls_ref = new LazyRef<>(col_system::get_main, Option.empty(), ClassTag$.MODULE$.apply(Class.class)); - InstanceField m = new InstanceField<>(new TClass<>(main_cls_ref, OriGen.create()), col_system.NO_FLAGS, OriGen.create("m")); + InstanceField m = new InstanceField<>(new TClass<>(main_cls_ref, Seqs.empty(), OriGen.create()), col_system.NO_FLAGS, OriGen.create("m")); declarations.add(m); col_system.add_class_main_ref(process, m); java.util.Map> fields = create_fields(process.get_generating_function(), process.get_methods(), @@ -74,7 +75,7 @@ public Class create_process_class(ProcessClass process) { // Add all newly generated methods to the declarations as well declarations.addAll(generated_instance_methods); - return new Class<>(List.from(CollectionConverters.asScala(new java.util.ArrayList(0))), + return new Class<>(Seqs.empty(), List.from(CollectionConverters.asScala(declarations)), col_system.NO_CLS_REFS, col_system.TRUE, OriGen.create(create_name(process.get_generating_instance(), process.get_generating_function()))); } @@ -90,7 +91,7 @@ public Class create_state_class(StateClass state_class) { // Transform class attributes Ref> main_cls_ref = new LazyRef<>(col_system::get_main, Option.empty(), ClassTag$.MODULE$.apply(Class.class)); - InstanceField m = new InstanceField<>(new TClass<>(main_cls_ref, OriGen.create()), col_system.NO_FLAGS, OriGen.create("m")); + InstanceField m = new InstanceField<>(new TClass<>(main_cls_ref, Seqs.empty(), OriGen.create()), col_system.NO_FLAGS, OriGen.create("m")); declarations.add(m); col_system.add_class_main_ref(state_class, m); java.util.Map> fields = create_fields(null, state_class.get_methods(), @@ -125,7 +126,7 @@ public Class create_state_class(StateClass state_class) { // Add newly generated methods to declaration list declarations.addAll(generated_instance_methods); - return new Class<>(List.from(CollectionConverters.asScala(new java.util.ArrayList(0))), + return new Class<>(Seqs.empty(), List.from(CollectionConverters.asScala(declarations)), col_system.NO_CLS_REFS, col_system.TRUE, OriGen.create(create_name(state_class.get_generating_instance()))); } diff --git a/src/parsers/vct/parsers/transform/systemctocol/engine/KnownTypeTransformer.java b/src/parsers/vct/parsers/transform/systemctocol/engine/KnownTypeTransformer.java index 29d05f3024..79b98b7e6f 100644 --- a/src/parsers/vct/parsers/transform/systemctocol/engine/KnownTypeTransformer.java +++ b/src/parsers/vct/parsers/transform/systemctocol/engine/KnownTypeTransformer.java @@ -19,6 +19,7 @@ import vct.parsers.transform.systemctocol.util.Constants; import vct.parsers.transform.systemctocol.util.GeneratedBlame; import vct.parsers.transform.systemctocol.util.OriGen; +import vct.parsers.transform.systemctocol.util.Seqs; /** * Transforms an SCKnownType (i.e. a SystemC-predefined primitive channel instance) into a COL class. We currently @@ -91,7 +92,7 @@ public void transform() { // Add channel field to COL system Ref> ref_to_cls = new DirectRef<>(cls, ClassTag$.MODULE$.apply(Class.class)); - col_system.add_primitive_channel(sc_inst, new InstanceField<>(new TClass<>(ref_to_cls, OriGen.create()), col_system.NO_FLAGS, + col_system.add_primitive_channel(sc_inst, new InstanceField<>(new TClass<>(ref_to_cls, Seqs.empty(), OriGen.create()), col_system.NO_FLAGS, OriGen.create(name.toLowerCase()))); } @@ -118,7 +119,7 @@ private String generate_class_name() { private Class transform_fifo(Origin o, Type t) { // Class fields Ref> main_cls_ref = new LazyRef<>(col_system::get_main, Option.empty(), ClassTag$.MODULE$.apply(Class.class)); - InstanceField m = new InstanceField<>(new TClass<>(main_cls_ref, OriGen.create()), col_system.NO_FLAGS, OriGen.create("m")); + InstanceField m = new InstanceField<>(new TClass<>(main_cls_ref, Seqs.empty(), OriGen.create()), col_system.NO_FLAGS, OriGen.create("m")); InstanceField buf = new InstanceField<>(new TSeq<>(t, OriGen.create()), col_system.NO_FLAGS, OriGen.create("buffer")); InstanceField nr_read = new InstanceField<>(col_system.T_INT, col_system.NO_FLAGS, OriGen.create("num_read")); InstanceField written = new InstanceField<>(new TSeq<>(t, OriGen.create()), col_system.NO_FLAGS, OriGen.create("written")); @@ -524,7 +525,7 @@ private InstanceMethod create_fifo_update_method(InstanceField m, Instance private Class transform_signal(Origin o, Type t) { // Class fields Ref> main_cls_ref = new LazyRef<>(col_system::get_main, Option.empty(), ClassTag$.MODULE$.apply(Class.class)); - InstanceField m = new InstanceField<>(new TClass<>(main_cls_ref, OriGen.create()), col_system.NO_FLAGS, OriGen.create("m")); + InstanceField m = new InstanceField<>(new TClass<>(main_cls_ref, Seqs.empty(), OriGen.create()), col_system.NO_FLAGS, OriGen.create("m")); InstanceField val = new InstanceField<>(t, col_system.NO_FLAGS, OriGen.create("val")); InstanceField _val = new InstanceField<>(t, col_system.NO_FLAGS, OriGen.create("_val")); diff --git a/src/parsers/vct/parsers/transform/systemctocol/engine/MainTransformer.java b/src/parsers/vct/parsers/transform/systemctocol/engine/MainTransformer.java index b401b913a4..fce38e37d1 100644 --- a/src/parsers/vct/parsers/transform/systemctocol/engine/MainTransformer.java +++ b/src/parsers/vct/parsers/transform/systemctocol/engine/MainTransformer.java @@ -189,7 +189,7 @@ private void create_instances() { // Get field type Class transformed_class = col_system.get_col_class_translation(process_class); Ref> ref_to_class = new DirectRef<>(transformed_class, ClassTag$.MODULE$.apply(Class.class)); - Type t = new TClass<>(ref_to_class, OriGen.create()); + Type t = new TClass<>(ref_to_class, Seqs.empty(), OriGen.create()); // Generate instance field InstanceField inst = new InstanceField<>(t, col_system.NO_FLAGS, OriGen.create(create_instance_name(process_class))); @@ -204,7 +204,7 @@ private void create_instances() { // Get field type Class transformed_class = col_system.get_col_class_translation(state_class); Ref> ref_to_class = new DirectRef<>(transformed_class, ClassTag$.MODULE$.apply(Class.class)); - Type t = new TClass<>(ref_to_class, OriGen.create()); + Type t = new TClass<>(ref_to_class, Seqs.empty(), OriGen.create()); // Generate instance field InstanceField inst = new InstanceField<>(t, col_system.NO_FLAGS, OriGen.create(create_instance_name(state_class))); diff --git a/src/rewrite/vct/rewrite/EncodeResourceValues.scala b/src/rewrite/vct/rewrite/EncodeResourceValues.scala index 08d6d399eb..7ae7e4894b 100644 --- a/src/rewrite/vct/rewrite/EncodeResourceValues.scala +++ b/src/rewrite/vct/rewrite/EncodeResourceValues.scala @@ -10,7 +10,7 @@ import vct.col.rewrite.ResolveScale.WrongScale import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder, Rewritten} import vct.col.util.DeclarationBox import vct.result.VerificationError.{SystemError, UserError} -import vct.rewrite.EncodeResourceValues.{UnknownResourceValue, UnsupportedResourceValue, WrongResourcePattern} +import vct.rewrite.EncodeResourceValues.{GenericsNotSupported, UnknownResourceValue, UnsupportedResourceValue, WrongResourcePattern} import vct.col.util.AstBuildHelpers.{ExprBuildHelpers, const, forall} import scala.collection.mutable @@ -25,6 +25,11 @@ case object EncodeResourceValues extends RewriterBuilder { node.o.messageInContext(s"$kind cannot yet be stored in a resource value") } + case class GenericsNotSupported(node: Node[_]) extends UserError { + override def code: String = "genericsNotSupported" + override def text: String = node.o.messageInContext("Generics not supported") + } + case class UnknownResourceValue(node: Expr[_]) extends SystemError { override def text: String = node.o.messageInContext("Unknown resource kind") } @@ -99,6 +104,9 @@ case class EncodeResourceValues[Pre <: Generation]() extends Rewriter[Pre] with val kindFunc: ScopedStack[ADTFunction[Post]] = ScopedStack() val arbitraryResourceValue: ScopedStack[Predicate[Post]] = ScopedStack() + def isGeneric(cls: Class[Pre]): Boolean = cls.typeArgs.isEmpty + def nonGeneric(cls: Class[Pre]): Unit = if (isGeneric(cls)) throw GenericsNotSupported(cls) + override def dispatch(program: Program[Pre]): Program[Post] = { implicit val o: Origin = program.o @@ -140,13 +148,17 @@ case class EncodeResourceValues[Pre <: Generation]() extends Rewriter[Pre] with val decls = patterns.zipWithIndex.flatMap { case (pattern, index) => def freeTypesLoc(location: ResourcePattern.ResourcePatternLoc): Seq[Type[Post]] = location match { case ResourcePattern.HeapVariableLocation(_) => Nil - case ResourcePattern.FieldLocation(f) => Seq(TClass(succ(fieldOwner(f)))) + case ResourcePattern.FieldLocation(f) => + nonGeneric(fieldOwner(f)) + Seq(TClass(succ(fieldOwner(f)), Seq())) case ResourcePattern.ModelLocation(f) => Seq(TModel(succ(modelFieldOwner(f)))) case ResourcePattern.SilverFieldLocation(_) => Seq(TRef()) case ResourcePattern.ArrayLocation(t) => Seq(TArray(dispatch(t)), TInt()) case ResourcePattern.PointerLocation(t) => Seq(TPointer(dispatch(t))) case ResourcePattern.PredicateLocation(ref) => ref.args.map(_.t).map(dispatch) - case ResourcePattern.InstancePredicateLocation(ref) => TClass[Post](succ(predicateOwner(ref))) +: ref.args.map(_.t).map(dispatch) + case ResourcePattern.InstancePredicateLocation(ref) => + nonGeneric(predicateOwner(ref)) + TClass[Post](succ(predicateOwner(ref)), Seq()) +: ref.args.map(_.t).map(dispatch) } def freeTypes(pattern: ResourcePattern): Seq[Type[Post]] = pattern match { @@ -154,7 +166,9 @@ case class EncodeResourceValues[Pre <: Generation]() extends Rewriter[Pre] with case ResourcePattern.Perm(loc) => freeTypesLoc(loc) :+ TRational() case ResourcePattern.Value(loc) => freeTypesLoc(loc) case ResourcePattern.Predicate(p) => p.args.map(_.t).map(dispatch) - case ResourcePattern.InstancePredicate(p) => TClass[Post](succ(predicateOwner(p))) +: p.args.map(_.t).map(dispatch) + case ResourcePattern.InstancePredicate(p) => + nonGeneric(predicateOwner(p)) + TClass[Post](succ(predicateOwner(p)), Seq()) +: p.args.map(_.t).map(dispatch) case ResourcePattern.Star(left, right) => freeTypes(left) ++ freeTypes(right) case ResourcePattern.Implies(res) => freeTypes(res) case ResourcePattern.Select(whenTrue, whenFalse) => freeTypes(whenTrue) ++ freeTypes(whenFalse) diff --git a/src/rewrite/vct/rewrite/ResolveExpressionSideEffects.scala b/src/rewrite/vct/rewrite/ResolveExpressionSideEffects.scala index fd5f0d2e87..1b4fc50ab5 100644 --- a/src/rewrite/vct/rewrite/ResolveExpressionSideEffects.scala +++ b/src/rewrite/vct/rewrite/ResolveExpressionSideEffects.scala @@ -499,7 +499,7 @@ case class ResolveExpressionSideEffects[Pre <: Generation]() extends Rewriter[Pr )(inv.blame)(e.o)) stored(res.get(SideEffectOrigin), method.returnType) case inv @ ConstructorInvocation(Ref(cons), args, outArgs, typeArgs, givenMap, yields) => - val typ = TClass[Post](succ(cons.cls.decl)) + val typ = TClass[Post](succ(cons.cls.decl), typeArgs.map(dispatch)) val res = new Variable[Post](typ)(ResultVar) variables.succeed(res.asInstanceOf[Variable[Pre]], res) effect(InvokeConstructor[Post]( @@ -511,12 +511,12 @@ case class ResolveExpressionSideEffects[Pre <: Generation]() extends Rewriter[Pr givenMap.map { case (Ref(v), e) => (succ(v), inlined(e)) }, yields.map { case (e, Ref(v)) => (inlined(e), succ(v)) }, )(inv.blame)(e.o)) - stored(res.get(SideEffectOrigin), TClass(cons.cls)) + stored(res.get(SideEffectOrigin), TClass(cons.cls, typeArgs)) case NewObject(Ref(cls)) => - val res = new Variable[Post](TClass(succ(cls)))(ResultVar) + val res = new Variable[Post](TClass(succ(cls), Seq()))(ResultVar) variables.succeed(res.asInstanceOf[Variable[Pre]], res) effect(Instantiate[Post](succ(cls), res.get(ResultVar))(e.o)) - stored(res.get(SideEffectOrigin), TClass(cls.ref)) + stored(res.get(SideEffectOrigin), TClass(cls.ref, Seq())) case other => stored(ReInliner().dispatch(rewriteDefault(other)), other.t) } diff --git a/src/rewrite/vct/rewrite/adt/ImportADT.scala b/src/rewrite/vct/rewrite/adt/ImportADT.scala index 933cd21810..8662110fbd 100644 --- a/src/rewrite/vct/rewrite/adt/ImportADT.scala +++ b/src/rewrite/vct/rewrite/adt/ImportADT.scala @@ -60,7 +60,7 @@ case object ImportADT { case TFraction() => "fract" case TZFraction() => "zfract" case TMap(key, value) => "map$" + typeText(key) + "__" + typeText(value) + "$" - case TClass(Ref(cls)) => cls.o.getPreferredNameOrElse().camel + case TClass(Ref(cls), _) => cls.o.getPreferredNameOrElse().camel case TVar(Ref(v)) => v.o.getPreferredNameOrElse().camel case TUnion(ts) => "union" + ts.map(typeText).mkString("$", "__", "$") case SilverPartialTAxiomatic(Ref(adt), _) => adt.o.getPreferredNameOrElse().camel diff --git a/src/rewrite/vct/rewrite/bip/EncodeBip.scala b/src/rewrite/vct/rewrite/bip/EncodeBip.scala index 8a055ea9e2..3851530dd7 100644 --- a/src/rewrite/vct/rewrite/bip/EncodeBip.scala +++ b/src/rewrite/vct/rewrite/bip/EncodeBip.scala @@ -292,7 +292,7 @@ case class EncodeBip[Pre <: Generation](results: VerificationResults) extends Re results.declare(component) implicit val o = DiagnosticOrigin val ref = succ[Class[Post]](classOf(constructor)) - val t = TClass[Post](ref) + val t = TClass[Post](ref, Seq()) rewritingBipConstructorBody.having(component) { constructorSucc(constructor) = globalDeclarations.declare( new Procedure[Post]( @@ -370,7 +370,7 @@ case class EncodeBip[Pre <: Generation](results: VerificationResults) extends Re We define those variables/arguments here. */ val varOf: Map[Declaration[Pre], Local[Post]] = transitions.flatMap { transition => - val v = new Variable[Post](TClass(succ[Class[Post]](classOf(transition))))( + val v = new Variable[Post](TClass(succ[Class[Post]](classOf(transition)), Seq()))( SynchronizationComponentVariableOrigin(synchronization, componentOf(transition))) Seq((transition, v.get), (classOf(transition), v.get)) }.toMap diff --git a/src/rewrite/vct/rewrite/exc/EncodeBreakReturn.scala b/src/rewrite/vct/rewrite/exc/EncodeBreakReturn.scala index 931c763387..99feba88d9 100644 --- a/src/rewrite/vct/rewrite/exc/EncodeBreakReturn.scala +++ b/src/rewrite/vct/rewrite/exc/EncodeBreakReturn.scala @@ -130,7 +130,7 @@ case class EncodeBreakReturn[Pre <: Generation]() extends Rewriter[Pre] { body = Block(Seq(Label(labelDecls.dispatch(decl), Block(Nil)), newBody)), after = Block(Nil), catches = Seq(CatchClause( - decl = new Variable(TClass(breakLabelException.ref(decl))), + decl = new Variable(TClass(breakLabelException.ref(decl), Seq())), body = Block(Nil), )), ) @@ -151,7 +151,7 @@ case class EncodeBreakReturn[Pre <: Generation]() extends Rewriter[Pre] { Throw(NewObject[Post](cls.ref))(PanicBlame("The result of NewObject is never null")) case Return(result) => - val exc = new Variable[Post](TClass(returnClass.get.ref)) + val exc = new Variable[Post](TClass(returnClass.get.ref, Seq())) Scope(Seq(exc), Block(Seq( assignLocal(exc.get, NewObject(returnClass.get.ref)), assignField(exc.get, valueField.get.ref, dispatch(result), PanicBlame("Have write permission immediately after NewObject")), @@ -177,7 +177,7 @@ case class EncodeBreakReturn[Pre <: Generation]() extends Rewriter[Pre] { val returnClass = new Class[Post](Nil, Seq(returnField), Nil, tt)(ReturnClass) globalDeclarations.declare(returnClass) - val caughtReturn = new Variable[Post](TClass(returnClass.ref)) + val caughtReturn = new Variable[Post](TClass(returnClass.ref, Seq())) TryCatchFinally( body = BreakReturnToException(Some(returnClass), Some(returnField)).dispatch(body), diff --git a/src/rewrite/vct/rewrite/lang/LangCPPToCol.scala b/src/rewrite/vct/rewrite/lang/LangCPPToCol.scala index 742453fdb8..34b970590a 100644 --- a/src/rewrite/vct/rewrite/lang/LangCPPToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangCPPToCol.scala @@ -898,7 +898,7 @@ case class LangCPPToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) extends L rw.globalDeclarations.succeed(preEventClass, postEventClass) // Create a variable to refer to the class instance - val eventClassRef = new Variable[Post](TClass(postEventClass.ref))(commandGroup.o.where(name = "sycl_event_ref")) + val eventClassRef = new Variable[Post](TClass(postEventClass.ref, Seq()))(commandGroup.o.where(name = "sycl_event_ref")) // Store the class ref and read-write accessors to be used when the kernel is done running currentlyRunningKernels.put(eventClassRef.get(commandGroup.o), accessors.filter(acc => acc.accessMode.isInstanceOf[SYCLReadWriteAccess[Post]])) @@ -1133,7 +1133,7 @@ case class LangCPPToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) extends L } private def createEventClassConstructor(accessors: Seq[SYCLAccessor[Post]], preClass: Class[Pre], commandGroupO: Origin): Procedure[Post] = { - val t = rw.dispatch(TClass[Pre](preClass.ref)) + val t = rw.dispatch(TClass[Pre](preClass.ref, Seq())) rw.globalDeclarations.declare(withResult((result: Result[Post]) => { val constructorPostConditions: mutable.Buffer[Expr[Post]] = mutable.Buffer.empty val constructorArgs: mutable.Buffer[Variable[Post]] = mutable.Buffer.empty diff --git a/src/rewrite/vct/rewrite/lang/LangCToCol.scala b/src/rewrite/vct/rewrite/lang/LangCToCol.scala index a89aa98992..5d0f516a50 100644 --- a/src/rewrite/vct/rewrite/lang/LangCToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangCToCol.scala @@ -791,7 +791,7 @@ case class LangCToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) extends Laz implicit val o: Origin = init.o val targetClass: Class[Post] = cStructSuccessor(ref.decl) - val t = TClass[Post](targetClass.ref) + val t = TClass[Post](targetClass.ref, Seq()) val v = new Variable[Post](t)(o.sourceName(info.name)) cNameSuccessor(RefCLocalDeclaration(decl, 0)) = v @@ -1028,7 +1028,7 @@ case class LangCToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) extends Laz def createStructCopy(a: Expr[Post], target: CGlobalDeclaration[Pre], blame: InstanceField[_] => Blame[InsufficientPermission]): Expr[Post] = { implicit val o: Origin = a.o val targetClass: Class[Post] = cStructSuccessor(target) - val t = TClass[Post](targetClass.ref) + val t = TClass[Post](targetClass.ref, Seq()) val v = new Variable[Post](t) val fieldAssigns = targetClass.declarations.collect { @@ -1229,6 +1229,6 @@ case class LangCToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) extends Laz def structType(t: CTStruct[Pre]): Type[Post] = { val targetClass = new LazyRef[Post, Class[Post]](cStructSuccessor(t.ref.decl)) - TClass[Post](targetClass)(t.o) + TClass[Post](targetClass, Seq())(t.o) } } diff --git a/src/rewrite/vct/rewrite/lang/LangJavaToCol.scala b/src/rewrite/vct/rewrite/lang/LangJavaToCol.scala index 1bb7ad2e40..aec0ccd95d 100644 --- a/src/rewrite/vct/rewrite/lang/LangJavaToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangJavaToCol.scala @@ -195,7 +195,7 @@ case class LangJavaToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) extends case cons: JavaConstructor[Pre] => logger.debug(s"Constructor for ${cons.o.inlineContextText}") implicit val o: Origin = cons.o - val t = TClass(ref) + val t = TClass(ref, Seq()) val `this` = ThisObject(ref) val results = currentJavaClass.top.modifiers.collect { @@ -296,7 +296,7 @@ case class LangJavaToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) extends currentJavaClass.having(cls) { val supports = cls.supports.map(rw.dispatch).flatMap { - case TClass(ref) => Seq(ref) + case TClass(ref, Seq()) => Seq(ref) case _ => ??? } @@ -331,7 +331,7 @@ case class LangJavaToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) extends }._1, Nil, tt)(JavaStaticsClassOrigin(cls)) rw.globalDeclarations.declare(staticsClass) - val t = TClass[Post](staticsClass.ref) + val t = TClass[Post](staticsClass.ref, Seq()) val singleton = withResult((res: Result[Post]) => function(AbstractApplicable, TrueSatisfiable, returnType = t, ensures = UnitAccountedPredicate((res !== Null()) && (TypeOf(res) === TypeValue(t))))(JavaStaticsClassSingletonOrigin(cls))) @@ -513,6 +513,6 @@ case class LangJavaToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) extends } def classType(t: JavaTClass[Pre]): Type[Post] = t.ref.decl match { - case classOrInterface: JavaClassOrInterface[Pre] => TClass(javaInstanceClassSuccessor.ref(classOrInterface)) + case classOrInterface: JavaClassOrInterface[Pre] => TClass(javaInstanceClassSuccessor.ref(classOrInterface), Seq()) } } diff --git a/src/rewrite/vct/rewrite/lang/LangPVLToCol.scala b/src/rewrite/vct/rewrite/lang/LangPVLToCol.scala index 1a6652f701..f91b7f8049 100644 --- a/src/rewrite/vct/rewrite/lang/LangPVLToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangPVLToCol.scala @@ -35,8 +35,6 @@ case class LangPVLToCol[Pre <: Generation](rw: LangSpecificToCol[Pre], veymontGe def rewriteConstructor(cons: PVLConstructor[Pre]): Unit = { implicit val o: Origin = cons.o - val t = TClass[Post](rw.succ(rw.currentClass.top)) - val resVar = new Variable(t) pvlConstructor(cons) = rw.currentThis.having(ThisObject(rw.succ(rw.currentClass.top))) { rw.classDeclarations.declare(new Constructor[Post]( @@ -53,7 +51,6 @@ case class LangPVLToCol[Pre <: Generation](rw: LangSpecificToCol[Pre], veymontGe def maybeDeclareDefaultConstructor(cls: Class[Pre]): Unit = { if (cls.decls.collectFirst { case _: PVLConstructor[Pre] => () }.isEmpty) { implicit val o: Origin = cls.o - val t = TClass[Post](rw.succ(cls)) val `this` = ThisObject(rw.succ[Class[Post]](cls)) val defaultBlame = PanicBlame("The postcondition of a default constructor cannot fail.") diff --git a/src/rewrite/vct/rewrite/lang/LangTypesToCol.scala b/src/rewrite/vct/rewrite/lang/LangTypesToCol.scala index e3a3e4607d..4ab0b2f7cd 100644 --- a/src/rewrite/vct/rewrite/lang/LangTypesToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangTypesToCol.scala @@ -65,7 +65,7 @@ case class LangTypesToCol[Pre <: Generation]() extends Rewriter[Pre] { case t @ PVLNamedType(_, typeArgs) => t.ref.get match { case spec: SpecTypeNameTarget[Pre] => specType(spec, typeArgs) - case RefClass(decl) => TClass(succ(decl)) + case RefClass(decl) => TClass(succ(decl), typeArgs.map(dispatch)) } case t @ CPrimitiveType(specs) => dispatch(C.getPrimitiveType(specs, context = Some(t))) diff --git a/src/rewrite/vct/rewrite/util/Extract.scala b/src/rewrite/vct/rewrite/util/Extract.scala index 4317855045..6d35c0f661 100644 --- a/src/rewrite/vct/rewrite/util/Extract.scala +++ b/src/rewrite/vct/rewrite/util/Extract.scala @@ -5,6 +5,7 @@ import vct.col.origin._ import vct.col.rewrite.util.FreeVariables.{FreeThisModel, FreeThisObject, ReadFreeVar, ReadTypeVar, WriteFreeVar} import vct.col.util.AstBuildHelpers.{VarBuildHelpers, assignLocal} import vct.col.util.Substitute +import vct.result.VerificationError.UserError import scala.collection.immutable.ListMap import scala.collection.mutable @@ -32,6 +33,11 @@ case object Extract { require(out.isEmpty) (result, in) } + + case class GenericsNotSupported(n: Node[_]) extends UserError { + override def code: String = "genericsNotSupported" + override def text: String = n.o.messageInContext("Generics not supported") + } } case class Extract[G]() { @@ -57,7 +63,8 @@ case class Extract[G]() { write += v v -> Local(getOrElseUpdate(ReadFreeVar(v), new Variable(extract(v.t))(v.ref.decl.o)).ref[Variable[G]])(ExtractOrigin("")) case free @ FreeThisObject(t) => - t -> Local(getOrElseUpdate(free, new Variable(extract(TClass(t.cls)))(ExtractOrigin("this"))).ref[Variable[G]])(ExtractOrigin("")) + if (t.cls.decl.typeArgs.nonEmpty) throw GenericsNotSupported(t.cls.decl) + t -> Local(getOrElseUpdate(free, new Variable(extract(TClass(t.cls, Seq())))(ExtractOrigin("this"))).ref[Variable[G]])(ExtractOrigin("")) case free @ FreeThisModel(t) => t -> Local(getOrElseUpdate(free, new Variable(extract(TModel(t.cls)))(ExtractOrigin("this"))).ref[Variable[G]])(ExtractOrigin("")) }.to(ListMap) diff --git a/src/rewrite/vct/rewrite/veymont/EncodeSeqProg.scala b/src/rewrite/vct/rewrite/veymont/EncodeSeqProg.scala index ac374deaa4..0867b2a4c4 100644 --- a/src/rewrite/vct/rewrite/veymont/EncodeSeqProg.scala +++ b/src/rewrite/vct/rewrite/veymont/EncodeSeqProg.scala @@ -102,7 +102,7 @@ case class EncodeSeqProg[Pre <: Generation]() extends Rewriter[Pre] with LazyLog implicit val o = prog.o prog.endpoints.foreach(_.drop()) for (endpoint <- prog.endpoints) { - endpointSucc((mode, endpoint)) = new Variable(TClass(succ[Class[Post]](endpoint.cls.decl)))(endpoint.o) + endpointSucc((mode, endpoint)) = new Variable(TClass(succ[Class[Post]](endpoint.cls.decl), Seq()))(endpoint.o) } // Maintain successor for seq_prog argument variables manually, as two contexts are maintained @@ -147,7 +147,7 @@ case class EncodeSeqProg[Pre <: Generation]() extends Rewriter[Pre] with LazyLog case (InProg(prog), method: InstanceMethod[Pre]) => currentInstanceMethod.having(method) { for (endpoint <- prog.endpoints) { - endpointSucc((mode, endpoint)) = new Variable(TClass(succ[Class[Post]](endpoint.cls.decl)))(endpoint.o) + endpointSucc((mode, endpoint)) = new Variable(TClass(succ[Class[Post]](endpoint.cls.decl), Seq()))(endpoint.o) } prog.args.foreach(_.drop()) @@ -172,7 +172,7 @@ case class EncodeSeqProg[Pre <: Generation]() extends Rewriter[Pre] with LazyLog currentRun.having(run) { for (endpoint <- prog.endpoints) { - endpointSucc((mode, endpoint)) = new Variable(TClass(succ[Class[Post]](endpoint.cls.decl)))(endpoint.o) + endpointSucc((mode, endpoint)) = new Variable(TClass(succ[Class[Post]](endpoint.cls.decl), Seq()))(endpoint.o) } for (arg <- prog.args) { diff --git a/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala b/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala index 9bab1532e8..02cf1d2916 100644 --- a/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala +++ b/src/rewrite/vct/rewrite/veymont/GenerateSeqProgPermissions.scala @@ -152,7 +152,7 @@ case class GenerateSeqProgPermissions[Pre <: Generation](enabled: Boolean = fals } def endpointPerm(endpoint: Endpoint[Pre])(implicit o: Origin): Expr[Post] = - transitivePerm(EndpointUse[Post](succ(endpoint)), TClass(endpoint.cls)) + transitivePerm(EndpointUse[Post](succ(endpoint)), TClass(endpoint.cls, Seq())) def endpointsPerm(endpoints: Seq[Endpoint[Pre]])(implicit o: Origin): Expr[Post] = foldStar(endpoints.map(endpointPerm)) @@ -167,7 +167,7 @@ case class GenerateSeqProgPermissions[Pre <: Generation](enabled: Boolean = fals transitivePerm(Result[Post](anySucc(app)), app.returnType) def classPerm(cls: Class[Pre]): Expr[Post] = - transitivePerm(ThisObject[Post](succ(cls))(cls.o), TClass(cls.ref))(cls.o) + transitivePerm(ThisObject[Post](succ(cls))(cls.o), TClass(cls.ref, Seq()))(cls.o) /* @@ -198,11 +198,11 @@ case class GenerateSeqProgPermissions[Pre <: Generation](enabled: Boolean = fals (Perm(ArrayLocation(e, i)(PanicBlame("Encoding guarantees well-formedness")), WritePerm()) &* transitivePerm(ArraySubscript(e, i)(PanicBlame("Encoding guarantees well-formedness")), u)) ) - case TClass(Ref(cls)) if !generatingClasses.contains(cls) => + case TClass(Ref(cls), _) if !generatingClasses.contains(cls) => generatingClasses.having(cls) { foldStar(cls.collect { case f: InstanceField[Pre] => fieldTransitivePerm(e, f)(f.o) }) } - case TClass(Ref(cls)) => + case TClass(Ref(cls), _) => // The class we are generating permission for has already been encountered when going through the chain // of fields. So we cut off the computation logger.warn(s"Not generating permissions for recursive occurrence of ${cls.o.debugName()}. Circular datastructures are not supported by permission generation") diff --git a/src/rewrite/vct/rewrite/veymont/ParalleliseVeyMontThreads.scala b/src/rewrite/vct/rewrite/veymont/ParalleliseVeyMontThreads.scala index 893add1c48..d48cf71cba 100644 --- a/src/rewrite/vct/rewrite/veymont/ParalleliseVeyMontThreads.scala +++ b/src/rewrite/vct/rewrite/veymont/ParalleliseVeyMontThreads.scala @@ -75,7 +75,7 @@ case class ParalleliseEndpoints[Pre <: Generation](channelClass: JavaClass[_]) e globalDeclarations.declare(c) } seqProg.endpoints.foreach(thread => { - val threadField = new InstanceField[Post](TClass(givenClassSucc.ref(thread.t)), Nil)(thread.o) + val threadField = new InstanceField[Post](TClass(givenClassSucc.ref(thread.t), Seq()), Nil)(thread.o) val channelFields = getChannelFields(thread, indexedChannelInfo, channelClasses) threadBuildingBlocks.having(new ThreadBuildingBlocks(seqProg.run, seqProg.decls, channelFields, channelClasses, thread, threadField)) { dispatch(thread) @@ -87,10 +87,10 @@ case class ParalleliseEndpoints[Pre <: Generation](channelClass: JavaClass[_]) e val rw = GivenClassRewriter() val gc = c.rewrite( decls = classDeclarations.collect { - (givenClassConstrSucc.get(TClass(c.ref)).get +: c.declarations).foreach(d => rw.dispatch(d)) + (givenClassConstrSucc.get(TClass(c.ref, Seq())).get +: c.declarations).foreach(d => rw.dispatch(d)) }._1 )(rw) - givenClassSucc.update(TClass(c.ref),gc) + givenClassSucc.update(TClass(c.ref, Seq()),gc) gc } @@ -134,7 +134,7 @@ case class ParalleliseEndpoints[Pre <: Generation](channelClass: JavaClass[_]) e JavaLocal[Post](getVarName(l.ref.decl).camel)(null)(e.o) else rewriteDefault(l) case t: ThisObject[Pre] => - val thisClassType = TClass(t.cls) + val thisClassType = TClass(t.cls, Seq()) if(rewritingConstr.nonEmpty && rewritingConstr.top._2 == thisClassType) ThisObject(givenClassSucc.ref[Post,Class[Post]](thisClassType))(t.o) else rewriteDefault(t) diff --git a/test/main/vct/helper/SimpleProgramGenerator.scala b/test/main/vct/helper/SimpleProgramGenerator.scala index 1ed8e8a9b7..f0587c3abd 100644 --- a/test/main/vct/helper/SimpleProgramGenerator.scala +++ b/test/main/vct/helper/SimpleProgramGenerator.scala @@ -20,7 +20,7 @@ object SimpleProgramGenerator { val contract1 = generateSimpleApplicableContract[G]() val blame1 = origin val method1 = new InstanceMethod(TVoid(), Nil, Nil, Nil, Option(body), contract1)(blame1) - val classNode1 = new Class(Seq(method1), Nil, tt) + val classNode1 = new Class(Nil, Seq(method1), Nil, tt) Program(Seq(classNode1))(DiagnosticOrigin) } From 8f9189de41adf9d5a1f01ae1a8db3d4309634c62 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Thu, 22 Feb 2024 14:16:55 +0100 Subject: [PATCH 07/52] Make generics work for methodinvocation --- examples/concepts/generics/box.pvl | 5 + .../col/ast/expr/apply/InvocationImpl.scala | 2 +- .../ast/expr/apply/MethodInvocationImpl.scala | 14 ++- .../col/ast/lang/pvl/PVLInvocationImpl.scala | 16 +++- .../vct/col/ast/type/typeclass/TypeImpl.scala | 2 +- src/main/vct/main/stages/Resolution.scala | 3 +- src/main/vct/main/stages/Transformation.scala | 2 +- .../vct/rewrite/MonomorphizeClass.scala | 95 ++++++++++++------- 8 files changed, 98 insertions(+), 41 deletions(-) diff --git a/examples/concepts/generics/box.pvl b/examples/concepts/generics/box.pvl index 8c2831fdfc..efbe017b65 100644 --- a/examples/concepts/generics/box.pvl +++ b/examples/concepts/generics/box.pvl @@ -5,4 +5,9 @@ class Box { T get() { return t; } +} + +void m() { + Box b = new Box(); + int t = b.get(); } \ No newline at end of file diff --git a/src/col/vct/col/ast/expr/apply/InvocationImpl.scala b/src/col/vct/col/ast/expr/apply/InvocationImpl.scala index 10cb20d571..bc552ab5f3 100644 --- a/src/col/vct/col/ast/expr/apply/InvocationImpl.scala +++ b/src/col/vct/col/ast/expr/apply/InvocationImpl.scala @@ -3,5 +3,5 @@ package vct.col.ast.expr.apply import vct.col.ast.{Invocation, Type} trait InvocationImpl[G] extends ApplyImpl[G] { this: Invocation[G] => - override lazy val t: Type[G] = super.t.particularize(ref.decl.typeArgs.zip(typeArgs).toMap) + override def t: Type[G] = super.t.particularize(ref.decl.typeArgs.zip(typeArgs).toMap) } \ No newline at end of file diff --git a/src/col/vct/col/ast/expr/apply/MethodInvocationImpl.scala b/src/col/vct/col/ast/expr/apply/MethodInvocationImpl.scala index a8560d85f9..981120447e 100644 --- a/src/col/vct/col/ast/expr/apply/MethodInvocationImpl.scala +++ b/src/col/vct/col/ast/expr/apply/MethodInvocationImpl.scala @@ -1,10 +1,11 @@ package vct.col.ast.expr.apply -import vct.col.ast.MethodInvocation +import vct.col.ast.{MethodInvocation, TClass, Type} import vct.col.print.{Ctx, Doc, DocUtil, Empty, Group, Precedence, Text} import vct.col.ast.ops.MethodInvocationOps +import vct.col.ref.Ref -trait MethodInvocationImpl[G] extends MethodInvocationOps[G] { this: MethodInvocation[G] => +trait MethodInvocationImpl[G] extends MethodInvocationOps[G] with InvocationImpl[G] { this: MethodInvocation[G] => override def precedence: Int = Precedence.POSTFIX override def layout(implicit ctx: Ctx): Doc = @@ -15,4 +16,13 @@ trait MethodInvocationImpl[G] extends MethodInvocationOps[G] { this: MethodInvoc "(" ) <> Doc.args(args ++ outArgs) <> ")" <> DocUtil.givenYields(givenMap, yields) ) + + override def t: Type[G] = { + val returnType = super.t + obj.t match { + case TClass(Ref(cls), typeArgs) if typeArgs.nonEmpty => + returnType.particularize(cls.typeArgs.zip(typeArgs).toMap) + case _ => returnType + } + } } diff --git a/src/col/vct/col/ast/lang/pvl/PVLInvocationImpl.scala b/src/col/vct/col/ast/lang/pvl/PVLInvocationImpl.scala index 83f0488802..44b251ac93 100644 --- a/src/col/vct/col/ast/lang/pvl/PVLInvocationImpl.scala +++ b/src/col/vct/col/ast/lang/pvl/PVLInvocationImpl.scala @@ -1,9 +1,10 @@ package vct.col.ast.lang.pvl -import vct.col.ast.{PVLInvocation, TProcess, TResource, Type} -import vct.col.print.{Ctx, Doc, DocUtil, Empty, Text, Group} +import vct.col.ast.{PVLInvocation, TClass, TProcess, TResource, Type} +import vct.col.print.{Ctx, Doc, DocUtil, Empty, Group, Text} import vct.col.resolve.ctx._ import vct.col.ast.ops.PVLInvocationOps +import vct.col.ref.Ref trait PVLInvocationImpl[G] extends PVLInvocationOps[G] { this: PVLInvocation[G] => override lazy val t: Type[G] = ref.get match { @@ -11,7 +12,16 @@ trait PVLInvocationImpl[G] extends PVLInvocationOps[G] { this: PVLInvocation[G] case RefProcedure(decl) => decl.returnType case RefPredicate(_) => TResource() case RefInstanceFunction(decl) => decl.returnType.particularize(decl.typeArgs.zip(typeArgs).toMap) - case RefInstanceMethod(decl) => decl.returnType + case RefInstanceMethod(decl) => + val returnType = decl.returnType.particularize(decl.typeArgs.zip(typeArgs).toMap) + obj match { + case None => returnType + case Some(e) => e.t match { + case TClass(Ref(cls), typeValues) if typeValues.nonEmpty => + returnType.particularize(cls.typeArgs.zip(typeValues).toMap) + case _ => returnType + } + } case RefInstancePredicate(_) => TResource() case RefADTFunction(decl) => decl.returnType case RefModelProcess(_) => TProcess() diff --git a/src/col/vct/col/ast/type/typeclass/TypeImpl.scala b/src/col/vct/col/ast/type/typeclass/TypeImpl.scala index 2ef0e5e572..e958a24877 100644 --- a/src/col/vct/col/ast/type/typeclass/TypeImpl.scala +++ b/src/col/vct/col/ast/type/typeclass/TypeImpl.scala @@ -44,7 +44,7 @@ trait TypeImpl[G] extends TypeFamilyOps[G] { this: Type[G] => override def succProvider: SuccessorsProvider[G, G] = IdentitySuccessorsProvider override def dispatch(t: Type[G]): Type[G] = t match { - case TVar(Ref(v)) => substitutions(v) + case t @ TVar(Ref(v)) => substitutions.get(v).getOrElse(t) case other => rewriteDefault(other) } } diff --git a/src/main/vct/main/stages/Resolution.scala b/src/main/vct/main/stages/Resolution.scala index 3d1ef5433d..bf57a28f0f 100644 --- a/src/main/vct/main/stages/Resolution.scala +++ b/src/main/vct/main/stages/Resolution.scala @@ -117,7 +117,8 @@ case class Resolution[G <: Generation] val typedProgram = LangTypesToCol().dispatch(joinedProgram) ResolveReferences.resolve(typedProgram, MyLocalJavaParser(blameProvider), MyLocalLLVMSpecParser(blameProvider)) match { case Nil => // ok - case some => throw InputResolutionError(some) + case some => + throw InputResolutionError(some) } val resolvedProgram = LangSpecificToCol(veymontGeneratePermissions, veymontAllowAssign).dispatch(typedProgram) resolvedProgram.check match { diff --git a/src/main/vct/main/stages/Transformation.scala b/src/main/vct/main/stages/Transformation.scala index f7e5416ffc..c1d25f77a1 100644 --- a/src/main/vct/main/stages/Transformation.scala +++ b/src/main/vct/main/stages/Transformation.scala @@ -262,7 +262,7 @@ case class SilverTransformation EncodeTryThrowSignals, ResolveScale, -// MonomorphizeClass, + MonomorphizeClass, // No more classes ClassToRef, HeapVariableToRef, diff --git a/src/rewrite/vct/rewrite/MonomorphizeClass.scala b/src/rewrite/vct/rewrite/MonomorphizeClass.scala index 1f1c53bc9c..f0b8d99b8b 100644 --- a/src/rewrite/vct/rewrite/MonomorphizeClass.scala +++ b/src/rewrite/vct/rewrite/MonomorphizeClass.scala @@ -5,26 +5,44 @@ import vct.col.ast._ import vct.col.ref.{LazyRef, Ref} import vct.col.rewrite.{Generation, Rewriter, RewriterBuilder, Rewritten} import vct.col.util.AstBuildHelpers.ContractApplicableBuildHelpers +import vct.col.util.{Substitute, SuccessionMap} import scala.collection.mutable case object MonomorphizeClass extends RewriterBuilder { - override def key: String = "monomorphizeClasses" + override def key: String = "monomorphizeClass" override def desc: String = "Monomorphize generic classes" } case class MonomorphizeClass[Pre <: Generation]() extends Rewriter[Pre] { - val currentSubstitutions: ScopedStack[Map[Variable[Pre], Type[Post]]] = ScopedStack() + val currentSubstitutions: ScopedStack[Map[Variable[Pre], Type[Pre]]] = ScopedStack() - val monomorphizedRef: mutable.Map[(Class[Pre], Seq[Type[Post]]), Ref[Post, Class[Post]]] = mutable.Map() - val monomorphizedImpl: mutable.Map[(Class[Pre], Seq[Type[Post]]), Class[Post]] = mutable.Map() + type Key = (Class[Pre], Seq[Type[Pre]]) + case class InstantiationContext(cls: Class[Pre], + typeValues: Seq[Type[Pre]], + removeBodies: Boolean, + substitutions: Map[TVar[Pre], Type[Pre]] + ) { + def key = (cls, typeValues) + def substitute = Substitute(Map.empty[Expr[Pre], Expr[Pre]], typeSubs = substitutions) + } + val ctx: ScopedStack[InstantiationContext] = ScopedStack() + + // key: generically instantiated type in pre + val genericSucc: SuccessionMap[(Key, Declaration[Pre]), Declaration[Post]] = SuccessionMap() - def getOrBuild(cls: Class[Pre], typeValues: Seq[Type[Post]]): Ref[Post, Class[Post]] = - monomorphizedRef.get((cls, typeValues)) match { - case Some(ref) => ref + def instantiate(cls: Class[Pre], typeValues: Seq[Type[Pre]]): Unit = { + val key = (cls, typeValues) + genericSucc.get((key, cls)) match { + case Some(ref) => // Done case None => - monomorphizedRef((cls, typeValues)) = new LazyRef(monomorphizedImpl((cls, typeValues))) - monomorphizedImpl((cls, typeValues)) = currentSubstitutions.having(cls.typeArgs.zip(typeValues).toMap) { + val newCtx = InstantiationContext( + cls, + typeValues, + removeBodies = false, + substitutions = cls.typeArgs.map { v: Variable[Pre] => TVar(v.ref[Variable[Pre]]) }.zip(typeValues).toMap + ) + genericSucc((key, cls)) = ctx.having(newCtx) { globalDeclarations.scope { classDeclarations.scope { variables.scope { @@ -33,37 +51,50 @@ case class MonomorphizeClass[Pre <: Generation]() extends Rewriter[Pre] { } } } - monomorphizedRef((cls, typeValues)) } + } override def dispatch(decl: Declaration[Pre]): Unit = decl match { - case cls: Class[Pre] if cls.typeArgs.nonEmpty => ??? -// case app: ContractApplicable[Pre] if app.typeArgs.nonEmpty => -// app.typeArgs.foreach(_.drop()) -// getOrBuild(app, app.typeArgs.map(v => dispatch(v.t.asInstanceOf[TType[Pre]].t))) - case other => other.rewriteDefault() + case cls: Class[Pre] if cls.typeArgs.nonEmpty => + cls.typeArgs.foreach(_.drop()) + instantiate(cls, cls.typeArgs.map(v => v.t.asInstanceOf[TType[Pre]].t)) + case m: InstanceMethod[Pre] if ctx.nonEmpty => + val `m'` = classDeclarations.collect(super.dispatch(m))._1.head + genericSucc((ctx.top.key, m)) = `m'` + case other => + allScopes.anySucceed(other, other.rewriteDefault()) } - /* - override def dispatch(e: Expr[Pre]): Expr[Rewritten[Pre]] = e match { - case inv: Invocation[Pre] if inv.ref.decl.typeArgs.nonEmpty => - val typeValues = inv.typeArgs.map(dispatch) - val ref = getOrBuild(inv.ref.decl, typeValues) - - inv match { - case inv: ProcedureInvocation[Pre] => inv.rewrite(ref = ref.asInstanceOf[Ref[Post, Procedure[Post]]], typeArgs = Nil) - case inv: MethodInvocation[Pre] => inv.rewrite(ref = ref.asInstanceOf[Ref[Post, InstanceMethod[Post]]], typeArgs = Nil) - case inv: FunctionInvocation[Pre] => inv.rewrite(ref = ref.asInstanceOf[Ref[Post, Function[Post]]], typeArgs = Nil) - case inv: InstanceFunctionInvocation[Pre] => inv.rewrite(ref = ref.asInstanceOf[Ref[Post, InstanceFunction[Post]]], typeArgs = Nil) - } - case other => rewriteDefault(other) + override def dispatch(t: Type[Pre]): Type[Post] = t match { + case TClass(Ref(cls), typeArgs) if typeArgs.nonEmpty => + val typeValues = typeArgs.map(ctx.top.substitute.dispatch) + instantiate(cls, typeValues) + TClass[Post](genericSucc.ref(((cls, typeValues), cls)).asInstanceOf, Seq()) + case TVar(Ref(v)) => + ??? // Don't think this should occur anymore? +// currentSubstitutions.top(v) + case _ => t.rewriteDefault() } - */ - override def dispatch(t: Type[Pre]): Type[Rewritten[Pre]] = t match { - case TVar(Ref(v)) => - currentSubstitutions.top(v) + def evalType(t: Type[Pre]): Type[Pre] = ctx.top.substitute.dispatch(t) + + def evalTypes(ts: Seq[Type[Pre]]): Seq[Type[Pre]] = ts.map(evalType) + + override def dispatch(e: Expr[Pre]): Expr[Rewritten[Pre]] = e match { + case inv: MethodInvocation[Pre] => + inv.obj.t match { + case TClass(Ref(cls), typeArgs) if typeArgs.nonEmpty => + val typeValues = evalTypes(typeArgs) + instantiate(cls, typeValues) + inv.rewrite( + ref = genericSucc.ref(((cls, typeValues), inv.ref.decl)).asInstanceOf + ) + case _ => inv.rewriteDefault() + } + case inv: ConstructorInvocation[Pre] => ??? + case inv: InstanceFunctionInvocation[Pre] => ??? + case inv: InstancePredicateApply[Pre] => ??? case other => other.rewriteDefault() } } From c350328760db329b44515f045358805f207baaa7 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Thu, 22 Feb 2024 17:08:57 +0100 Subject: [PATCH 08/52] There's a pass! --- src/col/vct/col/ast/Node.scala | 9 ++- .../ast/declaration/global/ClassImpl.scala | 3 +- src/col/vct/col/ast/type/TClassImpl.scala | 7 ++- .../unsorted/ConstructorInvocationImpl.scala | 4 +- src/col/vct/col/resolve/Resolve.scala | 7 +-- src/col/vct/col/resolve/lang/PVL.scala | 8 +-- src/col/vct/col/resolve/lang/Util.scala | 15 ++++- .../vct/col/typerules/CoercingRewriter.scala | 10 ++-- src/col/vct/col/util/AstBuildHelpers.scala | 3 +- src/main/vct/main/stages/Transformation.scala | 15 +++-- src/parsers/antlr4/LangPVLParser.g4 | 2 +- .../vct/parsers/transform/PVLToCol.scala | 7 ++- .../engine/FunctionTransformer.java | 3 +- .../engine/KnownTypeTransformer.java | 4 +- .../systemctocol/engine/MainTransformer.java | 4 +- src/rewrite/vct/rewrite/ClassToRef.scala | 2 +- .../vct/rewrite/MonomorphizeClass.scala | 57 ++++++++++++------- .../ResolveExpressionSideEffects.scala | 11 ++-- .../vct/rewrite/lang/LangJavaToCol.scala | 4 +- .../vct/rewrite/lang/LangPVLToCol.scala | 12 +++- 20 files changed, 114 insertions(+), 73 deletions(-) diff --git a/src/col/vct/col/ast/Node.scala b/src/col/vct/col/ast/Node.scala index ee2d4f7f77..637cb5d81f 100644 --- a/src/col/vct/col/ast/Node.scala +++ b/src/col/vct/col/ast/Node.scala @@ -220,7 +220,7 @@ sealed trait ExceptionalStatement[G] extends Statement[G] with ExceptionalStatem final case class Eval[G](expr: Expr[G])(implicit val o: Origin) extends ExceptionalStatement[G] with EvalImpl[G] sealed trait InvocationStatement[G] extends ExceptionalStatement[G] with InvokingNode[G] with InvocationStatementImpl[G] final case class InvokeProcedure[G](ref: Ref[G, Procedure[G]], args: Seq[Expr[G]], outArgs: Seq[Expr[G]], typeArgs: Seq[Type[G]], givenMap: Seq[(Ref[G, Variable[G]], Expr[G])], yields: Seq[(Expr[G], Ref[G, Variable[G]])])(val blame: Blame[InvocationFailure])(implicit val o: Origin) extends InvocationStatement[G] with InvokeProcedureImpl[G] -final case class InvokeConstructor[G](ref: Ref[G, Constructor[G]], out: Expr[G], args: Seq[Expr[G]], outArgs: Seq[Expr[G]], typeArgs: Seq[Type[G]], givenMap: Seq[(Ref[G, Variable[G]], Expr[G])], yields: Seq[(Expr[G], Ref[G, Variable[G]])])(val blame: Blame[InvocationFailure])(implicit val o: Origin) extends InvocationStatement[G] with InvokeConstructorImpl[G] +final case class InvokeConstructor[G](ref: Ref[G, Constructor[G]], classTypeArgs: Seq[Type[G]], out: Expr[G], args: Seq[Expr[G]], outArgs: Seq[Expr[G]], typeArgs: Seq[Type[G]], givenMap: Seq[(Ref[G, Variable[G]], Expr[G])], yields: Seq[(Expr[G], Ref[G, Variable[G]])])(val blame: Blame[InvocationFailure])(implicit val o: Origin) extends InvocationStatement[G] with InvokeConstructorImpl[G] final case class InvokeMethod[G](obj: Expr[G], ref: Ref[G, InstanceMethod[G]], args: Seq[Expr[G]], outArgs: Seq[Expr[G]], typeArgs: Seq[Type[G]], givenMap: Seq[(Ref[G, Variable[G]], Expr[G])], yields: Seq[(Expr[G], Ref[G, Variable[G]])])(val blame: Blame[InstanceInvocationFailure])(implicit val o: Origin) extends InvocationStatement[G] with InstanceApply[G] with InvokeMethodImpl[G] final case class Return[G](result: Expr[G])(implicit val o: Origin) extends ExceptionalStatement[G] with ReturnImpl[G] final case class Throw[G](obj: Expr[G])(val blame: Blame[ThrowNull])(implicit val o: Origin) extends ExceptionalStatement[G] with ThrowImpl[G] @@ -532,7 +532,7 @@ sealed trait Invocation[G] extends ApplyInlineable[G] with InvokingNode[G] with sealed trait AnyMethodInvocation[G] extends Invocation[G] with AnyMethodInvocationImpl[G] final case class ProcedureInvocation[G](ref: Ref[G, Procedure[G]], args: Seq[Expr[G]], outArgs: Seq[Expr[G]], typeArgs: Seq[Type[G]], givenMap: Seq[(Ref[G, Variable[G]], Expr[G])], yields: Seq[(Expr[G], Ref[G, Variable[G]])])(val blame: Blame[InvocationFailure])(implicit val o: Origin) extends AnyMethodInvocation[G] with ProcedureInvocationImpl[G] final case class MethodInvocation[G](obj: Expr[G], ref: Ref[G, InstanceMethod[G]], args: Seq[Expr[G]], outArgs: Seq[Expr[G]], typeArgs: Seq[Type[G]], givenMap: Seq[(Ref[G, Variable[G]], Expr[G])], yields: Seq[(Expr[G], Ref[G, Variable[G]])])(val blame: Blame[InstanceInvocationFailure])(implicit val o: Origin) extends AnyMethodInvocation[G] with InstanceApply[G] with MethodInvocationImpl[G] -final case class ConstructorInvocation[G](ref: Ref[G, Constructor[G]], args: Seq[Expr[G]], outArgs: Seq[Expr[G]], typeArgs: Seq[Type[G]], givenMap: Seq[(Ref[G, Variable[G]], Expr[G])], yields: Seq[(Expr[G], Ref[G, Variable[G]])])(val blame: Blame[InvocationFailure])(implicit val o: Origin) extends AnyMethodInvocation[G] with ConstructorInvocationImpl[G] +final case class ConstructorInvocation[G](ref: Ref[G, Constructor[G]], classTypeArgs: Seq[Type[G]], args: Seq[Expr[G]], outArgs: Seq[Expr[G]], typeArgs: Seq[Type[G]], givenMap: Seq[(Ref[G, Variable[G]], Expr[G])], yields: Seq[(Expr[G], Ref[G, Variable[G]])])(val blame: Blame[InvocationFailure])(implicit val o: Origin) extends AnyMethodInvocation[G] with ConstructorInvocationImpl[G] sealed trait AnyFunctionInvocation[G] extends Invocation[G] with AnyFunctionInvocationImpl[G] final case class FunctionInvocation[G](ref: Ref[G, Function[G]], args: Seq[Expr[G]], typeArgs: Seq[Type[G]], givenMap: Seq[(Ref[G, Variable[G]], Expr[G])], yields: Seq[(Expr[G], Ref[G, Variable[G]])])(val blame: Blame[InvocationFailure])(implicit val o: Origin) extends AnyFunctionInvocation[G] with FunctionInvocationImpl[G] @@ -1304,18 +1304,17 @@ final case class PVLInvocation[G](obj: Option[Expr[G]], method: String, args: Se var ref: Option[PVLInvocationTarget[G]] = None } -final case class PVLNew[G](t: Type[G], args: Seq[Expr[G]], givenMap: Seq[(Ref[G, Variable[G]], Expr[G])], yields: Seq[(Expr[G], Ref[G, Variable[G]])])(val blame: Blame[InvocationFailure])(implicit val o: Origin) extends PVLExpr[G] with PVLNewImpl[G] { +final case class PVLNew[G](t: Type[G], typeArgs: Seq[Type[G]], args: Seq[Expr[G]], givenMap: Seq[(Ref[G, Variable[G]], Expr[G])], yields: Seq[(Expr[G], Ref[G, Variable[G]])])(val blame: Blame[InvocationFailure])(implicit val o: Origin) extends PVLExpr[G] with PVLNewImpl[G] { var ref: Option[PVLConstructorTarget[G]] = None } sealed trait PVLClassDeclaration[G] extends ClassDeclaration[G] with PVLClassDeclarationImpl[G] -@scopes[LabelDecl] final class PVLConstructor[G](val contract: ApplicableContract[G], val args: Seq[Variable[G]], val body: Option[Statement[G]])(val blame: Blame[ConstructorFailure])(implicit val o: Origin) extends PVLClassDeclaration[G] with PVLConstructorImpl[G] +@scopes[LabelDecl] final class PVLConstructor[G](val contract: ApplicableContract[G], val typeArgs: Seq[Variable[G]], val args: Seq[Variable[G]], val body: Option[Statement[G]])(val blame: Blame[ConstructorFailure])(implicit val o: Origin) extends PVLClassDeclaration[G] with PVLConstructorImpl[G] final case class TSeqProg[G](cls: Ref[G, SeqProg[G]])(implicit val o: Origin = DiagnosticOrigin) extends DeclaredType[G] with TSeqProgImpl[G] final case class TPVLSeqProg[G](cls: Ref[G, PVLSeqProg[G]])(implicit val o: Origin = DiagnosticOrigin) extends DeclaredType[G] with TPVLSeqProgImpl[G] final case class TEndpoint[G](cls: Ref[G, Endpoint[G]])(implicit val o: Origin = DiagnosticOrigin) extends DeclaredType[G] with TEndpointImpl[G] -// TODO (RR): Integrate generics final class PVLEndpoint[G](val name: String, val cls: Ref[G, Class[G]], val args: Seq[Expr[G]])(val blame: Blame[EndpointFailure])(implicit val o: Origin) extends ClassDeclaration[G] with PVLEndpointImpl[G] { var ref: Option[PVLConstructorTarget[G]] = None } diff --git a/src/col/vct/col/ast/declaration/global/ClassImpl.scala b/src/col/vct/col/ast/declaration/global/ClassImpl.scala index 64d5d8d3e6..3ab9d80a94 100644 --- a/src/col/vct/col/ast/declaration/global/ClassImpl.scala +++ b/src/col/vct/col/ast/declaration/global/ClassImpl.scala @@ -24,9 +24,10 @@ trait ClassImpl[G] extends Declarator[G] with ClassOps[G] { this: Class[G] => (if(intrinsicLockInvariant == tt[G]) Empty else Doc.spec(Show.lazily(layoutLockInvariant(_)))) <> Group( Text("class") <+> ctx.name(this) <> + (if (typeArgs.nonEmpty) Text("<") <> Doc.args(typeArgs) <> ">" else Empty) <> (if(supports.isEmpty) Empty else Text(" implements") <+> Doc.args(supports.map(ctx.name).map(Text))) <+> "{" ) <>> - Doc.stack(declarations) <+/> + Doc.stack(decls) <+/> "}" } \ No newline at end of file diff --git a/src/col/vct/col/ast/type/TClassImpl.scala b/src/col/vct/col/ast/type/TClassImpl.scala index dcd0fe88a6..5824223ef6 100644 --- a/src/col/vct/col/ast/type/TClassImpl.scala +++ b/src/col/vct/col/ast/type/TClassImpl.scala @@ -1,11 +1,14 @@ package vct.col.ast.`type` -import vct.col.ast.{Class, TClass} +import vct.col.ast.{Class, TClass, Type, Variable} import vct.col.print.{Ctx, Doc, Empty, Group, Text} import vct.col.ast.ops.TClassOps trait TClassImpl[G] extends TClassOps[G] { this: TClass[G] => def transSupportArrows: Seq[(Class[G], Class[G])] = cls.decl.transSupportArrows - override def layout(implicit ctx: Ctx): Doc = Text(ctx.name(cls)) + override def layout(implicit ctx: Ctx): Doc = Text(ctx.name(cls)) <> ( + if (typeArgs.nonEmpty) Text("<") <> Doc.args(typeArgs) <> ">" else Empty) + + def typeEnv: Map[Variable[G], Type[G]] = cls.decl.typeArgs.zip(typeArgs).toMap } \ No newline at end of file diff --git a/src/col/vct/col/ast/unsorted/ConstructorInvocationImpl.scala b/src/col/vct/col/ast/unsorted/ConstructorInvocationImpl.scala index 581615c772..e51feca90f 100644 --- a/src/col/vct/col/ast/unsorted/ConstructorInvocationImpl.scala +++ b/src/col/vct/col/ast/unsorted/ConstructorInvocationImpl.scala @@ -1,9 +1,11 @@ package vct.col.ast.unsorted -import vct.col.ast.ConstructorInvocation +import vct.col.ast.{ConstructorInvocation, TClass, Type} import vct.col.ast.ops.ConstructorInvocationOps import vct.col.print._ trait ConstructorInvocationImpl[G] extends ConstructorInvocationOps[G] { this: ConstructorInvocation[G] => // override def layout(implicit ctx: Ctx): Doc = ??? + + override def t: Type[G] = TClass(ref.decl.cls, classTypeArgs) } diff --git a/src/col/vct/col/resolve/Resolve.scala b/src/col/vct/col/resolve/Resolve.scala index 810481a6c2..e84b835918 100644 --- a/src/col/vct/col/resolve/Resolve.scala +++ b/src/col/vct/col/resolve/Resolve.scala @@ -411,8 +411,7 @@ case object ResolveReferences extends LazyLogging { case access@PVLAccess(subject, field) => access.ref = Some(PVL.findDerefOfClass(subject.cls, field).getOrElse(throw NoSuchNameError("field", field, access))) case endpoint: PVLEndpoint[G] => - // TODO (RR): Integrate generics - endpoint.ref = Some(PVL.findConstructor(TClass(endpoint.cls.decl.ref[Class[G]], Seq()), endpoint.args).getOrElse(throw ConstructorNotFound(endpoint))) + endpoint.ref = Some(PVL.findConstructor(TClass(endpoint.cls.decl.ref[Class[G]], Seq()), Seq(), endpoint.args).getOrElse(throw ConstructorNotFound(endpoint))) case parAssign: PVLSeqAssign[G] => parAssign.receiver.tryResolve(receiver => PVL.findName(receiver, ctx) match { case Some(RefPVLEndpoint(decl)) => decl @@ -486,8 +485,8 @@ case object ResolveReferences extends LazyLogging { inv.ref = Some(PVL.findInstanceMethod(obj, method, args, typeArgs, inv.blame).getOrElse(throw NoSuchNameError("method", method, inv))) Spec.resolveGiven(givenMap, inv.ref.get, inv) Spec.resolveYields(ctx, yields, inv.ref.get, inv) - case inv@PVLNew(t, args, givenMap, yields) => - inv.ref = Some(PVL.findConstructor(t, args).getOrElse(throw NoSuchConstructor(inv))) + case inv@PVLNew(t, typeArgs, args, givenMap, yields) => + inv.ref = Some(PVL.findConstructor(t, typeArgs, args).getOrElse(throw NoSuchConstructor(inv))) Spec.resolveGiven(givenMap, inv.ref.get, inv) Spec.resolveYields(ctx, yields, inv.ref.get, inv) case n@NewObject(ref) => diff --git a/src/col/vct/col/resolve/lang/PVL.scala b/src/col/vct/col/resolve/lang/PVL.scala index 0b23877798..bf1a2f277f 100644 --- a/src/col/vct/col/resolve/lang/PVL.scala +++ b/src/col/vct/col/resolve/lang/PVL.scala @@ -7,13 +7,11 @@ import vct.col.ref.Ref import vct.col.resolve.ctx._ case object PVL { - // TODO (RR): Is more logic required here in the case of generic arguments? Probably for all methods here...? See also: Spec.scala - - def findConstructor[G](t: Type[G], args: Seq[Expr[G]]): Option[PVLConstructorTarget[G]] = { + def findConstructor[G](t: Type[G], typeArgs: Seq[Type[G]], args: Seq[Expr[G]]): Option[PVLConstructorTarget[G]] = { t match { - case TClass(Ref(cls), _) => + case t @ TClass(Ref(cls), _) => val resolvedCons = cls.decls.collectFirst { - case cons: PVLConstructor[G] if Util.compat(args, cons.args) => RefPVLConstructor(cons) + case cons: PVLConstructor[G] if Util.compat(args, cons.args, cons.typeArgs.zip(typeArgs).toMap ++ t.typeEnv) => RefPVLConstructor(cons) } args match { diff --git a/src/col/vct/col/resolve/lang/Util.scala b/src/col/vct/col/resolve/lang/Util.scala index 60ca8e9026..bf927bc0e5 100644 --- a/src/col/vct/col/resolve/lang/Util.scala +++ b/src/col/vct/col/resolve/lang/Util.scala @@ -15,8 +15,17 @@ case object Util { } def compat[G](args: Seq[Expr[G]], typeArgs: Seq[Type[G]], genericInvokable: ContractApplicable[G]): Boolean = - args.size == genericInvokable.args.size && typeArgs.size == genericInvokable.typeArgs.size && - genericInvokable.args.zip(args).forall { - case (v, e) => v.t.particularize(genericInvokable.typeArgs.zip(typeArgs).toMap).superTypeOf(e.t) + compat(args, typeArgs, genericInvokable.args, genericInvokable.typeArgs) + + def compat[G](args: Seq[Expr[G]], typeArgs: Seq[Type[G]], params: Seq[Variable[G]], typeParams: Seq[Variable[G]]): Boolean = + args.size == params.size && typeArgs.size == typeParams.size && + params.zip(args).forall { + case (v, e) => v.t.particularize(typeParams.zip(typeArgs).toMap).superTypeOf(e.t) + } + + def compat[G](args: Seq[Expr[G]], params: Seq[Variable[G]], typeEnv: Map[Variable[G], Type[G]]): Boolean = + args.size == params.size && + params.zip(args).forall { + case (v, e) => v.t.particularize(typeEnv).superTypeOf(e.t) } } diff --git a/src/col/vct/col/typerules/CoercingRewriter.scala b/src/col/vct/col/typerules/CoercingRewriter.scala index 371f467235..9414f5e736 100644 --- a/src/col/vct/col/typerules/CoercingRewriter.scala +++ b/src/col/vct/col/typerules/CoercingRewriter.scala @@ -774,8 +774,8 @@ abstract class CoercingRewriter[Pre <: Generation]() extends BaseCoercingRewrite case SYCLNDRange(globalRange, localRange) => SYCLNDRange(globalRange, localRange) case StringConcat(left, right) => StringConcat(string(left), string(right)) - case inv @ ConstructorInvocation(ref, args, outArgs, typeArgs, givenMap, yields) => - ConstructorInvocation(ref, coerceArgs(args, ref.decl, typeArgs, canCDemote = true), outArgs, typeArgs, coerceGiven(givenMap, canCDemote = true), coerceYields(yields, args.head))(inv.blame) + case inv @ ConstructorInvocation(ref, classTypeArgs, args, outArgs, typeArgs, givenMap, yields) => + ConstructorInvocation(ref, classTypeArgs, coerceArgs(args, ref.decl, typeArgs, canCDemote = true), outArgs, typeArgs, coerceGiven(givenMap, canCDemote = true), coerceYields(yields, args.head))(inv.blame) case acc @ CStructAccess(struct, field) => CStructAccess(struct, field)(acc.blame) case deref @ CStructDeref(struct, field) => @@ -1114,7 +1114,7 @@ abstract class CoercingRewriter[Pre <: Generation]() extends BaseCoercingRewrite case PVLDeref(obj, field) => e case PVLInvocation(obj, method, args, typeArgs, givenArgs, yields) => e case PVLLocal(name) => e - case PVLNew(t, args, givenMap, yields) => e + case PVLNew(t, typeArgs, args, givenMap, yields) => e case Range(from, to) => Range(int(from), int(to)) case div@RatDiv(left, right) => @@ -1491,8 +1491,8 @@ abstract class CoercingRewriter[Pre <: Generation]() extends BaseCoercingRewrite case IndetBranch(branches) => IndetBranch(branches) case Inhale(assn) => Inhale(res(assn)) case Instantiate(cls, dest) => Instantiate(cls, dest) - case inv @ InvokeConstructor(ref, out, args, outArgs, typeArgs, givenMap, yields) => - InvokeConstructor(ref, out, coerceArgs(args, ref.decl, typeArgs, canCDemote = true), outArgs, typeArgs, coerceGiven(givenMap, canCDemote = true), coerceYields(yields, args.head))(inv.blame) + case inv @ InvokeConstructor(ref, classTypeArgs, out, args, outArgs, typeArgs, givenMap, yields) => + InvokeConstructor(ref, classTypeArgs, out, coerceArgs(args, ref.decl, typeArgs, canCDemote = true), outArgs, typeArgs, coerceGiven(givenMap, canCDemote = true), coerceYields(yields, args.head))(inv.blame) case inv @ InvokeProcedure(ref, args, outArgs, typeArgs, givenMap, yields) => InvokeProcedure(ref, coerceArgs(args, ref.decl, typeArgs, canCDemote=true), outArgs, typeArgs, coerceGiven(givenMap,canCDemote=true), coerceYields(yields, args.head))(inv.blame) case inv @ InvokeMethod(obj, ref, args, outArgs, typeArgs, givenMap, yields) => diff --git a/src/col/vct/col/util/AstBuildHelpers.scala b/src/col/vct/col/util/AstBuildHelpers.scala index 3c59165e75..887362651f 100644 --- a/src/col/vct/col/util/AstBuildHelpers.scala +++ b/src/col/vct/col/util/AstBuildHelpers.scala @@ -362,12 +362,13 @@ object AstBuildHelpers { def constructorInvocation[G] (blame: Blame[InvocationFailure], ref: Ref[G, Constructor[G]], + classTypeArgs: Seq[Type[G]] = Nil, args: Seq[Expr[G]] = Nil, outArgs: Seq[Expr[G]] = Nil, typeArgs: Seq[Type[G]] = Nil, givenMap: Seq[(Ref[G, Variable[G]], Expr[G])] = Nil, yields: Seq[(Expr[G], Ref[G, Variable[G]])] = Nil)(implicit o: Origin): ConstructorInvocation[G] = - ConstructorInvocation(ref, args, outArgs, typeArgs, givenMap, yields)(blame) + ConstructorInvocation(ref, classTypeArgs, args, outArgs, typeArgs, givenMap, yields)(blame) private def GeneratedQuantifier: Origin = Origin( Seq( diff --git a/src/main/vct/main/stages/Transformation.scala b/src/main/vct/main/stages/Transformation.scala index c1d25f77a1..93bd930838 100644 --- a/src/main/vct/main/stages/Transformation.scala +++ b/src/main/vct/main/stages/Transformation.scala @@ -122,16 +122,23 @@ class Transformation case (key, action) => if (pass.key == key) action(result) } + println(s"Doing: ${pass.key}") + + val old = result result = pass().dispatch(result) - result.tasks.map(_.program).flatMap(program => program.check.map(program -> _)) match { - case Nil => // ok - case errors => throw TransformationCheckError(pass, errors) - } + println(s"Finished: ${pass.key}") onAfterPassKey.foreach { case (key, action) => if (pass.key == key) action(result) } + val k = pass.key + println("ok") + + result.tasks.map(_.program).flatMap(program => program.check.map(program -> _)) match { + case Nil => // ok + case errors => throw TransformationCheckError(pass, errors) + } result = PrettifyBlocks().dispatch(result) } diff --git a/src/parsers/antlr4/LangPVLParser.g4 b/src/parsers/antlr4/LangPVLParser.g4 index 7d666eeff1..31992c1301 100644 --- a/src/parsers/antlr4/LangPVLParser.g4 +++ b/src/parsers/antlr4/LangPVLParser.g4 @@ -34,7 +34,7 @@ field : finalFlag? type identifierList ';' ; method : contract valModifier* type identifier typeVars? '(' args? ')' methodBody ; methodBody : ';' | block ; -constructor : contract 'constructor' '(' args? ')' methodBody ; +constructor : contract 'constructor' typeVars? '(' args? ')' methodBody ; runMethod : contract 'run' methodBody ; diff --git a/src/parsers/vct/parsers/transform/PVLToCol.scala b/src/parsers/vct/parsers/transform/PVLToCol.scala index 21c29946b2..73b67c2421 100644 --- a/src/parsers/vct/parsers/transform/PVLToCol.scala +++ b/src/parsers/vct/parsers/transform/PVLToCol.scala @@ -142,9 +142,10 @@ case class PVLToCol[G](override val baseOrigin: Origin, } def convert(implicit constructor: ConstructorContext): Seq[ClassDeclaration[G]] = constructor match { - case Constructor0(contract, _, _, args, _, body) => + case Constructor0(contract, _, typeVars, _, args, _, body) => Seq(withContract(contract, contract => - new PVLConstructor(contract.consumeApplicableContract(blame(constructor)), args.map(convert(_)).getOrElse(Nil), convert(body) + new PVLConstructor(contract.consumeApplicableContract(blame(constructor)), typeVars.map(convert(_)).getOrElse(Seq()), + args.map(convert(_)).getOrElse(Nil), convert(body) )(blame(constructor)))) } @@ -273,7 +274,7 @@ case class PVLToCol[G](override val baseOrigin: Origin, def convert(implicit expr: NewExprContext): Expr[G] = expr match { case NewExpr0(_, name, Call0(typeArgs, args, given, yields)) => - PVLNew(convert(name), convert(args), convertGiven(given), convertYields(yields))(blame(expr)) + PVLNew(convert(name), typeArgs.map(convert(_)).getOrElse(Seq()), convert(args), convertGiven(given), convertYields(yields))(blame(expr)) case NewExpr1(_, t, dims) => NewArray(convert(t), convert(dims), moreDims = 0, true)(blame(expr)) case NewExpr2(inner) => convert(inner) } diff --git a/src/parsers/vct/parsers/transform/systemctocol/engine/FunctionTransformer.java b/src/parsers/vct/parsers/transform/systemctocol/engine/FunctionTransformer.java index 1e00b59bdb..00fbd88e07 100644 --- a/src/parsers/vct/parsers/transform/systemctocol/engine/FunctionTransformer.java +++ b/src/parsers/vct/parsers/transform/systemctocol/engine/FunctionTransformer.java @@ -19,6 +19,7 @@ import vct.parsers.transform.systemctocol.colmodel.ProcessClass; import vct.parsers.transform.systemctocol.util.GeneratedBlame; import vct.parsers.transform.systemctocol.util.OriGen; +import vct.parsers.transform.systemctocol.util.Seqs; /** * Transforms given SystemC functions into either constructors, run methods or regular methods in the COL system. @@ -195,7 +196,7 @@ public PVLConstructor transform_constructor(COLClass col_class, java.util.Map // Create constructor contract ApplicableContract contract = generate_constructor_specifications(fields, m_param); - return new PVLConstructor<>(contract, List.from(CollectionConverters.asScala(parameters)), Option.apply(body), + return new PVLConstructor<>(contract, Seqs.empty(), List.from(CollectionConverters.asScala(parameters)), Option.apply(body), new GeneratedBlame<>(), OriGen.create()); } diff --git a/src/parsers/vct/parsers/transform/systemctocol/engine/KnownTypeTransformer.java b/src/parsers/vct/parsers/transform/systemctocol/engine/KnownTypeTransformer.java index 79b98b7e6f..ac8000ccf1 100644 --- a/src/parsers/vct/parsers/transform/systemctocol/engine/KnownTypeTransformer.java +++ b/src/parsers/vct/parsers/transform/systemctocol/engine/KnownTypeTransformer.java @@ -272,7 +272,7 @@ private PVLConstructor create_fifo_constructor(Origin o, Type t, InstanceF new UnitAccountedPredicate<>(col_system.fold_star(conds), OriGen.create()), col_system.TRUE, col_system.NO_SIGNALS, col_system.NO_VARS, col_system.NO_VARS, Option.empty(), new GeneratedBlame<>(), OriGen.create()); - return new PVLConstructor<>(contract, params, Option.apply(body), new GeneratedBlame<>(), o); + return new PVLConstructor<>(contract, Seqs.empty(), params, Option.apply(body), new GeneratedBlame<>(), o); } /** @@ -627,7 +627,7 @@ private PVLConstructor create_signal_constructor(Origin o, InstanceField m ApplicableContract contract = new ApplicableContract<>(new UnitAccountedPredicate<>(col_system.TRUE, OriGen.create()), new UnitAccountedPredicate<>(col_system.fold_star(conditions), OriGen.create()), col_system.TRUE, col_system.NO_SIGNALS, col_system.NO_VARS, col_system.NO_VARS, Option.empty(), new GeneratedBlame<>(), OriGen.create()); - return new PVLConstructor<>(contract, params, Option.apply(body), new GeneratedBlame<>(), o); + return new PVLConstructor<>(contract, Seqs.empty(), params, Option.apply(body), new GeneratedBlame<>(), o); } /** diff --git a/src/parsers/vct/parsers/transform/systemctocol/engine/MainTransformer.java b/src/parsers/vct/parsers/transform/systemctocol/engine/MainTransformer.java index fce38e37d1..c22e7d0cde 100644 --- a/src/parsers/vct/parsers/transform/systemctocol/engine/MainTransformer.java +++ b/src/parsers/vct/parsers/transform/systemctocol/engine/MainTransformer.java @@ -473,7 +473,7 @@ private void create_main_constructor() { Expr ensures = create_scheduler_contract(); ApplicableContract contract = col_system.to_applicable_contract(col_system.TRUE, ensures); - main_constructor = new PVLConstructor<>(contract, col_system.NO_VARS, Option.apply(body), new GeneratedBlame<>(), OriGen.create()); + main_constructor = new PVLConstructor<>(contract, Seqs.empty(), col_system.NO_VARS, Option.apply(body), new GeneratedBlame<>(), OriGen.create()); } /** @@ -596,7 +596,7 @@ private Statement create_field_initialization(InstanceField field) { } // Create the new expression and return the assignment - PVLNew new_expr = new PVLNew<>(field.t(), List.from(CollectionConverters.asScala(parameters)), col_system.NO_GIVEN, + PVLNew new_expr = new PVLNew<>(field.t(), Seqs.empty(), List.from(CollectionConverters.asScala(parameters)), col_system.NO_GIVEN, col_system.NO_YIELDS, new GeneratedBlame<>(), OriGen.create()); return new Assign<>(field_deref, new_expr, new GeneratedBlame<>(), OriGen.create()); } diff --git a/src/rewrite/vct/rewrite/ClassToRef.scala b/src/rewrite/vct/rewrite/ClassToRef.scala index 8b6833e73a..003308c74b 100644 --- a/src/rewrite/vct/rewrite/ClassToRef.scala +++ b/src/rewrite/vct/rewrite/ClassToRef.scala @@ -239,7 +239,7 @@ case class ClassToRef[Pre <: Generation]() extends Rewriter[Pre] { givenMap = givenMap.map { case (Ref(v), e) => (succ(v), dispatch(e)) }, yields = yields.map { case (e, Ref(v)) => (dispatch(e), succ(v)) }, )(PreBlameSplit.left(InstanceNullPreconditionFailed(inv.blame, inv), PreBlameSplit.left(PanicBlame("incorrect instance method type?"), inv.blame)))(inv.o) - case inv @ InvokeConstructor(Ref(cons), out, args, outArgs, typeArgs, givenMap, yields) => + case inv @ InvokeConstructor(Ref(cons), _, out, args, outArgs, typeArgs, givenMap, yields) => InvokeProcedure[Post]( ref = consSucc.ref(cons), args = args.map(dispatch), diff --git a/src/rewrite/vct/rewrite/MonomorphizeClass.scala b/src/rewrite/vct/rewrite/MonomorphizeClass.scala index f0b8d99b8b..18c9270007 100644 --- a/src/rewrite/vct/rewrite/MonomorphizeClass.scala +++ b/src/rewrite/vct/rewrite/MonomorphizeClass.scala @@ -25,6 +25,10 @@ case class MonomorphizeClass[Pre <: Generation]() extends Rewriter[Pre] { ) { def key = (cls, typeValues) def substitute = Substitute(Map.empty[Expr[Pre], Expr[Pre]], typeSubs = substitutions) + + def evalType(t: Type[Pre]): Type[Pre] = substitute.dispatch(t) + def evalTypes(ts: Seq[Type[Pre]]): Seq[Type[Pre]] = ts.map(evalType) + } val ctx: ScopedStack[InstantiationContext] = ScopedStack() @@ -34,7 +38,7 @@ case class MonomorphizeClass[Pre <: Generation]() extends Rewriter[Pre] { def instantiate(cls: Class[Pre], typeValues: Seq[Type[Pre]]): Unit = { val key = (cls, typeValues) genericSucc.get((key, cls)) match { - case Some(ref) => // Done + case Some(_) => // Done case None => val newCtx = InstantiationContext( cls, @@ -59,42 +63,51 @@ case class MonomorphizeClass[Pre <: Generation]() extends Rewriter[Pre] { case cls: Class[Pre] if cls.typeArgs.nonEmpty => cls.typeArgs.foreach(_.drop()) instantiate(cls, cls.typeArgs.map(v => v.t.asInstanceOf[TType[Pre]].t)) - case m: InstanceMethod[Pre] if ctx.nonEmpty => - val `m'` = classDeclarations.collect(super.dispatch(m))._1.head - genericSucc((ctx.top.key, m)) = `m'` + case method: InstanceMethod[Pre] if ctx.nonEmpty => + val newMethod = method.rewriteDefault() + genericSucc((ctx.top.key, method)) = newMethod + classDeclarations.declare(newMethod) + classDeclarations.succeedOnly(method, newMethod) + case cons: Constructor[Pre] if ctx.nonEmpty => + val newCons = cons.rewriteDefault() + genericSucc((ctx.top.key, cons)) = newCons + classDeclarations.declare(newCons) + classDeclarations.succeedOnly(cons, newCons) case other => allScopes.anySucceed(other, other.rewriteDefault()) } - override def dispatch(t: Type[Pre]): Type[Post] = t match { - case TClass(Ref(cls), typeArgs) if typeArgs.nonEmpty => - val typeValues = typeArgs.map(ctx.top.substitute.dispatch) + override def dispatch(t: Type[Pre]): Type[Post] = (t, ctx.topOption) match { + case (TClass(Ref(cls), typeArgs), ctx) if typeArgs.nonEmpty => + val typeValues = ctx match { + case Some(ctx) => typeArgs.map(ctx.substitute.dispatch) + case None => typeArgs + } instantiate(cls, typeValues) - TClass[Post](genericSucc.ref(((cls, typeValues), cls)).asInstanceOf, Seq()) - case TVar(Ref(v)) => - ??? // Don't think this should occur anymore? -// currentSubstitutions.top(v) + TClass[Post](genericSucc.ref[Post, Class[Post]](((cls, typeValues), cls)), Seq()) + case (tvar @ TVar(_), Some(ctx)) => + dispatch(ctx.substitutions(tvar)) case _ => t.rewriteDefault() } - def evalType(t: Type[Pre]): Type[Pre] = ctx.top.substitute.dispatch(t) - - def evalTypes(ts: Seq[Type[Pre]]): Seq[Type[Pre]] = ts.map(evalType) - - override def dispatch(e: Expr[Pre]): Expr[Rewritten[Pre]] = e match { - case inv: MethodInvocation[Pre] => + override def dispatch(stmt: Statement[Pre]): Statement[Post] = stmt match { + case inv: InvokeConstructor[Pre] if inv.classTypeArgs.nonEmpty => + val cls = inv.ref.decl.cls.decl + val typeValues = ctx.topOption.map(_.evalTypes(inv.classTypeArgs)).getOrElse(inv.classTypeArgs) + instantiate(cls, typeValues) + inv.rewrite( + ref = genericSucc.ref[Post, Constructor[Post]](((cls, typeValues), inv.ref.decl)) + ) + case inv: InvokeMethod[Pre] => inv.obj.t match { case TClass(Ref(cls), typeArgs) if typeArgs.nonEmpty => - val typeValues = evalTypes(typeArgs) + val typeValues = ctx.topOption.map(_.evalTypes(typeArgs)).getOrElse(typeArgs) instantiate(cls, typeValues) inv.rewrite( - ref = genericSucc.ref(((cls, typeValues), inv.ref.decl)).asInstanceOf + ref = genericSucc.ref[Post, InstanceMethod[Post]](((cls, typeValues), inv.ref.decl)) ) case _ => inv.rewriteDefault() } - case inv: ConstructorInvocation[Pre] => ??? - case inv: InstanceFunctionInvocation[Pre] => ??? - case inv: InstancePredicateApply[Pre] => ??? case other => other.rewriteDefault() } } diff --git a/src/rewrite/vct/rewrite/ResolveExpressionSideEffects.scala b/src/rewrite/vct/rewrite/ResolveExpressionSideEffects.scala index 1b4fc50ab5..7a4473b84d 100644 --- a/src/rewrite/vct/rewrite/ResolveExpressionSideEffects.scala +++ b/src/rewrite/vct/rewrite/ResolveExpressionSideEffects.scala @@ -474,7 +474,7 @@ case class ResolveExpressionSideEffects[Pre <: Generation]() extends Rewriter[Pr effect(dispatch(post)) stored(value, oldValue.t) case inv @ MethodInvocation(obj, Ref(method), args, outArgs, typeArgs, givenMap, yields) => - val res = new Variable[Post](dispatch(method.returnType))(ResultVar) + val res = new Variable[Post](dispatch(inv.t))(ResultVar) variables.succeed(res.asInstanceOf[Variable[Pre]], res) effect(InvokeMethod[Post]( obj = inlined(obj), @@ -485,7 +485,7 @@ case class ResolveExpressionSideEffects[Pre <: Generation]() extends Rewriter[Pr givenMap.map { case (Ref(v), e) => (succ(v), inlined(e)) }, yields.map { case (e, Ref(v)) => (inlined(e), succ(v)) }, )(inv.blame)(e.o)) - stored(res.get(SideEffectOrigin), method.returnType) + stored(res.get(SideEffectOrigin), inv.t) case inv @ ProcedureInvocation(Ref(method), args, outArgs, typeArgs, givenMap, yields) => val res = new Variable[Post](dispatch(method.returnType))(ResultVar) variables.succeed(res.asInstanceOf[Variable[Pre]], res) @@ -498,12 +498,13 @@ case class ResolveExpressionSideEffects[Pre <: Generation]() extends Rewriter[Pr yields.map { case (e, Ref(v)) => (inlined(e), succ(v)) }, )(inv.blame)(e.o)) stored(res.get(SideEffectOrigin), method.returnType) - case inv @ ConstructorInvocation(Ref(cons), args, outArgs, typeArgs, givenMap, yields) => - val typ = TClass[Post](succ(cons.cls.decl), typeArgs.map(dispatch)) + case inv @ ConstructorInvocation(Ref(cons), classTypeArgs, args, outArgs, typeArgs, givenMap, yields) => + val typ = TClass[Post](succ(cons.cls.decl), classTypeArgs.map(dispatch)) val res = new Variable[Post](typ)(ResultVar) variables.succeed(res.asInstanceOf[Variable[Pre]], res) effect(InvokeConstructor[Post]( ref = succ(cons), + classTypeArgs = classTypeArgs.map(dispatch), out = res.get(ResultVar), args = args.map(inlined), outArgs = outArgs.map(dispatch), @@ -511,7 +512,7 @@ case class ResolveExpressionSideEffects[Pre <: Generation]() extends Rewriter[Pr givenMap.map { case (Ref(v), e) => (succ(v), inlined(e)) }, yields.map { case (e, Ref(v)) => (inlined(e), succ(v)) }, )(inv.blame)(e.o)) - stored(res.get(SideEffectOrigin), TClass(cons.cls, typeArgs)) + stored(res.get(SideEffectOrigin), TClass(cons.cls, classTypeArgs)) case NewObject(Ref(cls)) => val res = new Variable[Post](TClass(succ(cls), Seq()))(ResultVar) variables.succeed(res.asInstanceOf[Variable[Pre]], res) diff --git a/src/rewrite/vct/rewrite/lang/LangJavaToCol.scala b/src/rewrite/vct/rewrite/lang/LangJavaToCol.scala index aec0ccd95d..2d83321d24 100644 --- a/src/rewrite/vct/rewrite/lang/LangJavaToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangJavaToCol.scala @@ -465,13 +465,13 @@ case class LangJavaToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) extends inv.ref.get match { case RefModel(decl) => ModelNew[Post](rw.succ(decl)) case RefJavaConstructor(cons) => - ConstructorInvocation[Post](javaConstructor.ref(cons), args.map(rw.dispatch), Nil, typeParams.map(rw.dispatch), + ConstructorInvocation[Post](javaConstructor.ref(cons), Seq(), args.map(rw.dispatch), Nil, typeParams.map(rw.dispatch), givenMap.map { case (Ref(v), e) => (rw.succ(v), rw.dispatch(e)) }, yields.map { case (e, Ref(v)) => (rw.dispatch(e), rw.succ(v)) })(inv.blame) case ImplicitDefaultJavaConstructor(_) => val cls = t.asInstanceOf[JavaTClass[Pre]].ref.decl val ref = new LazyRef[Post, Constructor[Post]](javaConstructor(javaDefaultConstructor(cls))) - ConstructorInvocation[Post](ref, + ConstructorInvocation[Post](ref, Seq(), args.map(rw.dispatch), Nil, typeParams.map(rw.dispatch), givenMap.map { case (Ref(v), e) => (rw.succ(v), rw.dispatch(e)) }, yields.map { case (e, Ref(v)) => (rw.dispatch(e), rw.succ(v)) })(inv.blame) diff --git a/src/rewrite/vct/rewrite/lang/LangPVLToCol.scala b/src/rewrite/vct/rewrite/lang/LangPVLToCol.scala index f91b7f8049..d14e36d010 100644 --- a/src/rewrite/vct/rewrite/lang/LangPVLToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangPVLToCol.scala @@ -104,16 +104,22 @@ case class LangPVLToCol[Pre <: Generation](rw: LangSpecificToCol[Pre], veymontGe } def newClass(inv: PVLNew[Pre]): Expr[Post] = { - val PVLNew(t, args, givenMap, yields) = inv + val PVLNew(t, typeArgs, args, givenMap, yields) = inv + val classTypeArgs = t match { + case TClass(_, typeArgs) => typeArgs + case _ => Seq() + } implicit val o: Origin = inv.o inv.ref.get match { case RefModel(decl) => ModelNew[Post](rw.succ(decl)) case RefPVLConstructor(decl) => - ConstructorInvocation[Post](pvlConstructor.ref(decl), args.map(rw.dispatch), Nil, Nil, + ConstructorInvocation[Post](pvlConstructor.ref(decl), classTypeArgs.map(rw.dispatch), args.map(rw.dispatch), + Nil, typeArgs.map(rw.dispatch), givenMap.map { case (Ref(v), e) => (rw.succ(v), rw.dispatch(e)) }, yields.map { case (e, Ref(v)) => (rw.dispatch(e), rw.succ(v)) })(inv.blame) case ImplicitDefaultPVLConstructor(_) => - ConstructorInvocation[Post](pvlDefaultConstructor.ref(t.asInstanceOf[TClass[Pre]].cls.decl), args.map(rw.dispatch), Nil, Nil, + ConstructorInvocation[Post](pvlDefaultConstructor.ref(t.asInstanceOf[TClass[Pre]].cls.decl), classTypeArgs.map(rw.dispatch), + args.map(rw.dispatch), Nil, typeArgs.map(rw.dispatch), givenMap.map { case (Ref(v), e) => (rw.succ(v), rw.dispatch(e)) }, yields.map { case (e, Ref(v)) => (rw.dispatch(e), rw.succ(v)) })(inv.blame) } From 11336d2a9fd867f742b9e868c24c024f740225d2 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Thu, 22 Feb 2024 17:17:35 +0100 Subject: [PATCH 09/52] Move some stuff back --- src/main/vct/main/stages/Transformation.scala | 15 ++++--------- .../vct/rewrite/MonomorphizeClass.scala | 22 ++++++++++--------- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/src/main/vct/main/stages/Transformation.scala b/src/main/vct/main/stages/Transformation.scala index 93bd930838..c1d25f77a1 100644 --- a/src/main/vct/main/stages/Transformation.scala +++ b/src/main/vct/main/stages/Transformation.scala @@ -122,24 +122,17 @@ class Transformation case (key, action) => if (pass.key == key) action(result) } - println(s"Doing: ${pass.key}") - - val old = result result = pass().dispatch(result) - println(s"Finished: ${pass.key}") - - onAfterPassKey.foreach { - case (key, action) => if (pass.key == key) action(result) - } - val k = pass.key - println("ok") - result.tasks.map(_.program).flatMap(program => program.check.map(program -> _)) match { case Nil => // ok case errors => throw TransformationCheckError(pass, errors) } + onAfterPassKey.foreach { + case (key, action) => if (pass.key == key) action(result) + } + result = PrettifyBlocks().dispatch(result) } diff --git a/src/rewrite/vct/rewrite/MonomorphizeClass.scala b/src/rewrite/vct/rewrite/MonomorphizeClass.scala index 18c9270007..10cd547448 100644 --- a/src/rewrite/vct/rewrite/MonomorphizeClass.scala +++ b/src/rewrite/vct/rewrite/MonomorphizeClass.scala @@ -20,7 +20,7 @@ case class MonomorphizeClass[Pre <: Generation]() extends Rewriter[Pre] { type Key = (Class[Pre], Seq[Type[Pre]]) case class InstantiationContext(cls: Class[Pre], typeValues: Seq[Type[Pre]], - removeBodies: Boolean, + keepBodies: Boolean, substitutions: Map[TVar[Pre], Type[Pre]] ) { def key = (cls, typeValues) @@ -28,14 +28,15 @@ case class MonomorphizeClass[Pre <: Generation]() extends Rewriter[Pre] { def evalType(t: Type[Pre]): Type[Pre] = substitute.dispatch(t) def evalTypes(ts: Seq[Type[Pre]]): Seq[Type[Pre]] = ts.map(evalType) - } val ctx: ScopedStack[InstantiationContext] = ScopedStack() + def keepBodies: Boolean = ctx.topOption.map { ctx => ctx.keepBodies }.getOrElse(true) + // key: generically instantiated type in pre val genericSucc: SuccessionMap[(Key, Declaration[Pre]), Declaration[Post]] = SuccessionMap() - def instantiate(cls: Class[Pre], typeValues: Seq[Type[Pre]]): Unit = { + def instantiate(cls: Class[Pre], typeValues: Seq[Type[Pre]], keepBodies: Boolean): Unit = { val key = (cls, typeValues) genericSucc.get((key, cls)) match { case Some(_) => // Done @@ -43,7 +44,7 @@ case class MonomorphizeClass[Pre <: Generation]() extends Rewriter[Pre] { val newCtx = InstantiationContext( cls, typeValues, - removeBodies = false, + keepBodies = keepBodies, substitutions = cls.typeArgs.map { v: Variable[Pre] => TVar(v.ref[Variable[Pre]]) }.zip(typeValues).toMap ) genericSucc((key, cls)) = ctx.having(newCtx) { @@ -62,14 +63,15 @@ case class MonomorphizeClass[Pre <: Generation]() extends Rewriter[Pre] { override def dispatch(decl: Declaration[Pre]): Unit = decl match { case cls: Class[Pre] if cls.typeArgs.nonEmpty => cls.typeArgs.foreach(_.drop()) - instantiate(cls, cls.typeArgs.map(v => v.t.asInstanceOf[TType[Pre]].t)) + instantiate(cls, cls.typeArgs.map(v => v.t.asInstanceOf[TType[Pre]].t), true) case method: InstanceMethod[Pre] if ctx.nonEmpty => - val newMethod = method.rewriteDefault() + val newMethod: InstanceMethod[Post] = + method.rewrite(body = if(keepBodies) method.body.map(dispatch) else None) genericSucc((ctx.top.key, method)) = newMethod classDeclarations.declare(newMethod) classDeclarations.succeedOnly(method, newMethod) case cons: Constructor[Pre] if ctx.nonEmpty => - val newCons = cons.rewriteDefault() + val newCons = cons.rewrite(body = if(keepBodies) cons.body.map(dispatch) else None) genericSucc((ctx.top.key, cons)) = newCons classDeclarations.declare(newCons) classDeclarations.succeedOnly(cons, newCons) @@ -83,7 +85,7 @@ case class MonomorphizeClass[Pre <: Generation]() extends Rewriter[Pre] { case Some(ctx) => typeArgs.map(ctx.substitute.dispatch) case None => typeArgs } - instantiate(cls, typeValues) + instantiate(cls, typeValues, false) TClass[Post](genericSucc.ref[Post, Class[Post]](((cls, typeValues), cls)), Seq()) case (tvar @ TVar(_), Some(ctx)) => dispatch(ctx.substitutions(tvar)) @@ -94,7 +96,7 @@ case class MonomorphizeClass[Pre <: Generation]() extends Rewriter[Pre] { case inv: InvokeConstructor[Pre] if inv.classTypeArgs.nonEmpty => val cls = inv.ref.decl.cls.decl val typeValues = ctx.topOption.map(_.evalTypes(inv.classTypeArgs)).getOrElse(inv.classTypeArgs) - instantiate(cls, typeValues) + instantiate(cls, typeValues, false) inv.rewrite( ref = genericSucc.ref[Post, Constructor[Post]](((cls, typeValues), inv.ref.decl)) ) @@ -102,7 +104,7 @@ case class MonomorphizeClass[Pre <: Generation]() extends Rewriter[Pre] { inv.obj.t match { case TClass(Ref(cls), typeArgs) if typeArgs.nonEmpty => val typeValues = ctx.topOption.map(_.evalTypes(typeArgs)).getOrElse(typeArgs) - instantiate(cls, typeValues) + instantiate(cls, typeValues, false) inv.rewrite( ref = genericSucc.ref[Post, InstanceMethod[Post]](((cls, typeValues), inv.ref.decl)) ) From 41dcf2c1089de9f78fb28098b5231e2c1cb9a4f3 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Thu, 22 Feb 2024 17:31:58 +0100 Subject: [PATCH 10/52] Add genericChannel spec --- examples/concepts/generics/genericChannel.pvl | 40 +++++++++++++++++++ .../examples/GenericsExamplesSpec.scala | 1 + 2 files changed, 41 insertions(+) create mode 100644 examples/concepts/generics/genericChannel.pvl diff --git a/examples/concepts/generics/genericChannel.pvl b/examples/concepts/generics/genericChannel.pvl new file mode 100644 index 0000000000..08587d709e --- /dev/null +++ b/examples/concepts/generics/genericChannel.pvl @@ -0,0 +1,40 @@ +lock_invariant Perm(transfering, 1) ** Perm(exchangeValue,1); +class IntegerChannel { + boolean transfering; + T exchangeValue; + + constructor() { + transfering = false; + } + + void writeValue(T v) { + lock this; + + loop_invariant Perm(transfering, 1) ** Perm(exchangeValue,1); + loop_invariant held(this); + while (!transfering) { + unlock this; + lock this; + } + + transfering = false; + exchangeValue = v; + unlock this; + } + + T readValue() { + lock this; + + loop_invariant Perm(transfering, 1) ** Perm(exchangeValue,1); + loop_invariant held(this); + while (transfering) { + wait this; + } + + T m = exchangeValue; + transfering = false; + unlock this; + + return m; + } +} diff --git a/test/main/vct/test/integration/examples/GenericsExamplesSpec.scala b/test/main/vct/test/integration/examples/GenericsExamplesSpec.scala index d49476b66b..0563fbba29 100644 --- a/test/main/vct/test/integration/examples/GenericsExamplesSpec.scala +++ b/test/main/vct/test/integration/examples/GenericsExamplesSpec.scala @@ -5,4 +5,5 @@ import vct.test.integration.helper.VercorsSpec class GenericsExamplesSpec() extends VercorsSpec { vercors should verify using silicon example "concepts/generics/genericProcedure.pvl" vercors should verify using silicon example "concepts/generics/box.pvl" + vercors should verify using silicon example "concepts/generics/genericChannel.pvl" } From b41377e7015d8479edacaa398e32c45b7b711ed2 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Fri, 23 Feb 2024 09:14:17 +0100 Subject: [PATCH 11/52] Generic channel verifies --- examples/concepts/generics/genericChannel.pvl | 11 ++++++++--- src/rewrite/vct/rewrite/EncodeIntrinsicLock.scala | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/examples/concepts/generics/genericChannel.pvl b/examples/concepts/generics/genericChannel.pvl index 08587d709e..5ca1724a4d 100644 --- a/examples/concepts/generics/genericChannel.pvl +++ b/examples/concepts/generics/genericChannel.pvl @@ -3,10 +3,13 @@ class IntegerChannel { boolean transfering; T exchangeValue; + ensures committed(this); constructor() { transfering = false; + commit this; } - + + context committed(this); void writeValue(T v) { lock this; @@ -21,14 +24,16 @@ class IntegerChannel { exchangeValue = v; unlock this; } - + + context committed(this); T readValue() { lock this; loop_invariant Perm(transfering, 1) ** Perm(exchangeValue,1); loop_invariant held(this); while (transfering) { - wait this; + unlock this; + lock this; } T m = exchangeValue; diff --git a/src/rewrite/vct/rewrite/EncodeIntrinsicLock.scala b/src/rewrite/vct/rewrite/EncodeIntrinsicLock.scala index 04a587f139..9f526369a0 100644 --- a/src/rewrite/vct/rewrite/EncodeIntrinsicLock.scala +++ b/src/rewrite/vct/rewrite/EncodeIntrinsicLock.scala @@ -70,7 +70,7 @@ case class EncodeIntrinsicLock[Pre <: Generation]() extends Rewriter[Pre] { val needsCommitted: mutable.Set[Class[Pre]] = mutable.Set() def getClass(obj: Expr[Pre]): Class[Pre] = obj.t match { - case TClass(Ref(cls), Seq()) => cls + case TClass(Ref(cls), _) => cls case _ => throw UnreachableAfterTypeCheck("This argument is not a class type.", obj) } From 108fc9a53c472f7b7cf714d11086874b3ad1517c Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Fri, 23 Feb 2024 09:50:10 +0100 Subject: [PATCH 12/52] Typecheck number of generic type arguments --- src/col/vct/col/typerules/CoercingRewriter.scala | 11 +++++++++-- src/main/vct/main/stages/Transformation.scala | 12 +++++++++++- src/rewrite/vct/rewrite/lang/LangJavaToCol.scala | 8 +++----- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/col/vct/col/typerules/CoercingRewriter.scala b/src/col/vct/col/typerules/CoercingRewriter.scala index 9414f5e736..ab11aa614c 100644 --- a/src/col/vct/col/typerules/CoercingRewriter.scala +++ b/src/col/vct/col/typerules/CoercingRewriter.scala @@ -26,6 +26,7 @@ case object CoercingRewriter { case IncoercibleText(e, target) => s"Expression `$e` could not be coerced to $target." case IncoercibleExplanation(e, message) => s"At `$e`: $message" case WrongType(n, expectedType, actualType) => s"$n was expected to have type $expectedType, but turned out to have type $actualType" + case WrongNumberOfTypeArguments(n, expectedLength, actualLength) => s"$n was expected to have $expectedLength number of type arguments, but it actually has $actualLength" }) ) } @@ -40,6 +41,8 @@ case object CoercingRewriter { case class WrongType(n: Node[_], expectedType: Type[_], actualType: Type[_]) extends CoercionError + case class WrongNumberOfTypeArguments(n: Node[_], expectedLength: Int, actualLength: Int) extends CoercionError + private def coercionOrigin(of: Expr[_]): Origin = of.o.where(name = "unknown") } @@ -1736,8 +1739,12 @@ abstract class CoercingRewriter[Pre <: Generation]() extends BaseCoercingRewrite } // PB: types may very well contain expressions eventually, but for now they don't. - def coerce(node: Type[Pre]): Type[Pre] = - node + def coerce(node: Type[Pre]): Type[Pre] = node match { + case t @ TClass(r @ Ref(cls), args) => + if (cls.typeArgs.length == args.length) node + else throw WrongNumberOfTypeArguments(t, cls.typeArgs.length, args.length) + case _ => node + } def coerce(node: LoopContract[Pre]): LoopContract[Pre] = { implicit val o: Origin = node.o diff --git a/src/main/vct/main/stages/Transformation.scala b/src/main/vct/main/stages/Transformation.scala index c1d25f77a1..afff1fdbea 100644 --- a/src/main/vct/main/stages/Transformation.scala +++ b/src/main/vct/main/stages/Transformation.scala @@ -2,6 +2,7 @@ package vct.main.stages import com.typesafe.scalalogging.LazyLogging import hre.debug.TimeTravel +import hre.debug.TimeTravel.CauseWithBadEffect import hre.progress.Progress import hre.stages.Stage import vct.col.ast.{Program, SimplificationRule, Verification} @@ -122,7 +123,16 @@ class Transformation case (key, action) => if (pass.key == key) action(result) } - result = pass().dispatch(result) + val oldResult = result + try { + result = pass().dispatch(result) + } catch { + case c @ CauseWithBadEffect(effect) => + logger.error(s"An error occurred in pass ${pass.key}") + throw c + } + + logger.info(s"Done: ${pass.key}") result.tasks.map(_.program).flatMap(program => program.check.map(program -> _)) match { case Nil => // ok diff --git a/src/rewrite/vct/rewrite/lang/LangJavaToCol.scala b/src/rewrite/vct/rewrite/lang/LangJavaToCol.scala index 2d83321d24..749eb355ce 100644 --- a/src/rewrite/vct/rewrite/lang/LangJavaToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangJavaToCol.scala @@ -285,10 +285,6 @@ case class LangJavaToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) extends def rewriteClass(cls: JavaClassOrInterface[Pre]): Unit = { implicit val o: Origin = cls.o - if (cls.typeParams.nonEmpty) { - throw new GenericJavaNotSupported(cls) - } - cls.decls.collect({ case decl: JavaClassDeclaration[Pre] => javaClassDeclToJavaClass(decl) = cls @@ -310,7 +306,9 @@ case class LangJavaToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) extends } val instanceClass = rw.currentThis.having(ThisObject(javaInstanceClassSuccessor.ref(cls))) { - new Class[Post](Seq(), rw.classDeclarations.collect { + new Class[Post]( + rw.variables.dispatch(cls.typeParams)(rw), + rw.classDeclarations.collect { makeJavaClass(cls.name, instDecls, javaInstanceClassSuccessor.ref(cls), isStaticPart = false) cls match { case cls: JavaClass[Pre] if BipComponent.get(cls).isDefined => From a1a4c26207d970e546d10863c2104b6a71679516 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Fri, 23 Feb 2024 10:06:41 +0100 Subject: [PATCH 13/52] Enhance reflection import to account for generics --- src/col/vct/col/resolve/lang/Java.scala | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/col/vct/col/resolve/lang/Java.scala b/src/col/vct/col/resolve/lang/Java.scala index ff6c13e93a..5b10024d4a 100644 --- a/src/col/vct/col/resolve/lang/Java.scala +++ b/src/col/vct/col/resolve/lang/Java.scala @@ -19,7 +19,7 @@ import vct.col.util.AstBuildHelpers._ import vct.result.VerificationError.{Unreachable, UserError} import java.io.File -import java.lang.reflect.{Modifier, Parameter} +import java.lang.reflect.{Modifier, Parameter, TypeVariable} import java.nio.file.Path import scala.annotation.tailrec import scala.collection.mutable @@ -151,6 +151,15 @@ case object Java extends LazyLogging { new JavaParam(Seq(), param.getName, translateRuntimeType(param.getType))(o.where(name = param.getName)) } + def translateTypeParameter[G](param: TypeVariable[_])(implicit o: Origin, ctx: TypeResolutionContext[G]): Variable[G] = { + // Guess that bound 0 is the one we want + val bound = param.getBounds()(0) match { + case cls: Class[_] => JavaTClass(translateRuntimeClass(cls).ref[JavaClassOrInterface[G]], Seq()) // Pretty sure the seq is not always empty here + case typeVar: TypeVariable[_] => ??? // We need some extra state here to find where this type var comes from + } + new Variable(bound)(o.where(name = param.getName)) + } + def translateRuntimeClass[G](cls: Class[_])(implicit o: Origin, ctx: TypeResolutionContext[G]): JavaClassOrInterface[G] = { val cons = cls.getConstructors.map(cons => { new JavaConstructor( @@ -188,6 +197,8 @@ case object Java extends LazyLogging { ) }) + val typeParameters = cls.getTypeParameters.map(translateTypeParameter(_)(o, ctx)) + if(cls.isAnnotation) { new JavaAnnotationInterface[G]( name = cls.getName.split('.').last, @@ -199,7 +210,7 @@ case object Java extends LazyLogging { new JavaInterface[G]( name = cls.getName.split('.').last, modifiers = Nil, - typeParams = Nil, + typeParams = typeParameters, ext = cls.getInterfaces.toIndexedSeq.map(cls => lazyType(cls.getName.split('.').toIndexedSeq, ctx)), decls = fields.toIndexedSeq ++ cons.toIndexedSeq ++ methods.toIndexedSeq, )(o.where(name = cls.getName.split('.').last)) @@ -207,7 +218,7 @@ case object Java extends LazyLogging { new JavaClass[G]( name = cls.getName.split('.').last, modifiers = Nil, - typeParams = Nil, + typeParams = typeParameters, intrinsicLockInvariant = `tt`, ext = Option(cls.getSuperclass).map(cls => lazyType(cls.getName.split('.').toIndexedSeq, ctx)).getOrElse(JAVA_LANG_OBJECT), imp = cls.getInterfaces.toIndexedSeq.map(cls => lazyType(cls.getName.split('.').toIndexedSeq, ctx)), From cf285ccedd29a1328122df6b8dee9ad378d2cfb3 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Fri, 23 Feb 2024 17:15:22 +0100 Subject: [PATCH 14/52] Seems to work sufficiently for java now too. --- .../technical/java/generics/MyRetention.java | 13 ++++ src/col/vct/col/resolve/lang/Java.scala | 59 ++++++++++++++----- src/main/vct/main/stages/Transformation.scala | 3 - .../vct/rewrite/MonomorphizeClass.scala | 9 ++- .../vct/rewrite/lang/LangJavaToCol.scala | 3 +- 5 files changed, 67 insertions(+), 20 deletions(-) create mode 100644 examples/technical/java/generics/MyRetention.java diff --git a/examples/technical/java/generics/MyRetention.java b/examples/technical/java/generics/MyRetention.java new file mode 100644 index 0000000000..b363da30a5 --- /dev/null +++ b/examples/technical/java/generics/MyRetention.java @@ -0,0 +1,13 @@ +import java.lang.annotation.Retention; + +class C { + Retention r; +} + +class D { } + +class E { + void m() { + D d = null; + } +} \ No newline at end of file diff --git a/src/col/vct/col/resolve/lang/Java.scala b/src/col/vct/col/resolve/lang/Java.scala index 5b10024d4a..deb9054de8 100644 --- a/src/col/vct/col/resolve/lang/Java.scala +++ b/src/col/vct/col/resolve/lang/Java.scala @@ -5,7 +5,7 @@ import hre.io.RWFile import hre.util.FuncTools import vct.col.ast.lang.java.JavaAnnotationEx import vct.col.ast.`type`.typeclass.TFloats -import vct.col.ast.{ADTFunction, ApplicableContract, AxiomaticDataType, BipPortType, Block, CType, EmptyProcess, Expr, JavaAnnotation, JavaAnnotationInterface, JavaClass, JavaClassDeclaration, JavaClassOrInterface, JavaConstructor, JavaFields, JavaFinal, JavaImport, JavaInterface, JavaMethod, JavaModifier, JavaName, JavaNamedType, JavaNamespace, JavaParam, JavaStatic, JavaTClass, JavaType, JavaVariableDeclaration, LiteralBag, LiteralMap, LiteralSeq, LiteralSet, Node, Null, OptNone, PVLType, TAnyClass, TArray, TAxiomatic, TBag, TBool, TBoundedInt, TChar, TClass, TEither, TEnum, TFloat, TFraction, TInt, TMap, TMatrix, TModel, TNotAValue, TNothing, TNull, TOption, TPointer, TProcess, TProverType, TRational, TRef, TResource, TSeq, TSet, TString, TTuple, TType, TUnion, TVar, TVoid, TZFraction, Type, UnitAccountedPredicate, Variable, Void} +import vct.col.ast.{ADTFunction, ApplicableContract, AxiomaticDataType, BipPortType, Block, CType, EmptyProcess, Expr, JavaAnnotation, JavaAnnotationInterface, JavaClass, JavaClassDeclaration, JavaClassOrInterface, JavaConstructor, JavaFields, JavaFinal, JavaImport, JavaInterface, JavaMethod, JavaModifier, JavaName, JavaNamedType, JavaNamespace, JavaParam, JavaStatic, JavaTClass, JavaType, JavaVariableDeclaration, JavaWildcard, LiteralBag, LiteralMap, LiteralSeq, LiteralSet, Node, Null, OptNone, PVLType, TAnyClass, TArray, TAxiomatic, TBag, TBool, TBoundedInt, TChar, TClass, TEither, TEnum, TFloat, TFraction, TInt, TMap, TMatrix, TModel, TNotAValue, TNothing, TNull, TOption, TPointer, TProcess, TProverType, TRational, TRef, TResource, TSeq, TSet, TString, TTuple, TType, TUnion, TVar, TVoid, TZFraction, Type, UnitAccountedPredicate, Variable, Void} import vct.col.origin._ import vct.col.ref.Ref import vct.col.resolve.ResolveTypes.JavaClassPathEntry @@ -20,6 +20,7 @@ import vct.result.VerificationError.{Unreachable, UserError} import java.io.File import java.lang.reflect.{Modifier, Parameter, TypeVariable} +import java.lang.reflect import java.nio.file.Path import scala.annotation.tailrec import scala.collection.mutable @@ -85,8 +86,27 @@ case object Java extends LazyLogging { private val currentlyLoading = mutable.Map[Seq[String], mutable.ArrayBuffer[JavaNamedType[_ <: Any]]]() - def lazyType[G](name: Seq[String], ctx: TypeResolutionContext[G]): JavaNamedType[G] = { - val result = JavaNamedType[G](name.map((_, None))) + def lazyType[G](t: reflect.Type, ctx: TypeResolutionContext[G]): Type[G] = + t match { + case t: reflect.ParameterizedType => + // Blatantly assume generic types are always classes applied to some type arguments + val name = t.getRawType.asInstanceOf[Class[_]].getName + // TODO: DiagnosticOrigin makes no sense here + lazyType(name, t.getActualTypeArguments.map(translateRuntimeType(_)(DiagnosticOrigin, ctx)).toIndexedSeq, ctx) + case t: java.lang.Class[_] => + val name = t.getName + lazyType(name, Seq(), ctx) + case _ => + // Don't call this method with primitive or array types + // These are handled in translateRuntimeType + throw Unreachable(???) + } + + def lazyType[G](name: String, typeArgs: Seq[Type[G]], ctx: TypeResolutionContext[G]): Type[G] = + lazyType(name.split('.').toIndexedSeq, typeArgs, ctx) + + def lazyType[G](name: Seq[String], typeArgs: Seq[Type[G]], ctx: TypeResolutionContext[G]): Type[G] = { + val result = JavaNamedType[G](name.init.map((_, None)) :+ (name.last, Some(typeArgs))) currentlyLoading.get(name) match { case Some(lazyQueue) => lazyQueue += result @@ -133,7 +153,7 @@ case object Java extends LazyLogging { Nil } - def translateRuntimeType[G](t: Class[_])(implicit o: Origin, ctx: TypeResolutionContext[G]): Type[G] = t match { + def translateRuntimeType[G](t: reflect.Type)(implicit o: Origin, ctx: TypeResolutionContext[G]): Type[G] = t match { case java.lang.Boolean.TYPE => TBool() case java.lang.Character.TYPE => TChar() case java.lang.Byte.TYPE => TInt() @@ -143,12 +163,13 @@ case object Java extends LazyLogging { case java.lang.Float.TYPE => float case java.lang.Double.TYPE => double case java.lang.Void.TYPE => TVoid() - case arr if arr.isArray => TArray(translateRuntimeType(arr.getComponentType)) - case cls => lazyType(cls.getName.split('.').toIndexedSeq, ctx) + case arr: reflect.GenericArrayType => TArray(translateRuntimeType(arr.getGenericComponentType)) + case t: reflect.WildcardType => JavaWildcard() + case t: reflect.Type => lazyType(t, ctx) } - def translateRuntimeParameter[G](param: Parameter)(implicit o: Origin, ctx: TypeResolutionContext[G]): JavaParam[G] = { - new JavaParam(Seq(), param.getName, translateRuntimeType(param.getType))(o.where(name = param.getName)) + def translateRuntimeParameter[G](param: Parameter, t: reflect.Type)(implicit o: Origin, ctx: TypeResolutionContext[G]): JavaParam[G] = { + new JavaParam(Seq(), param.getName, translateRuntimeType(t))(o.where(name = param.getName)) } def translateTypeParameter[G](param: TypeVariable[_])(implicit o: Origin, ctx: TypeResolutionContext[G]): Variable[G] = { @@ -165,7 +186,11 @@ case object Java extends LazyLogging { new JavaConstructor( modifiers = Nil, name = cls.getSimpleName, - parameters = cons.getParameters.toIndexedSeq.map(translateRuntimeParameter[G]), + parameters = { + val params = cons.getParameters.toIndexedSeq + val types = cons.getGenericParameterTypes.toIndexedSeq + params.zip(types).map { case (p, t) => translateRuntimeParameter[G](p, t) } + }, typeParameters = Nil, signals = Nil, body = Block(Nil), @@ -176,10 +201,14 @@ case object Java extends LazyLogging { val methods = cls.getMethods.map(method => { new JavaMethod( modifiers = if((method.getModifiers & Modifier.STATIC) != 0) Seq(JavaStatic[G]()) else Nil, - returnType = translateRuntimeType(method.getReturnType), + returnType = translateRuntimeType(method.getGenericReturnType), dims = 0, name = method.getName, - parameters = method.getParameters.toIndexedSeq.map(translateRuntimeParameter[G]), + parameters = { + val params = method.getParameters.toIndexedSeq + val types = method.getGenericParameterTypes.toIndexedSeq + params.zip(types).map { case (p, t) => translateRuntimeParameter[G](p, t) } + }, typeParameters = Nil, signals = Nil, body = None, @@ -203,7 +232,7 @@ case object Java extends LazyLogging { new JavaAnnotationInterface[G]( name = cls.getName.split('.').last, modifiers = Nil, - ext = cls.getInterfaces.toIndexedSeq.map(cls => lazyType(cls.getName.split('.').toIndexedSeq, ctx))(0), + ext = cls.getGenericInterfaces.toIndexedSeq.map(cls => lazyType(cls, ctx))(0), decls = fields.toIndexedSeq ++ cons.toIndexedSeq ++ methods.toIndexedSeq, )(o.where(name = cls.getName.split('.').last)) } else if(cls.isInterface) { @@ -211,7 +240,7 @@ case object Java extends LazyLogging { name = cls.getName.split('.').last, modifiers = Nil, typeParams = typeParameters, - ext = cls.getInterfaces.toIndexedSeq.map(cls => lazyType(cls.getName.split('.').toIndexedSeq, ctx)), + ext = cls.getGenericInterfaces.toIndexedSeq.map(cls => lazyType(cls, ctx)), decls = fields.toIndexedSeq ++ cons.toIndexedSeq ++ methods.toIndexedSeq, )(o.where(name = cls.getName.split('.').last)) } else { @@ -220,8 +249,8 @@ case object Java extends LazyLogging { modifiers = Nil, typeParams = typeParameters, intrinsicLockInvariant = `tt`, - ext = Option(cls.getSuperclass).map(cls => lazyType(cls.getName.split('.').toIndexedSeq, ctx)).getOrElse(JAVA_LANG_OBJECT), - imp = cls.getInterfaces.toIndexedSeq.map(cls => lazyType(cls.getName.split('.').toIndexedSeq, ctx)), + ext = Option(cls.getGenericSuperclass).map(cls => lazyType(cls, ctx)).getOrElse(JAVA_LANG_OBJECT), + imp = cls.getGenericInterfaces.toIndexedSeq.map(cls => lazyType(cls, ctx)), decls = fields.toIndexedSeq ++ cons.toIndexedSeq ++ methods.toIndexedSeq, )(o.where(name = cls.getName.split('.').last)) } diff --git a/src/main/vct/main/stages/Transformation.scala b/src/main/vct/main/stages/Transformation.scala index afff1fdbea..c9fbe0baa0 100644 --- a/src/main/vct/main/stages/Transformation.scala +++ b/src/main/vct/main/stages/Transformation.scala @@ -123,7 +123,6 @@ class Transformation case (key, action) => if (pass.key == key) action(result) } - val oldResult = result try { result = pass().dispatch(result) } catch { @@ -132,8 +131,6 @@ class Transformation throw c } - logger.info(s"Done: ${pass.key}") - result.tasks.map(_.program).flatMap(program => program.check.map(program -> _)) match { case Nil => // ok case errors => throw TransformationCheckError(pass, errors) diff --git a/src/rewrite/vct/rewrite/MonomorphizeClass.scala b/src/rewrite/vct/rewrite/MonomorphizeClass.scala index 10cd547448..47ac5cf57c 100644 --- a/src/rewrite/vct/rewrite/MonomorphizeClass.scala +++ b/src/rewrite/vct/rewrite/MonomorphizeClass.scala @@ -1,5 +1,6 @@ package vct.rewrite +import com.typesafe.scalalogging.LazyLogging import hre.util.ScopedStack import vct.col.ast._ import vct.col.ref.{LazyRef, Ref} @@ -14,7 +15,7 @@ case object MonomorphizeClass extends RewriterBuilder { override def desc: String = "Monomorphize generic classes" } -case class MonomorphizeClass[Pre <: Generation]() extends Rewriter[Pre] { +case class MonomorphizeClass[Pre <: Generation]() extends Rewriter[Pre] with LazyLogging { val currentSubstitutions: ScopedStack[Map[Variable[Pre], Type[Pre]]] = ScopedStack() type Key = (Class[Pre], Seq[Type[Pre]]) @@ -47,6 +48,10 @@ case class MonomorphizeClass[Pre <: Generation]() extends Rewriter[Pre] { keepBodies = keepBodies, substitutions = cls.typeArgs.map { v: Variable[Pre] => TVar(v.ref[Variable[Pre]]) }.zip(typeValues).toMap ) + // TODO: See below problem + // The entry in genericSucc is only updated after finishing rewriting the generic class. + // So if the generic class is mentioned inside, it will also be instantiated abstractly. + // So the "filler" entry in genericSucc is missing for now. How to resolve? genericSucc((key, cls)) = ctx.having(newCtx) { globalDeclarations.scope { classDeclarations.scope { @@ -63,6 +68,7 @@ case class MonomorphizeClass[Pre <: Generation]() extends Rewriter[Pre] { override def dispatch(decl: Declaration[Pre]): Unit = decl match { case cls: Class[Pre] if cls.typeArgs.nonEmpty => cls.typeArgs.foreach(_.drop()) + logger.info(s"Instantiating abstract type: ${cls.o.debugName()}") instantiate(cls, cls.typeArgs.map(v => v.t.asInstanceOf[TType[Pre]].t), true) case method: InstanceMethod[Pre] if ctx.nonEmpty => val newMethod: InstanceMethod[Post] = @@ -85,6 +91,7 @@ case class MonomorphizeClass[Pre <: Generation]() extends Rewriter[Pre] { case Some(ctx) => typeArgs.map(ctx.substitute.dispatch) case None => typeArgs } + logger.info(s"Instantiating concrete type: ${cls.o.debugName()}") instantiate(cls, typeValues, false) TClass[Post](genericSucc.ref[Post, Class[Post]](((cls, typeValues), cls)), Seq()) case (tvar @ TVar(_), Some(ctx)) => diff --git a/src/rewrite/vct/rewrite/lang/LangJavaToCol.scala b/src/rewrite/vct/rewrite/lang/LangJavaToCol.scala index 749eb355ce..9be6c355e8 100644 --- a/src/rewrite/vct/rewrite/lang/LangJavaToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangJavaToCol.scala @@ -511,6 +511,7 @@ case class LangJavaToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) extends } def classType(t: JavaTClass[Pre]): Type[Post] = t.ref.decl match { - case classOrInterface: JavaClassOrInterface[Pre] => TClass(javaInstanceClassSuccessor.ref(classOrInterface), Seq()) + case classOrInterface: JavaClassOrInterface[Pre] => + TClass(javaInstanceClassSuccessor.ref(classOrInterface), t.typeArgs.map(rw.dispatch)) } } From 97778a81b1208155903d6686e39ea741b1d2f433 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Tue, 27 Feb 2024 11:56:04 +0100 Subject: [PATCH 15/52] Add generic fields, make type instantiation a bit more centralized --- examples/concepts/generics/box.pvl | 1 + .../technical/veymont/genericEndpoints.pvl | 22 ++++++ src/col/vct/col/ast/Node.scala | 4 +- .../col/ast/expr/heap/read/DerefImpl.scala | 4 +- .../vct/col/ast/lang/pvl/PVLDerefImpl.scala | 5 +- .../col/ast/lang/pvl/PVLInvocationImpl.scala | 21 +++--- src/col/vct/col/ast/type/TClassImpl.scala | 13 +++- .../vct/col/typerules/CoercingRewriter.scala | 2 +- .../vct/parsers/transform/PVLToCol.scala | 3 +- .../vct/rewrite/MonomorphizeClass.scala | 68 ++++++++++++------- .../vct/rewrite/lang/LangVeyMontToCol.scala | 1 + 11 files changed, 104 insertions(+), 40 deletions(-) create mode 100644 examples/technical/veymont/genericEndpoints.pvl diff --git a/examples/concepts/generics/box.pvl b/examples/concepts/generics/box.pvl index efbe017b65..e8ed70f5fb 100644 --- a/examples/concepts/generics/box.pvl +++ b/examples/concepts/generics/box.pvl @@ -10,4 +10,5 @@ class Box { void m() { Box b = new Box(); int t = b.get(); + int t = b.t; } \ No newline at end of file diff --git a/examples/technical/veymont/genericEndpoints.pvl b/examples/technical/veymont/genericEndpoints.pvl new file mode 100644 index 0000000000..fb9eb60470 --- /dev/null +++ b/examples/technical/veymont/genericEndpoints.pvl @@ -0,0 +1,22 @@ +class Role { + T x; + int v; + + T fromInt(int x); + int toInt(T t); +} + +adt A { } + +adt B { } + +seq_program genericTest() { + endpoint a = Role(); + endpoint b = Role(); + + seq_run { + a.v := a.toInt(a.x); + communicate a.v -> b.v; + b.x := b.fromInt(b.x); + } +} \ No newline at end of file diff --git a/src/col/vct/col/ast/Node.scala b/src/col/vct/col/ast/Node.scala index 637cb5d81f..1d1ae1a88a 100644 --- a/src/col/vct/col/ast/Node.scala +++ b/src/col/vct/col/ast/Node.scala @@ -1315,7 +1315,7 @@ final case class TSeqProg[G](cls: Ref[G, SeqProg[G]])(implicit val o: Origin = D final case class TPVLSeqProg[G](cls: Ref[G, PVLSeqProg[G]])(implicit val o: Origin = DiagnosticOrigin) extends DeclaredType[G] with TPVLSeqProgImpl[G] final case class TEndpoint[G](cls: Ref[G, Endpoint[G]])(implicit val o: Origin = DiagnosticOrigin) extends DeclaredType[G] with TEndpointImpl[G] -final class PVLEndpoint[G](val name: String, val cls: Ref[G, Class[G]], val args: Seq[Expr[G]])(val blame: Blame[EndpointFailure])(implicit val o: Origin) extends ClassDeclaration[G] with PVLEndpointImpl[G] { +final class PVLEndpoint[G](val name: String, val cls: Ref[G, Class[G]], val typeArgs: Seq[Type[G]], val args: Seq[Expr[G]])(val blame: Blame[EndpointFailure])(implicit val o: Origin) extends ClassDeclaration[G] with PVLEndpointImpl[G] { var ref: Option[PVLConstructorTarget[G]] = None } final class PVLSeqProg[G](val name: String, val declarations: Seq[ClassDeclaration[G]], val contract: ApplicableContract[G], val args: Seq[Variable[G]])(val blame: Blame[SeqCallableFailure])(implicit val o: Origin) extends GlobalDeclaration[G] with PVLSeqProgImpl[G] with Declarator[G] @@ -1337,7 +1337,7 @@ case class PVLFamilyRange[G](family: String, binder: String, start: Expr[G], end case class PVLCommunicate[G](sender: PVLAccess[G], receiver: PVLAccess[G])(val blame: Blame[PVLCommunicateFailure])(implicit val o: Origin) extends Statement[G] with PVLCommunicateImpl[G] final case class PVLSeqAssign[G](receiver: Ref[G, PVLEndpoint[G]], field: Ref[G, InstanceField[G]], value: Expr[G])(val blame: Blame[PVLSeqAssignFailure])(implicit val o: Origin) extends Statement[G] with PVLSeqAssignImpl[G] -@family final class Endpoint[G](val cls: Ref[G, Class[G]], val constructor: Ref[G, Constructor[G]], val args: Seq[Expr[G]])(val blame: Blame[EndpointFailure])(implicit val o: Origin) extends Declaration[G] with EndpointImpl[G] +@family final class Endpoint[G](val cls: Ref[G, Class[G]], val typeArgs: Seq[Type[G]], val constructor: Ref[G, Constructor[G]], val args: Seq[Expr[G]])(val blame: Blame[EndpointFailure])(implicit val o: Origin) extends Declaration[G] with EndpointImpl[G] @scopes[Endpoint] final class SeqProg[G](val contract: ApplicableContract[G], val args : Seq[Variable[G]], val endpoints: Seq[Endpoint[G]], val run: SeqRun[G], val decls: Seq[ClassDeclaration[G]])(val blame: Blame[SeqCallableFailure])(implicit val o: Origin) extends GlobalDeclaration[G] with SeqProgImpl[G] @family final case class SeqRun[G](body: Statement[G], contract: ApplicableContract[G])(val blame: Blame[SeqCallableFailure])(implicit val o: Origin) extends NodeFamily[G] with SeqRunImpl[G] @family sealed trait Subject[G] extends NodeFamily[G] with SubjectImpl[G] diff --git a/src/col/vct/col/ast/expr/heap/read/DerefImpl.scala b/src/col/vct/col/ast/expr/heap/read/DerefImpl.scala index 3da8e7879c..d745a97b78 100644 --- a/src/col/vct/col/ast/expr/heap/read/DerefImpl.scala +++ b/src/col/vct/col/ast/expr/heap/read/DerefImpl.scala @@ -8,7 +8,9 @@ import vct.col.ref.Ref import vct.col.ast.ops.DerefOps trait DerefImpl[G] extends ExprImpl[G] with DerefOps[G] { this: Deref[G] => - override def t: Type[G] = ref.decl.t + override def t: Type[G] = + obj.t.asClass.map(_.instantiate(ref.decl.t)).getOrElse(ref.decl.t) + override def check(context: CheckContext[G]): Seq[CheckError] = Check.inOrder( super.check(context), diff --git a/src/col/vct/col/ast/lang/pvl/PVLDerefImpl.scala b/src/col/vct/col/ast/lang/pvl/PVLDerefImpl.scala index f74a290ac4..0ecea7a19d 100644 --- a/src/col/vct/col/ast/lang/pvl/PVLDerefImpl.scala +++ b/src/col/vct/col/ast/lang/pvl/PVLDerefImpl.scala @@ -1,6 +1,6 @@ package vct.col.ast.lang.pvl -import vct.col.ast.{TEnum, Enum, PVLDeref, TNotAValue, Type} +import vct.col.ast.{Enum, PVLDeref, TClass, TEnum, TNotAValue, Type} import vct.col.print.{Ctx, Doc, Precedence} import vct.col.resolve.ctx._ import vct.col.ast.ops.PVLDerefOps @@ -8,7 +8,8 @@ import vct.col.ast.ops.PVLDerefOps trait PVLDerefImpl[G] extends PVLDerefOps[G] { this: PVLDeref[G] => override lazy val t: Type[G] = ref.get match { case ref: RefModelField[G] => ref.decl.t - case ref: RefField[G] => ref.decl.t + case ref: RefField[G] => + obj.t.asClass.map(_.instantiate(ref.decl.t)).getOrElse(ref.decl.t) case RefEnumConstant(enum, _) => TEnum(enum.get.ref) case ref: BuiltinField[G] => ref.f(obj).t } diff --git a/src/col/vct/col/ast/lang/pvl/PVLInvocationImpl.scala b/src/col/vct/col/ast/lang/pvl/PVLInvocationImpl.scala index 44b251ac93..35a441abb7 100644 --- a/src/col/vct/col/ast/lang/pvl/PVLInvocationImpl.scala +++ b/src/col/vct/col/ast/lang/pvl/PVLInvocationImpl.scala @@ -1,6 +1,6 @@ package vct.col.ast.lang.pvl -import vct.col.ast.{PVLInvocation, TClass, TProcess, TResource, Type} +import vct.col.ast.{Applicable, PVLInvocation, TClass, TProcess, TResource, Type} import vct.col.print.{Ctx, Doc, DocUtil, Empty, Group, Text} import vct.col.resolve.ctx._ import vct.col.ast.ops.PVLInvocationOps @@ -11,17 +11,20 @@ trait PVLInvocationImpl[G] extends PVLInvocationOps[G] { this: PVLInvocation[G] case RefFunction(decl) => decl.returnType.particularize(decl.typeArgs.zip(typeArgs).toMap) case RefProcedure(decl) => decl.returnType case RefPredicate(_) => TResource() - case RefInstanceFunction(decl) => decl.returnType.particularize(decl.typeArgs.zip(typeArgs).toMap) + case RefInstanceFunction(decl) => + val returnType = decl.returnType.particularize(decl.typeArgs.zip(typeArgs).toMap) + obj.flatMap { e => + e.t.asClass.map { tcls => + tcls.instantiate(returnType) + } + }.getOrElse(returnType) case RefInstanceMethod(decl) => val returnType = decl.returnType.particularize(decl.typeArgs.zip(typeArgs).toMap) - obj match { - case None => returnType - case Some(e) => e.t match { - case TClass(Ref(cls), typeValues) if typeValues.nonEmpty => - returnType.particularize(cls.typeArgs.zip(typeValues).toMap) - case _ => returnType + obj.flatMap { e => + e.t.asClass.map { tcls => + tcls.instantiate(returnType) } - } + }.getOrElse(returnType) case RefInstancePredicate(_) => TResource() case RefADTFunction(decl) => decl.returnType case RefModelProcess(_) => TProcess() diff --git a/src/col/vct/col/ast/type/TClassImpl.scala b/src/col/vct/col/ast/type/TClassImpl.scala index 5824223ef6..6e528ed4b7 100644 --- a/src/col/vct/col/ast/type/TClassImpl.scala +++ b/src/col/vct/col/ast/type/TClassImpl.scala @@ -1,8 +1,10 @@ package vct.col.ast.`type` -import vct.col.ast.{Class, TClass, Type, Variable} +import vct.col.ast.{Applicable, Class, ClassDeclaration, Constructor, ContractApplicable, InstanceField, InstanceFunction, InstanceMethod, InstanceOperatorFunction, InstanceOperatorMethod, TClass, Type, Variable} import vct.col.print.{Ctx, Doc, Empty, Group, Text} import vct.col.ast.ops.TClassOps +import vct.col.ref.Ref +import vct.result.VerificationError.Unreachable trait TClassImpl[G] extends TClassOps[G] { this: TClass[G] => def transSupportArrows: Seq[(Class[G], Class[G])] = cls.decl.transSupportArrows @@ -11,4 +13,13 @@ trait TClassImpl[G] extends TClassOps[G] { this: TClass[G] => if (typeArgs.nonEmpty) Text("<") <> Doc.args(typeArgs) <> ">" else Empty) def typeEnv: Map[Variable[G], Type[G]] = cls.decl.typeArgs.zip(typeArgs).toMap + + def instantiate(t: Type[G]): Type[G] = + this match { + case TClass(Ref(cls), typeArgs) if typeArgs.nonEmpty => + t.particularize(cls.typeArgs.zip(typeArgs).toMap) + case _ => t + } + + def fieldType(decl: InstanceField[G]): Type[G] = instantiate(decl.t) } \ No newline at end of file diff --git a/src/col/vct/col/typerules/CoercingRewriter.scala b/src/col/vct/col/typerules/CoercingRewriter.scala index ab11aa614c..acf310584b 100644 --- a/src/col/vct/col/typerules/CoercingRewriter.scala +++ b/src/col/vct/col/typerules/CoercingRewriter.scala @@ -1677,7 +1677,7 @@ abstract class CoercingRewriter[Pre <: Generation]() extends BaseCoercingRewrite JavaVariableDeclaration(name, dims, Some(coerce(v, FuncTools.repeat[Type[Pre]](TArray(_), dims, declaration.t)))) }) case seqProg: SeqProg[Pre] => seqProg - case endpoint: Endpoint[Pre] => new Endpoint(endpoint.cls, endpoint.constructor, endpoint.args)(endpoint.blame) + case endpoint: Endpoint[Pre] => new Endpoint(endpoint.cls, endpoint.typeArgs, endpoint.constructor, endpoint.args)(endpoint.blame) case bc: BipConstructor[Pre] => new BipConstructor(bc.args, bc.body, bc.requires)(bc.blame) case bc: BipComponent[Pre] => new BipComponent(bc.fqn, res(bc.invariant), bc.initial) diff --git a/src/parsers/vct/parsers/transform/PVLToCol.scala b/src/parsers/vct/parsers/transform/PVLToCol.scala index 73b67c2421..ae783e6b43 100644 --- a/src/parsers/vct/parsers/transform/PVLToCol.scala +++ b/src/parsers/vct/parsers/transform/PVLToCol.scala @@ -51,10 +51,11 @@ case class PVLToCol[G](override val baseOrigin: Origin, convert(body), contract.consumeApplicableContract(blame(decl)) )(blame(decl))(origin(decl).where(name = "run"))) - case PvlEndpoint(_, name, _, ClassType0(endpointType, None), _, args, _, _) => + case PvlEndpoint(_, name, _, ClassType0(endpointType, typeArgs), _, args, _, _) => new PVLEndpoint( convert(name), new UnresolvedRef[G, Class[G]](convert(endpointType)), + typeArgs.map(convert(_)).getOrElse(Seq()), args.map(convert(_)).getOrElse(Nil))(blame(decl))(origin(decl).sourceName(convert(name)) ) case PvlEndpoint(_, name, _, t@ClassType0(_, Some(_)), _, args, _, _) => ??(t) diff --git a/src/rewrite/vct/rewrite/MonomorphizeClass.scala b/src/rewrite/vct/rewrite/MonomorphizeClass.scala index 47ac5cf57c..63f76b8047 100644 --- a/src/rewrite/vct/rewrite/MonomorphizeClass.scala +++ b/src/rewrite/vct/rewrite/MonomorphizeClass.scala @@ -34,41 +34,45 @@ case class MonomorphizeClass[Pre <: Generation]() extends Rewriter[Pre] with Laz def keepBodies: Boolean = ctx.topOption.map { ctx => ctx.keepBodies }.getOrElse(true) + val knownInstantiations: mutable.LinkedHashSet[Key] = mutable.LinkedHashSet() // key: generically instantiated type in pre val genericSucc: SuccessionMap[(Key, Declaration[Pre]), Declaration[Post]] = SuccessionMap() def instantiate(cls: Class[Pre], typeValues: Seq[Type[Pre]], keepBodies: Boolean): Unit = { + /* Known limitation: the knownInstantations set does not take into account how a class was instantiated. + A class can be instantiated both abstractly (without method bodies) and concretely (with method bodies) + for the same sequence of type arguments, maybe. If that's the case, the knownInstantiations should take the + "mode" of instantiation into account. But as this difference is at the moment also not encoded in genericSucc, + it's not taken into account for knowInstantiations either + */ val key = (cls, typeValues) - genericSucc.get((key, cls)) match { - case Some(_) => // Done - case None => - val newCtx = InstantiationContext( - cls, - typeValues, - keepBodies = keepBodies, - substitutions = cls.typeArgs.map { v: Variable[Pre] => TVar(v.ref[Variable[Pre]]) }.zip(typeValues).toMap - ) - // TODO: See below problem - // The entry in genericSucc is only updated after finishing rewriting the generic class. - // So if the generic class is mentioned inside, it will also be instantiated abstractly. - // So the "filler" entry in genericSucc is missing for now. How to resolve? - genericSucc((key, cls)) = ctx.having(newCtx) { - globalDeclarations.scope { - classDeclarations.scope { - variables.scope { - allScopes.anyDeclare(allScopes.anySucceedOnly(cls, cls.rewrite(typeArgs = Seq()))) - } - } + if (knownInstantiations.contains(key)) { + logger.info(s"Class ${cls.o.debugName()} with type args $typeValues is already instantiated, so skipping instantiation") + return + } + val mode = if (keepBodies) { "concretely" } else { "abstractly" } + logger.info(s"Instantiating class ${cls.o.debugName()} $mode, args: $typeValues") + val newCtx = InstantiationContext( + cls, + typeValues, + keepBodies = keepBodies, + substitutions = cls.typeArgs.map { v: Variable[Pre] => TVar(v.ref[Variable[Pre]]) }.zip(typeValues).toMap + ) + knownInstantiations.add(key) + genericSucc((key, cls)) = ctx.having(newCtx) { + globalDeclarations.scope { + classDeclarations.scope { + variables.scope { + allScopes.anyDeclare(allScopes.anySucceedOnly(cls, cls.rewrite(typeArgs = Seq()))) } } + } } } - override def dispatch(decl: Declaration[Pre]): Unit = decl match { case cls: Class[Pre] if cls.typeArgs.nonEmpty => cls.typeArgs.foreach(_.drop()) - logger.info(s"Instantiating abstract type: ${cls.o.debugName()}") instantiate(cls, cls.typeArgs.map(v => v.t.asInstanceOf[TType[Pre]].t), true) case method: InstanceMethod[Pre] if ctx.nonEmpty => val newMethod: InstanceMethod[Post] = @@ -81,6 +85,11 @@ case class MonomorphizeClass[Pre <: Generation]() extends Rewriter[Pre] with Laz genericSucc((ctx.top.key, cons)) = newCons classDeclarations.declare(newCons) classDeclarations.succeedOnly(cons, newCons) + case field: InstanceField[Pre] if ctx.nonEmpty => + val newField = field.rewrite() + genericSucc((ctx.top.key, field)) = newField + classDeclarations.declare(newField) + classDeclarations.succeedOnly(field, newField) case other => allScopes.anySucceed(other, other.rewriteDefault()) } @@ -91,7 +100,6 @@ case class MonomorphizeClass[Pre <: Generation]() extends Rewriter[Pre] with Laz case Some(ctx) => typeArgs.map(ctx.substitute.dispatch) case None => typeArgs } - logger.info(s"Instantiating concrete type: ${cls.o.debugName()}") instantiate(cls, typeValues, false) TClass[Post](genericSucc.ref[Post, Class[Post]](((cls, typeValues), cls)), Seq()) case (tvar @ TVar(_), Some(ctx)) => @@ -119,4 +127,18 @@ case class MonomorphizeClass[Pre <: Generation]() extends Rewriter[Pre] with Laz } case other => other.rewriteDefault() } + + override def dispatch(expr: Expr[Pre]): Expr[Post] = expr match { + case deref @ Deref(obj, Ref(field)) => + obj.t match { + case TClass(Ref(cls), typeArgs) if typeArgs.nonEmpty => + val typeArgs1 = ctx.topOption.map(_.evalTypes(typeArgs)).getOrElse(typeArgs) + instantiate(cls, typeArgs1, false) + deref.rewrite( + ref = genericSucc.ref[Post, InstanceField[Post]](((cls, typeArgs1), field)) + ) + case _ => expr.rewriteDefault() + } + case _ => expr.rewriteDefault() + } } diff --git a/src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala b/src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala index afc3257085..fcdb92016a 100644 --- a/src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangVeyMontToCol.scala @@ -56,6 +56,7 @@ case class LangVeyMontToCol[Pre <: Generation](rw: LangSpecificToCol[Pre], allow def rewriteEndpoint(endpoint: PVLEndpoint[Pre]): Unit = endpointSucc(endpoint) = rw.endpoints.declare(new Endpoint( rw.succ[Class[Post]](endpoint.cls.decl), + endpoint.typeArgs.map(rw.dispatch), rw.pvl.constructorSucc(endpoint.ref.get), endpoint.args.map(rw.dispatch) )(endpoint.blame)(endpoint.o)) From e8814b1e7e5fad4afe1b7622529d659b46f56a19 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Tue, 27 Feb 2024 12:04:47 +0100 Subject: [PATCH 16/52] Generic arguments need to be propagated for endpoints as well --- examples/technical/veymont/genericEndpoints.pvl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/technical/veymont/genericEndpoints.pvl b/examples/technical/veymont/genericEndpoints.pvl index fb9eb60470..01de471184 100644 --- a/examples/technical/veymont/genericEndpoints.pvl +++ b/examples/technical/veymont/genericEndpoints.pvl @@ -1,8 +1,8 @@ class Role { - T x; - int v; + T t; + int i; - T fromInt(int x); + T fromInt(int i); int toInt(T t); } @@ -15,8 +15,8 @@ seq_program genericTest() { endpoint b = Role(); seq_run { - a.v := a.toInt(a.x); - communicate a.v -> b.v; - b.x := b.fromInt(b.x); + a.i := a.toInt(a.t); + communicate a.i -> b.i; + b.t := b.fromInt(b.i); } } \ No newline at end of file From 3b46a9570c5426a08f5ed1189a92b3ffbc973789 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Tue, 27 Feb 2024 12:55:27 +0100 Subject: [PATCH 17/52] generic endpoints work --- .../technical/veymont/genericEndpoints.pvl | 4 ++-- .../declaration/singular/EndpointImpl.scala | 2 +- .../ast/expr/apply/MethodInvocationImpl.scala | 10 +++------- .../vct/col/typerules/CoercingRewriter.scala | 20 +++++++++++-------- src/main/vct/main/stages/Transformation.scala | 8 ++++---- .../vct/rewrite/MonomorphizeClass.scala | 20 ++++++++++++++++--- .../vct/rewrite/veymont/EncodeSeqProg.scala | 5 +++-- 7 files changed, 42 insertions(+), 27 deletions(-) diff --git a/examples/technical/veymont/genericEndpoints.pvl b/examples/technical/veymont/genericEndpoints.pvl index 01de471184..874bf54447 100644 --- a/examples/technical/veymont/genericEndpoints.pvl +++ b/examples/technical/veymont/genericEndpoints.pvl @@ -6,9 +6,9 @@ class Role { int toInt(T t); } -adt A { } +class A { } -adt B { } +class B { } seq_program genericTest() { endpoint a = Role(); diff --git a/src/col/vct/col/ast/declaration/singular/EndpointImpl.scala b/src/col/vct/col/ast/declaration/singular/EndpointImpl.scala index 7e813e6aa5..a1bf922e81 100644 --- a/src/col/vct/col/ast/declaration/singular/EndpointImpl.scala +++ b/src/col/vct/col/ast/declaration/singular/EndpointImpl.scala @@ -8,5 +8,5 @@ trait EndpointImpl[G] extends EndpointOps[G] with EndpointFamilyOps[G] { this: E override def layout(implicit ctx: Ctx): Doc = Group(Text("endpoint") <+> ctx.name(this) <+> "=" <>> { Group(t.show <> "(" <> Doc.args(args) <> ");") }) - def t: Type[G] = TClass(cls, Seq()) + def t: TClass[G] = TClass(cls, typeArgs) } diff --git a/src/col/vct/col/ast/expr/apply/MethodInvocationImpl.scala b/src/col/vct/col/ast/expr/apply/MethodInvocationImpl.scala index 981120447e..e242bc9434 100644 --- a/src/col/vct/col/ast/expr/apply/MethodInvocationImpl.scala +++ b/src/col/vct/col/ast/expr/apply/MethodInvocationImpl.scala @@ -17,12 +17,8 @@ trait MethodInvocationImpl[G] extends MethodInvocationOps[G] with InvocationImpl ) <> Doc.args(args ++ outArgs) <> ")" <> DocUtil.givenYields(givenMap, yields) ) - override def t: Type[G] = { - val returnType = super.t - obj.t match { - case TClass(Ref(cls), typeArgs) if typeArgs.nonEmpty => - returnType.particularize(cls.typeArgs.zip(typeArgs).toMap) - case _ => returnType - } + override def t: Type[G] = obj.t match { + case t: TClass[G] => t.instantiate(super.t) + case _ => super.t } } diff --git a/src/col/vct/col/typerules/CoercingRewriter.scala b/src/col/vct/col/typerules/CoercingRewriter.scala index acf310584b..005bc14ffa 100644 --- a/src/col/vct/col/typerules/CoercingRewriter.scala +++ b/src/col/vct/col/typerules/CoercingRewriter.scala @@ -295,9 +295,11 @@ abstract class CoercingRewriter[Pre <: Generation]() extends BaseCoercingRewrite case (value, arg) => coerce(value, arg.t) } - def coerceArgs(args: Seq[Expr[Pre]], app: ContractApplicable[Pre], tArgs: Seq[Type[Pre]], canCDemote: Boolean = false): Seq[Expr[Pre]] = + def coerceArgs(args: Seq[Expr[Pre]], app: ContractApplicable[Pre], tArgs: Seq[Type[Pre]], tCls: Option[TClass[Pre]] = None, canCDemote: Boolean = false): Seq[Expr[Pre]] = args.zip(app.args).map { - case (value, arg) => coerce(value, arg.t.particularize(app.typeArgs.zip(tArgs).toMap), canCDemote) + case (value, arg) => + val t = arg.t.particularize(app.typeArgs.zip(tArgs).toMap) + coerce(value, tCls.map(_.instantiate(t)).getOrElse(t), canCDemote) } def coerceGiven(givenMap: Seq[(Ref[Pre, Variable[Pre]], Expr[Pre])], canCDemote: Boolean = false): Seq[(Ref[Pre, Variable[Pre]], Expr[Pre])] = @@ -778,7 +780,8 @@ abstract class CoercingRewriter[Pre <: Generation]() extends BaseCoercingRewrite case StringConcat(left, right) => StringConcat(string(left), string(right)) case inv @ ConstructorInvocation(ref, classTypeArgs, args, outArgs, typeArgs, givenMap, yields) => - ConstructorInvocation(ref, classTypeArgs, coerceArgs(args, ref.decl, typeArgs, canCDemote = true), outArgs, typeArgs, coerceGiven(givenMap, canCDemote = true), coerceYields(yields, args.head))(inv.blame) + val tCls = TClass(ref.decl.cls, classTypeArgs) + ConstructorInvocation(ref, classTypeArgs, coerceArgs(args, ref.decl, typeArgs, Some(tCls), canCDemote = true), outArgs, typeArgs, coerceGiven(givenMap, canCDemote = true), coerceYields(yields, args.head))(inv.blame) case acc @ CStructAccess(struct, field) => CStructAccess(struct, field)(acc.blame) case deref @ CStructDeref(struct, field) => @@ -863,7 +866,7 @@ abstract class CoercingRewriter[Pre <: Generation]() extends BaseCoercingRewrite case InlinePattern(inner, parent, group) => InlinePattern(inner, parent, group) case inv @ InstanceFunctionInvocation(obj, ref, args, typeArgs, givenMap, yields) => - InstanceFunctionInvocation(cls(obj), ref, coerceArgs(args, ref.decl, typeArgs, canCDemote=true), typeArgs, coerceGiven(givenMap, canCDemote=true), coerceYields(yields, inv))(inv.blame) + InstanceFunctionInvocation(cls(obj), ref, coerceArgs(args, ref.decl, typeArgs, obj.t.asClass, canCDemote=true), typeArgs, coerceGiven(givenMap, canCDemote=true), coerceYields(yields, inv))(inv.blame) case InstanceOf(value, typeValue) => InstanceOf(value, typeValue) case InstancePredicateApply(obj, ref, args, perm) => @@ -971,7 +974,7 @@ abstract class CoercingRewriter[Pre <: Generation]() extends BaseCoercingRewrite case MatrixSum(indices, mat) => MatrixSum(coerce(indices, TSeq[Pre](TInt())), coerce(mat, TSeq[Pre](TRational()))) case inv @ MethodInvocation(obj, ref, args, outArgs, typeArgs, givenMap, yields) => - MethodInvocation(obj, ref, coerceArgs(args, ref.decl, typeArgs, canCDemote=true), outArgs, typeArgs, coerceGiven(givenMap, canCDemote=true), coerceYields(yields, inv))(inv.blame) + MethodInvocation(obj, ref, coerceArgs(args, ref.decl, typeArgs, obj.t.asClass, canCDemote=true), outArgs, typeArgs, coerceGiven(givenMap, canCDemote=true), coerceYields(yields, inv))(inv.blame) case Minus(left, right) => firstOk(e, s"Expected both operands to be numeric, but got ${left.t} and ${right.t}.", Minus(int(left), int(right)), @@ -1495,11 +1498,12 @@ abstract class CoercingRewriter[Pre <: Generation]() extends BaseCoercingRewrite case Inhale(assn) => Inhale(res(assn)) case Instantiate(cls, dest) => Instantiate(cls, dest) case inv @ InvokeConstructor(ref, classTypeArgs, out, args, outArgs, typeArgs, givenMap, yields) => - InvokeConstructor(ref, classTypeArgs, out, coerceArgs(args, ref.decl, typeArgs, canCDemote = true), outArgs, typeArgs, coerceGiven(givenMap, canCDemote = true), coerceYields(yields, args.head))(inv.blame) + val cls = TClass(ref.decl.cls, classTypeArgs) + InvokeConstructor(ref, classTypeArgs, out, coerceArgs(args, ref.decl, typeArgs, Some(cls), canCDemote = true), outArgs, typeArgs, coerceGiven(givenMap, canCDemote = true), coerceYields(yields, args.head))(inv.blame) case inv @ InvokeProcedure(ref, args, outArgs, typeArgs, givenMap, yields) => InvokeProcedure(ref, coerceArgs(args, ref.decl, typeArgs, canCDemote=true), outArgs, typeArgs, coerceGiven(givenMap,canCDemote=true), coerceYields(yields, args.head))(inv.blame) case inv @ InvokeMethod(obj, ref, args, outArgs, typeArgs, givenMap, yields) => - InvokeMethod(cls(obj), ref, coerceArgs(args, ref.decl, typeArgs,canCDemote=true), outArgs, typeArgs, coerceGiven(givenMap,canCDemote=true), coerceYields(yields, args.head))(inv.blame) + InvokeMethod(cls(obj), ref, coerceArgs(args, ref.decl, typeArgs, obj.t.asClass, canCDemote=true), outArgs, typeArgs, coerceGiven(givenMap,canCDemote=true), coerceYields(yields, args.head))(inv.blame) case JavaLocalDeclarationStatement(decl) => JavaLocalDeclarationStatement(decl) case j @ Join(obj) => Join(cls(obj))(j.blame) case Label(decl, stat) => Label(decl, stat) @@ -1547,7 +1551,7 @@ abstract class CoercingRewriter[Pre <: Generation]() extends BaseCoercingRewrite throw err } case a @ SeqAssign(r, f, v) => - try { SeqAssign(r, f, coerce(v, f.decl.t))(a.blame) } catch { + try { SeqAssign(r, f, coerce(v, r.decl.t.instantiate(f.decl.t)))(a.blame) } catch { case err: Incoercible => println(err.text) throw err diff --git a/src/main/vct/main/stages/Transformation.scala b/src/main/vct/main/stages/Transformation.scala index c9fbe0baa0..ad73d312c5 100644 --- a/src/main/vct/main/stages/Transformation.scala +++ b/src/main/vct/main/stages/Transformation.scala @@ -131,15 +131,15 @@ class Transformation throw c } + onAfterPassKey.foreach { + case (key, action) => if (pass.key == key) action(result) + } + result.tasks.map(_.program).flatMap(program => program.check.map(program -> _)) match { case Nil => // ok case errors => throw TransformationCheckError(pass, errors) } - onAfterPassKey.foreach { - case (key, action) => if (pass.key == key) action(result) - } - result = PrettifyBlocks().dispatch(result) } diff --git a/src/rewrite/vct/rewrite/MonomorphizeClass.scala b/src/rewrite/vct/rewrite/MonomorphizeClass.scala index 63f76b8047..98e16d1e96 100644 --- a/src/rewrite/vct/rewrite/MonomorphizeClass.scala +++ b/src/rewrite/vct/rewrite/MonomorphizeClass.scala @@ -47,11 +47,11 @@ case class MonomorphizeClass[Pre <: Generation]() extends Rewriter[Pre] with Laz */ val key = (cls, typeValues) if (knownInstantiations.contains(key)) { - logger.info(s"Class ${cls.o.debugName()} with type args $typeValues is already instantiated, so skipping instantiation") + logger.debug(s"Class ${cls.o.debugName()} with type args $typeValues is already instantiated, so skipping instantiation") return } val mode = if (keepBodies) { "concretely" } else { "abstractly" } - logger.info(s"Instantiating class ${cls.o.debugName()} $mode, args: $typeValues") + logger.debug(s"Instantiating class ${cls.o.debugName()} $mode, args: $typeValues") val newCtx = InstantiationContext( cls, typeValues, @@ -128,6 +128,20 @@ case class MonomorphizeClass[Pre <: Generation]() extends Rewriter[Pre] with Laz case other => other.rewriteDefault() } + override def dispatch(loc: Location[Pre]): Location[Post] = loc match { + case loc @ FieldLocation(obj, Ref(field)) => + obj.t match { + case TClass(Ref(cls), typeArgs) if typeArgs.nonEmpty => + val typeArgs1 = ctx.topOption.map(_.evalTypes(typeArgs)).getOrElse(typeArgs) + instantiate(cls, typeArgs1, false) + loc.rewrite( + field = genericSucc.ref[Post, InstanceField[Post]](((cls, typeArgs1), field)) + ) + case _ => loc.rewriteDefault() + } + case _ => loc.rewriteDefault() + } + override def dispatch(expr: Expr[Pre]): Expr[Post] = expr match { case deref @ Deref(obj, Ref(field)) => obj.t match { @@ -137,7 +151,7 @@ case class MonomorphizeClass[Pre <: Generation]() extends Rewriter[Pre] with Laz deref.rewrite( ref = genericSucc.ref[Post, InstanceField[Post]](((cls, typeArgs1), field)) ) - case _ => expr.rewriteDefault() + case _ => deref.rewriteDefault() } case _ => expr.rewriteDefault() } diff --git a/src/rewrite/vct/rewrite/veymont/EncodeSeqProg.scala b/src/rewrite/vct/rewrite/veymont/EncodeSeqProg.scala index 0867b2a4c4..5f006e48ad 100644 --- a/src/rewrite/vct/rewrite/veymont/EncodeSeqProg.scala +++ b/src/rewrite/vct/rewrite/veymont/EncodeSeqProg.scala @@ -102,7 +102,7 @@ case class EncodeSeqProg[Pre <: Generation]() extends Rewriter[Pre] with LazyLog implicit val o = prog.o prog.endpoints.foreach(_.drop()) for (endpoint <- prog.endpoints) { - endpointSucc((mode, endpoint)) = new Variable(TClass(succ[Class[Post]](endpoint.cls.decl), Seq()))(endpoint.o) + endpointSucc((mode, endpoint)) = new Variable(dispatch(endpoint.t))(endpoint.o) } // Maintain successor for seq_prog argument variables manually, as two contexts are maintained @@ -116,6 +116,7 @@ case class EncodeSeqProg[Pre <: Generation]() extends Rewriter[Pre] with LazyLog val endpointsInit = prog.endpoints.map { endpoint => Assign(Local[Post](endpointSucc((mode, endpoint)).ref), constructorInvocation[Post]( + classTypeArgs = endpoint.typeArgs.map(dispatch), ref = succ(endpoint.constructor.decl), args = endpoint.args.map(dispatch), blame = InvocationFailureToEndpointFailure(endpoint) @@ -172,7 +173,7 @@ case class EncodeSeqProg[Pre <: Generation]() extends Rewriter[Pre] with LazyLog currentRun.having(run) { for (endpoint <- prog.endpoints) { - endpointSucc((mode, endpoint)) = new Variable(TClass(succ[Class[Post]](endpoint.cls.decl), Seq()))(endpoint.o) + endpointSucc((mode, endpoint)) = new Variable(dispatch(endpoint.t))(endpoint.o) } for (arg <- prog.args) { From c3f9b18ee6c1362d66e643459fae8b625c0a85f0 Mon Sep 17 00:00:00 2001 From: Lars Date: Tue, 5 Mar 2024 17:08:33 +0100 Subject: [PATCH 18/52] Add challenge with test that uses no-infer-backend --- .../verifythis/2019/challenge3_complete.pvl | 594 ++++++++++++++++++ .../integration/examples/VerifyThisSpec.scala | 1 + .../test/integration/helper/VercorsSpec.scala | 4 + 3 files changed, 599 insertions(+) create mode 100644 examples/verifythis/2019/challenge3_complete.pvl diff --git a/examples/verifythis/2019/challenge3_complete.pvl b/examples/verifythis/2019/challenge3_complete.pvl new file mode 100644 index 0000000000..913f23d863 --- /dev/null +++ b/examples/verifythis/2019/challenge3_complete.pvl @@ -0,0 +1,594 @@ +// -*- tab-width:2 ; indent-tabs-mode:nil -*- +//:: cases VerifyThis19ThreeComplete +//:: tools silicon +//:: suite medium +//:: option --no-infer-heap-context-into-frame +//:: verdict Pass + + context N >= 0 && M >= 0; + + context m_perm(m, N, M, 1\2); + context x_perm(x, N, 1\2); + context boundAndSorted(m, N, M); + + given seq, int>> m_; + context |m_| == m.length && (\forall int i=0..m.length; {:m_[i]:} == m[i]); + given seq x_; + context |x_| == x.length && (\forall int i=0..x.length; {:x_[i]:} == x[i]); + + // The full matrix + given seq> m_full; + requires |m_full| == N; + requires (\forall int i=0..N; {:|m_full[i]|:} == M); + + requires (\forall int x=0..M, int y=0..N; {:1:lookup(x, y, m_, N, M).fst:} == {:m_full[y][x]:}); + requires (\forall int i=0..|m_|; {:m_full[r(m_[i])][c(m_[i])]:} == v(m_[i])); + // I think the below condition is not per se needed. The sparse matrix is in theory allowed members that are zero. + // Furthermore, I do not use this anywhere in my proof + // requires (\forall int i=0..|m_|, int x=0..M, int y=0..N; + // {:m_full[y][x]:} == 0 ==> !({:r(m_[i]):} == y && {:c(m_[i]):} == x)); + + ensures \array(\result, M); + ensures (\forall* int i=0..M; Perm({:\result[i]:}, write)); + ensures (\forall int i=0..M; {:SparseXxMcol(m_, x_, i, N, M):} == \result[i]); + ensures (\forall int i=0..M; {:XxMcol(m_full, x_, i, N, M):} == \result[i]); + + decreases; +int[] parallel_sum(tuple, int>[] m, int[] x, int N, int M){ + + int len = m.length; + Ghost g = newGhost(len); + // Ghost g = new Ghost(); + // g.written_ = create(len, false); + // g.written = new bool[len]; + + // int[] y = new int[M]; + int [] y = newInt(M); + lemma_sum_init_zero_all(m_, x_, g.written_, N, M); + + + invariant sumInv( + (\forall* int j=0..y.length; Perm({:y[j]:}, write)) ** Perm(g.written_, write) ** |g.written_| == len + ** (\forall int i=0..M; {:SparseXxMcolPartial(m_, x_, g.written_, i, N, M):} == y[i]) + ** Perm(g.written, 1\2) ** \array(g.written, len) + ** (\forall* int i=0..len; Perm({:g.written[i]:}, 1\2)) + ** (\forall int i=0..len; g.written[i] == {:g.written_[i]:}) + ) + { + par (int tid = 0 .. len) + context len == m.length; + context tid >= 0 && tid < len; + context y != null && y.length == M; + context m_perm(m, N, M, 1\(2*len)); + context x_perm(x, N, 1\(2*len)); + context boundAndSorted(m, N, M); + context Perm(g.written, 1\(2*len)) ** \array(g.written, len) ** Perm(g.written[tid], 1\2); + context |m_| == m.length && (\forall int i=0..m.length; {:m_[i]:} == m[i]); + context |x_| == x.length && (\forall int i=0..x.length; {:x_[i]:} == x[i]); + requires !g.written[tid]; + ensures g.written[tid]; + { + int r = r(m[tid]); + int c = c(m[tid]); + int v = v(m[tid]); + int add = x[r]*v; + + atomic(sumInv){ + lemma_sum_upd(m_, x_, g.written_, N, M, tid); + assert (\forall int i=0..M; i!=c(m[tid]); {:SparseXxMcolPartial(m_, x_, g.written_, i, N, M):} == + SparseXxMcolPartial(m_, x_, update(g.written_, tid, true), i, N, M)); + + y[c] = y[c] + add; + g.written_ = update(g.written_, tid, true); + g.written[tid] = true; + } + } + } + lemma_sum_final_all(m_, x_, g.written_, N, M); + + lemma_sparse_full(m_, m_full, x_, N, M); + return y; +} + +//----------------------------------------------- +// Definitions for the implementation +//----------------------------------------------- +inline resource m_perm(tuple, int>[] m, int N, int M, rational r) = + (m != null && m.length < M * N) ** (\forall* int i; 0<=i && i, int>[] m, int N, int M) = + // The matrix is bound + (\forall int i; 0<=i && i update(seq xs, int j, bool v) = + 0 < j ? xs.head :: update(xs.tail, j - 1, v) : v :: xs.tail; + + requires N >= 0; + ensures |\result| == N; + ensures (\forall int i=0..N; {:\result[i]:} == init); + decreases N; +pure seq create(int N, bool init) = N > 0 ? init :: create(N-1, init) : [t:bool]; + + context_everywhere M >= 0; + context_everywhere |mask| == |m|; + context_everywhere |x| == N; + context_everywhere (\forall int i=0..|mask|; {:mask[i]:} == false); + context_everywhere boundAndSorted(m, N, M); + + ensures (\forall int i=0..M; {:SparseXxMcolPartial(m, x, mask, i, N, M):} == 0); + + decreases; +void lemma_sum_init_zero_all(seq, int>> m, seq x, seq mask, int N, int M){ + loop_invariant 0 <= j && j <= M; + loop_invariant (\forall int i; 0 <= i && i < j; {:SparseXxMcolPartial(m, x, mask, i, N, M):} == 0); + decreases M-j; + for(int j=0; j= 0 && col < M; + + ensures SparseXxMcolPartial(m, x, mask, col, N, M) == 0; + + decreases |m|; +void lemma_sum_init_zero(seq, int>> m, seq x, seq mask, int col, int N, int M){ + if(|m| > 0){ + lemma_sum_init_zero(m.tail, x, mask.tail, col, N, M); + } +} + + context_everywhere 0 <= tid && tid < |m|; + context_everywhere |m| == |mask|; + context_everywhere !mask[tid]; + context_everywhere boundAndSorted(m, N, M); + context_everywhere |x| == N; + ensures (\forall int i; 0 <= i && i < M && i != c(m[tid]); + {:1:SparseXxMcolPartial(m, x, update(mask, tid, true), i, N, M):} == + {:SparseXxMcolPartial(m, x, mask, i, N, M):}); + ensures SparseXxMcolPartial(m, x, update(mask, tid, true), c(m[tid]), N, M) == + SparseXxMcolPartial(m, x, mask, c(m[tid]), N, M) + x[r(m[tid])]*v(m[tid]); + + decreases |m|; +void lemma_sum_upd(seq, int>> m, seq x, seq mask, int N, int M, int tid){ + if(tid > 0){ + lemma_sum_upd(m.tail, x, mask.tail, N, M, tid - 1); + assert update(mask, tid, true).tail == update(mask.tail, tid-1, true); + } else { + assert (\forall int i; 0 <= i && i < M; + {:SparseXxMcolPartial(m, x, mask, i, N, M):} == SparseXxMcolPartial(m.tail, x, mask.tail, i, N, M)); + assert update(mask, tid, true).tail == mask.tail; + } +} + + requires |m| == |mask|; + requires boundAndSorted(m, N, M); + requires |x| == N; + requires col >= 0 && col < M; + + requires (\forall int i=0..|mask|; {:mask[i]:} == true); + ensures (\let int i=col; SparseXxMcolPartial(m, x, mask, i, N, M) == SparseXxMcol(m, x, i, N, M)); + + decreases |m|; +void lemma_sum_final(seq, int>> m, seq x, seq mask, int col, int N, int M){ + if(|m|>0){ + lemma_sum_final(m.tail, x, mask.tail, col, N, M); + } +} + + context_everywhere |m| == |mask|; + context_everywhere boundAndSorted(m, N, M); + context_everywhere |x| == N; + context_everywhere M >= 0; + + requires (\forall int i=0..|mask|; {:mask[i]:} == true); + ensures (\forall int i=0..M; {:SparseXxMcolPartial(m, x, mask, i, N, M):} == SparseXxMcol(m, x, i, N, M)); + + decreases; +void lemma_sum_final_all(seq, int>> m, seq x, seq mask, int N, int M){ + int k = 0; + loop_invariant 0 <= k && k <= M; + loop_invariant (\forall int i; 0 <= i && i < k; {:SparseXxMcolPartial(m, x, mask, i, N, M):} == SparseXxMcol(m, x, i, N, M)); + decreases M-k; + while (k < M) { + lemma_sum_final(m, x, mask, k, N, M); + k = k + 1; + } +} + + requires boundAndSorted(m, N, M); + requires col >= 0 && col < M; + requires |x| == N; + requires |mask| == |m|; + decreases |m|; +pure int SparseXxMcolPartial(seq, int>> m, seq x, seq mask, int col, int N, int M) = + |m| > 0 + ? (!mask.head ? 0 : (c(m.head) == col ? x[r(m.head)] * v(m.head) : 0)) + + SparseXxMcolPartial(m.tail, x, mask.tail, col, N, M) + : 0; + +class Ghost { + seq written_; + bool[] written; +} + + requires len >= 0; + ensures \result != null ** Perm(\result.written, 1) ** Perm(\result.written_, 1); + ensures |\result.written_| == len; + ensures (\forall int i=0..len; {:\result.written_[i]:} == false); + ensures \array(\result.written, len); + ensures (\forall* int i=0..len; Perm({:\result.written[i]:}, 1) ); + ensures (\forall int i=0..len; {:\result.written[i]:} == false); + // decreases // // Need proof that new Ghost() and new bool[] decrease + decreases assume; +Ghost newGhost(int len){ + Ghost g = new Ghost(); + g.written = new bool[len]; + g.written_ = create(len, false); + return g; +} + + requires len >= 0; + ensures \array(\result, len); + ensures (\forall* int i=0..len; Perm({:\result[i]:}, 1) ); + ensures (\forall int i=0..len; {:\result[i]:} == 0); + // decreases // // Need proof that new int[] decrease + decreases assume; +int[] newInt(int len){ + int[] res = new int[len]; + return res; +} + +//----------------------------------------------- +// Function definitions for the proof that +// Sparse multiplication === Full multiplication +//----------------------------------------------- + + decreases; +pure int r(tuple, int> i) = i.fst.fst; + decreases; +pure int c(tuple, int> i) = i.fst.snd; + decreases; +pure int v(tuple, int> i) = i.snd; + +inline pure bool boundAndSorted(seq, int>> m, int N, int M) = + // The matrix is bound + (\forall int i; 0<=i && i<|m|; 0 <= {:r(m[i]):} && {:r(m[i]):} < N) && + (\forall int i; 0<=i && i<|m|; 0 <= {:c(m[i]):} && {:c(m[i]):} < M) && + // The matrix is sorted by row and column + (\forall int i=0..|m|, int j=0..i; {:r(m[j]):} < {:r(m[i]):} || (r(m[j]) == r(m[i]) && c(m[j]) < c(m[i]))); + + requires boundAndSorted(m, N, M); + ensures (\result.snd >= -1 && \result.snd < |m|); + ensures \result.snd == -1 ==> \result.fst == 0; + ensures \result.snd != -1 ==> \result.fst == (v(m[\result.snd])); + decreases; +pure tuple lookup(int x, int y, seq, int>> m, int N, int M) = + lookupH(x, y, m, N, M, 0); + + requires boundAndSorted(m, N, M); + requires k >= 0 && k <= |m|; + ensures (\result.snd >= -1 && \result.snd < |m|); + ensures \result.snd == -1 ==> \result.fst == 0; + ensures \result.snd != -1 ==> \result.fst == (v(m[\result.snd])); + decreases |m|-k; +pure tuple lookupH(int x, int y, seq, int>> m, int N, int M, int k) = + k == |m| ? tuple{0, -1} : + (r(m[k]) == y && c(m[k]) == x ? tuple{v(m[k]), k} : + (r(m[k]) > y ? tuple{0, -1} : + r(m[k]) == y && c(m[k]) > x ? tuple{0, -1} : + lookupH(x, y, m, N, M, k+1))); + + requires boundAndSorted(m, N, M); + requires |x| == N; + requires col >= 0 && col < M; + decreases |m|; +pure int SparseXxMcol(seq, int>> m, seq x, int col, int N, int M) = + |m| > 0 ? + (c(m.head) == col ? x[r(m.head)] * v(m.head) : 0) + SparseXxMcol(m.tail, x, col, N, M) + : 0; + + requires boundAndSorted(m, N, M); + requires |x| == N; + requires col >= 0 && col < M; + decreases; +pure int SparseXxMcol2(seq, int>> m, seq x, int col, int N, int M) = + SparseXxMcol2H(m, x, col, N, M, 0); + + requires boundAndSorted(m, N, M); + requires |x| == N; + requires col >= 0 && col < M; + requires k >= 0 && k <= |m|; + decreases |m|-k; +pure int SparseXxMcol2H(seq, int>> m, seq x, int col, int N, int M, int k) = + k < |m| ? + (c(m[k]) == col ? x[r(m[k])] * v(m[k]) : 0) + SparseXxMcol2H(m, x, col, N, M, k+1) + : 0; + + requires |x| == N; + requires |m| == N; + requires (\forall int i=0..N; {:|m[i]|:} == M); + requires col >= 0 && col < M; + decreases |m|; +pure int XxMcol(seq> m, seq x, int col, int N, int M) = + |m| > 0 ? + (m.head[col] * x.head) + XxMcol(m.tail, x.tail, col, N-1, M) + : 0; + + requires |x| == N; + requires |m| == N; + requires (\forall int i=0..N; {:|m[i]|:} == M); + requires col >= 0 && col < M; + decreases; +pure int XxMcol2(seq> m, seq x, int col, int N, int M) = + XxMcol2H(m, x, col, N, M, 0); + + requires |x| == N; + requires |m| == N; + requires (\forall int i=0..N; {:|m[i]|:} == M); + requires col >= 0 && col < M; + requires k >= 0 && k <= N; + decreases |m|-k; +pure int XxMcol2H(seq> m, seq x, int col, int N, int M, int k) = + k < |m| ? + (m[k][col] * x[k]) + XxMcol2H(m, x, col, N, M, k+1) + : 0; + +//----------------------------------------------- +// Lemma's +//----------------------------------------------- + +//----------------------------------------------- +// Different multiplication definitions are equivalent +//----------------------------------------------- + + requires boundAndSorted(m, N, M); + requires |x| == N; + requires col >= 0 && col < M; + requires k >= 0 && k <= |m|; + ensures SparseXxMcol2H(m, x, col, N, M, k) == SparseXxMcol(m.slice(k, |m|), x, col, N, M); + ensures k==0 ==> SparseXxMcol2(m, x, col, N, M) == SparseXxMcol(m, x, col, N, M); + decreases |m| - k; +void lemma_sparseXxM_equiv(seq, int>> m, seq x, int col, int N, int M, int k){ + if(k<|m|){ + lemma_sparseXxM_equiv(m, x, col, N, M, k+1); + assert m[k] == m.slice(k, |m|).head; + assert m.slice(k, |m|).tail == m.slice(k+1, |m|); + } + if(k==0){ + assert m.slice(k, |m|) == m; + } +} + + requires |x| == N; + requires |m| == N; + requires (\forall int i=0..N; {:|m[i]|:} == M); + requires col >= 0 && col < M; + requires k >= 0 && k <= N; + ensures XxMcol2H(m, x, col, N, M, k) == XxMcol(m.slice(k, N), x.slice(k, N), col, N-k, M); + ensures k == 0 ==> XxMcol2(m, x, col, N, M) == XxMcol(m, x, col, N, M); + decreases |m|-k; +void lemma_XxM_equiv(seq> m, seq x, int col, int N, int M, int k){ + if(k<|m|){ + lemma_XxM_equiv(m, x, col, N, M, k+1); + assert m[k] == m.slice(k, N).head; + assert x[k] == x.slice(k, N).head; + assert m.slice(k, N).tail == m.slice(k+1, N); + assert x.slice(k, N).tail == x.slice(k+1, N); + } + if(k==0){ + assert m.slice(k, N) == m; + assert x.slice(k, N) == x; + } +} + +//----------------------------------------------------------- +// Proof that sparse matrix and normal multiplication are the same +//----------------------------------------------------------- + + requires M > 0; + requires |x| == N; + requires boundAndSorted(m_s, N, M); + requires (\forall int i; 0<=i && i<|m_s|; 0 < {:c(m_s[i]):} && {:c(m_s[i]):} < M); + ensures SparseXxMcol(m_s, x, 0, N, M) == 0; + decreases |m_s|; +void lemma_zero(seq, int>> m_s, seq x, int N, int M){ + if(|m_s| > 0){ + assert 0 < {:c(m_s[0]):} && {:c(m_s[0]):} < M; + lemma_zero(m_s.tail, x, N, M); + assert SparseXxMcol(m_s, x, 0, N, M) == 0; + } +} + + +/* + The idea of this method is that we cross every x (column), y (row) of the matrix `m`, and that + we keep track on which element `k` of `m_s` we are. We note that m_s is sorted. + Originally we require that the `lookup` function of `m_s` is equivalent to `m`. + + Each time we pass the `(x,y)` for which `c(m_s(k)), r(m_s(k))` is the same, we increase `k`. + After that we do not say that `m` and `m_s` are equivalent anymore, but that they still are + everywhere after `(x,y)` and we use the `lookupH` function but only consider `m_s` after index `k`. + Since `m_s` was sorted, any values after `(x,y)` are can only be found after index `k`. + + The post-condition also keeps track of the value of `m[y][col]*xv[y]`, since if `x` already is + passed, we don't know if `m[y][col]` was present in `m_s`. +*/ + requires M > 0; + requires |xv| == N; + requires |m| == N; + requires (\forall int i=0..N; {:|m[i]|:} == M); + requires boundAndSorted(m_s, N, M); + requires 0 <= col && col < M; + requires 0 <= y && y < N && -1 <= x && x < M; + requires 0 <= k && k<|m_s|; + + requires y < r(m_s[k]) || (y == r(m_s[k]) && x < c(m_s[k])); + + requires (\forall int x_=(x+1)..M, int y_=y..(y+1); {:1:lookupH(x_, y_, m_s, N, M, k).fst:} == {:m[y_][x_]:}); + requires (\forall int x_=0..M, int y_=(y+1)..N; {:1:lookupH(x_, y_, m_s, N, M, k).fst:} == {:m[y_][x_]:}); + + requires (\forall int i=0..|m_s|; {:m[r(m_s[i])][c(m_s[i])]:} == v(m_s[i])); + + ensures SparseXxMcol2H(m_s, xv, col, N, M, k) + (x >= col ? m[y][col]*xv[y] : 0) == XxMcol2H(m, xv, col, N, M, y); + decreases N-y, M-x; +void lemma_tail(seq, int>> m_s, seq> m, seq xv, int N, int M, int col, int y, int x, int k){ + if(y == r(m_s[k])){ // We are on the same row + assert x= col ? m[y][col]*xv[y] : 0) == XxMcol2H(m, xv, col, N, M, y); + // DONE + } else { + assert x+1 == c(m_s[k]); + if(k+1<|m_s|){ + lemma_tail(m_s, m, xv, N, M, col, y, x+1, k+1); + if(x+1 == col){ + assert SparseXxMcol2H(m_s, xv, col, N, M, k) == SparseXxMcol2H(m_s, xv, col, N, M, k+1) + m[y][col]*xv[y]; + } + + assert SparseXxMcol2H(m_s, xv, col, N, M, k) + (x >= col ? m[y][col]*xv[y] : 0) == XxMcol2H(m, xv, col, N, M, y); + // DONE + } else { + assert k+1 == |m_s|; + assert SparseXxMcol2H(m_s, xv, col, N, M, k+1) == 0; + lemma_remaining(m_s, m, xv, N, M, col, y, x, k, y); + + if(x+1 == col){ + assert SparseXxMcol2H(m_s, xv, col, N, M, k) == m[y][col]*xv[y]; + assert XxMcol2H(m, xv, col, N, M, y) == m[y][col]*xv[y] + XxMcol2H(m, xv, col, N, M, y+1); + + assert SparseXxMcol2H(m_s, xv, col, N, M, k) + (x >= col ? m[y][col]*xv[y] : 0) == XxMcol2H(m, xv, col, N, M, y); + // DONE + } else { + assert SparseXxMcol2H(m_s, xv, col, N, M, k) == 0; + if(x < col){ + lookupH(col, y, m_s, N, M, k+1).fst == 0; + assert col != c(m_s[k]); + assert lookupH(col, y, m_s, N, M, k).fst == 0; + assert m[y][col]*xv[y] == 0; + } + + assert SparseXxMcol2H(m_s, xv, col, N, M, k) + (x >= col ? m[y][col]*xv[y] : 0) == XxMcol2H(m, xv, col, N, M, y); + // DONE + }// DONE + } // DONE + } // DONE + assert SparseXxMcol2H(m_s, xv, col, N, M, k) + (x >= col ? m[y][col]*xv[y] : 0) == XxMcol2H(m, xv, col, N, M, y); + } else { + assert y < r(m_s[k]); + if(x+1= col ? m[y][col]*xv[y] : 0) == XxMcol2H(m, xv, col, N, M, y); + // DONE + } else { + lemma_tail(m_s, m, xv, N, M, col, y+1, -1, k); + assert SparseXxMcol2H(m_s, xv, col, N, M, k) + (x >= col ? m[y][col]*xv[y] : 0) == XxMcol2H(m, xv, col, N, M, y); + // DONE + } + assert SparseXxMcol2H(m_s, xv, col, N, M, k) + (x >= col ? m[y][col]*xv[y] : 0) == XxMcol2H(m, xv, col, N, M, y); + //DONE + } + // QED +} + requires M > 0; + requires |xv| == N; + requires |m| == N; + requires (\forall int i=0..N; {:|m[i]|:} == M); + requires boundAndSorted(m_s, N, M); + requires 0 <= col && col < M; + requires k>=0 && k+1 == |m_s|; + requires (y == r(m_s[k]) && x < c(m_s[k])); + requires x+1 == c(m_s[k]); + requires y_ >= y && y_ < |m|; + + requires (\forall int x_=(x+1)..M, int y_=y..(y+1); {:1:lookupH(x_, y_, m_s, N, M, k).fst:} == {:m[y_][x_]:}); + requires (\forall int x_=0..M, int y_=(y+1)..N; {:1:lookupH(x_, y_, m_s, N, M, k).fst:} == {:m[y_][x_]:}); + + ensures XxMcol2H(m, xv, col, N, M, y_+1) == 0; + + decreases |m|-y_; +void lemma_remaining(seq, int>> m_s, seq> m, seq xv, int N, int M, int col, int y, int x, int k, int y_){ + if(y_+1 < |m|){ + lemma_remaining(m_s, m, xv, N, M, col, y, x, k, y_+1); + assert XxMcol2H(m, xv, col, N, M, y_+2) == 0; + assert lookupH(col, y_+1, m_s, N, M, k+1).fst == 0; + assert lookupH(col, y_+1, m_s, N, M, k).fst == 0; + assert m[y_+1][col] == 0; + } +} + + requires M > 0; + requires |x| == N; + requires |m| == N; + requires (\forall int i=0..N; {:|m[i]|:} == M); + requires boundAndSorted(m_s, N, M); + requires (\forall int x=0..M, int y=0..N; {:1:lookup(x, y, m_s, N, M).fst:} == {:m[y][x]:}); + requires |m_s| == 0; + requires 0 <= col && col < M; + requires r >= 0 && r <= N; + ensures XxMcol2H(m, x, col, N, M, r) == 0; + + decreases N - r; +void lemma_empty_matrix(seq, int>> m_s, seq> m, seq x, int N, int M, int col, int r){ + if(r < N){ + lemma_empty_matrix(m_s, m, x, N, M, col, r+1); + assert lookup(col, r, m_s, N, M).fst == 0; + } +} + + context_everywhere |x| == N; + context_everywhere |m| == N; + context_everywhere (\forall int i=0..N; {:|m[i]|:} == M); + context_everywhere boundAndSorted(m_s, N, M); + + context_everywhere (\forall int x=0..M, int y=0..N; {:1:lookup(x, y, m_s, N, M).fst:} == {:m[y][x]:}); + context_everywhere (\forall int i=0..|m_s|; {:m[r(m_s[i])][c(m_s[i])]:} == v(m_s[i])); + + ensures (\forall int col=0..M; {:XxMcol2(m, x, col, N, M):} == SparseXxMcol2(m_s, x, col, N, M)); + ensures (\forall int col=0..M; {:XxMcol(m, x, col, N, M):} == SparseXxMcol(m_s, x, col, N, M)); + decreases; +void lemma_sparse_full(seq, int>> m_s, seq> m, seq x, int N, int M){ + if(M>0){ + loop_invariant i >= 0 && i <= M; + loop_invariant (\forall int col=0..i; {:XxMcol2(m, x, col, N, M):} == SparseXxMcol2(m_s, x, col, N, M)); + loop_invariant (\forall int col=0..i; {:XxMcol(m, x, col, N, M):} == SparseXxMcol(m_s, x, col, N, M)); + decreases M-i; + for(int i=0; i 0 && N>0 && |m_s| > 0){ + lemma_tail(m_s, m, x, N, M, i, 0, -1, 0); + } else if(M>0) { + if(|m_s| == 0){ + lemma_empty_matrix(m_s, m, x, N, M, i, 0); + assert XxMcol2(m, x, i, N, M) == 0; + assert SparseXxMcol2(m_s, x, i, N, M) == 0; + } + if(N == 0){ + assert XxMcol2(m, x, i, N, M) == 0; + assert SparseXxMcol2(m_s, x, i, N, M) == 0; + } + } + lemma_XxM_equiv(m, x, i, N, M, 0); + lemma_sparseXxM_equiv(m_s, x, i, N, M, 0); + } + } +} \ No newline at end of file diff --git a/test/main/vct/test/integration/examples/VerifyThisSpec.scala b/test/main/vct/test/integration/examples/VerifyThisSpec.scala index a8cd688fd4..ad57769af3 100644 --- a/test/main/vct/test/integration/examples/VerifyThisSpec.scala +++ b/test/main/vct/test/integration/examples/VerifyThisSpec.scala @@ -17,6 +17,7 @@ class VerifyThisSpec extends VercorsSpec { // Due to be fixed by https://github.com/viperproject/silicon/pull/642 // vercors should verify using silicon example "verifythis/2019/challenge1.pvl" vercors should verify using silicon example "verifythis/2019/challenge2b.pvl" + vercors should verify usingFlags("--no-infer-heap-context-into-frame", silicon) example "/verifythis/2019/challenge3_complete.pvl" vercors should verify using silicon example "verifythis/2021/TeamBlue/Challenge1.pvl" // vercors should verify using silicon example "verifythis/2021/TeamBlue/Challenge2.pvl" // vercors should verify using silicon example "verifythis/2021/TeamBlue/Challenge3.pvl" diff --git a/test/main/vct/test/integration/helper/VercorsSpec.scala b/test/main/vct/test/integration/helper/VercorsSpec.scala index 390cdc7350..d73891a0d7 100644 --- a/test/main/vct/test/integration/helper/VercorsSpec.scala +++ b/test/main/vct/test/integration/helper/VercorsSpec.scala @@ -181,6 +181,10 @@ abstract class VercorsSpec extends AnyFlatSpec { class VerdictPhrase(val verdict: Verdict, val reportPath: Option[Path]) { def using(backend: Seq[Backend]): BackendPhrase = new BackendPhrase(verdict, reportPath, backend, Nil) + + def usingFlags(flags: Seq[String], backend: Seq[Backend]): BackendPhrase = new BackendPhrase(verdict, reportPath, backend, flags) + + def usingFlags(flag: String, backend: Seq[Backend]): BackendPhrase = new BackendPhrase(verdict, reportPath, backend, Seq(flag)) } class BackendPhrase(val verdict: Verdict, val reportPath: Option[Path], val backends: Seq[Backend], val _flags: Seq[String]) { From 9269cab0abd7a91653a8eb0924dddf8ac5b16f52 Mon Sep 17 00:00:00 2001 From: Lars Date: Wed, 6 Mar 2024 15:07:22 +0100 Subject: [PATCH 19/52] Use flag option that was present --- test/main/vct/test/integration/examples/VerifyThisSpec.scala | 2 +- test/main/vct/test/integration/helper/VercorsSpec.scala | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/test/main/vct/test/integration/examples/VerifyThisSpec.scala b/test/main/vct/test/integration/examples/VerifyThisSpec.scala index ad57769af3..5ee6897927 100644 --- a/test/main/vct/test/integration/examples/VerifyThisSpec.scala +++ b/test/main/vct/test/integration/examples/VerifyThisSpec.scala @@ -17,7 +17,7 @@ class VerifyThisSpec extends VercorsSpec { // Due to be fixed by https://github.com/viperproject/silicon/pull/642 // vercors should verify using silicon example "verifythis/2019/challenge1.pvl" vercors should verify using silicon example "verifythis/2019/challenge2b.pvl" - vercors should verify usingFlags("--no-infer-heap-context-into-frame", silicon) example "/verifythis/2019/challenge3_complete.pvl" + vercors should verify using silicon flag "--no-infer-heap-context-into-frame" example "/verifythis/2019/challenge3_complete.pvl" vercors should verify using silicon example "verifythis/2021/TeamBlue/Challenge1.pvl" // vercors should verify using silicon example "verifythis/2021/TeamBlue/Challenge2.pvl" // vercors should verify using silicon example "verifythis/2021/TeamBlue/Challenge3.pvl" diff --git a/test/main/vct/test/integration/helper/VercorsSpec.scala b/test/main/vct/test/integration/helper/VercorsSpec.scala index d73891a0d7..390cdc7350 100644 --- a/test/main/vct/test/integration/helper/VercorsSpec.scala +++ b/test/main/vct/test/integration/helper/VercorsSpec.scala @@ -181,10 +181,6 @@ abstract class VercorsSpec extends AnyFlatSpec { class VerdictPhrase(val verdict: Verdict, val reportPath: Option[Path]) { def using(backend: Seq[Backend]): BackendPhrase = new BackendPhrase(verdict, reportPath, backend, Nil) - - def usingFlags(flags: Seq[String], backend: Seq[Backend]): BackendPhrase = new BackendPhrase(verdict, reportPath, backend, flags) - - def usingFlags(flag: String, backend: Seq[Backend]): BackendPhrase = new BackendPhrase(verdict, reportPath, backend, Seq(flag)) } class BackendPhrase(val verdict: Verdict, val reportPath: Option[Path], val backends: Seq[Backend], val _flags: Seq[String]) { From 6bc57f430266f2b4d93acb64f1fa60a80f85a71b Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Thu, 25 Apr 2024 15:31:15 +0200 Subject: [PATCH 20/52] Move col printing back to after typechecking --- src/main/vct/main/stages/Transformation.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/vct/main/stages/Transformation.scala b/src/main/vct/main/stages/Transformation.scala index ad73d312c5..c9fbe0baa0 100644 --- a/src/main/vct/main/stages/Transformation.scala +++ b/src/main/vct/main/stages/Transformation.scala @@ -131,15 +131,15 @@ class Transformation throw c } - onAfterPassKey.foreach { - case (key, action) => if (pass.key == key) action(result) - } - result.tasks.map(_.program).flatMap(program => program.check.map(program -> _)) match { case Nil => // ok case errors => throw TransformationCheckError(pass, errors) } + onAfterPassKey.foreach { + case (key, action) => if (pass.key == key) action(result) + } + result = PrettifyBlocks().dispatch(result) } From f5d753457fd5e62bae0a779e22e9ee4c40d828e1 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Thu, 25 Apr 2024 16:10:18 +0200 Subject: [PATCH 21/52] Little bit of progress on Pieter's code review feedback --- src/col/vct/col/ast/type/TClassImpl.scala | 5 ++++- src/col/vct/col/typerules/CoercionUtils.scala | 2 +- src/col/vct/col/typerules/Types.scala | 11 +++++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/col/vct/col/ast/type/TClassImpl.scala b/src/col/vct/col/ast/type/TClassImpl.scala index 6e528ed4b7..d59d0f74c6 100644 --- a/src/col/vct/col/ast/type/TClassImpl.scala +++ b/src/col/vct/col/ast/type/TClassImpl.scala @@ -7,7 +7,10 @@ import vct.col.ref.Ref import vct.result.VerificationError.Unreachable trait TClassImpl[G] extends TClassOps[G] { this: TClass[G] => - def transSupportArrows: Seq[(Class[G], Class[G])] = cls.decl.transSupportArrows + def transSupportArrows: Seq[(TClass[G], TClass[G])] = cls.decl.transSupportArrows.map { + // TODO (RR): To implement this, cls.supports needs to be TClass instead of Class + case (clsA, clsB) => (TClass(clsA.ref, Seq()) TClass(clsB.ref, Seq()) + } override def layout(implicit ctx: Ctx): Doc = Text(ctx.name(cls)) <> ( if (typeArgs.nonEmpty) Text("<") <> Doc.args(typeArgs) <> ">" else Empty) diff --git a/src/col/vct/col/typerules/CoercionUtils.scala b/src/col/vct/col/typerules/CoercionUtils.scala index 0eb72aa475..dc9bf84f0d 100644 --- a/src/col/vct/col/typerules/CoercionUtils.scala +++ b/src/col/vct/col/typerules/CoercionUtils.scala @@ -119,7 +119,7 @@ case object CoercionUtils { case (_: IntType[G], TRational()) => CoerceIntRat() case (source @ TClass(sourceClass, Seq()), target @ TClass(targetClass, Seq())) - if source.transSupportArrows.exists { case (_, supp) => supp == targetClass.decl } => + if source.transSupportArrows.exists { case (_, supp) => supp.cls.decl == targetClass.decl } => CoerceSupports(sourceClass, targetClass) case (source @ TClass(sourceClass, typeArgs), TAnyClass()) => diff --git a/src/col/vct/col/typerules/Types.scala b/src/col/vct/col/typerules/Types.scala index 66c41a0431..28c4a65a25 100644 --- a/src/col/vct/col/typerules/Types.scala +++ b/src/col/vct/col/typerules/Types.scala @@ -51,10 +51,9 @@ object Types { case (TType(left), TType(right)) => TType(leastCommonSuperType(left, right)) - case (TClass(left, Seq()), TClass(right, Seq())) => - // TODO (RR): Not sure how generics factor into this part... See also: next match statement - val leftArrows = left.decl.transSupportArrows - val rightArrows = right.decl.transSupportArrows + case (left @ TClass(_, _), right @ TClass(_, _)) => + val leftArrows = left.transSupportArrows + val rightArrows = right.transSupportArrows // Shared support are classes where there is an incoming left-arrow and right-arrow // If left supports right (or vice-versa), there would be a problem, since right will not have a self-arrow // However, this is caught by the simple sub-typing relation above already. @@ -64,8 +63,8 @@ object Types { val classes = (shared.toSet -- nonBottom.toSet).toSeq classes match { case Nil => TAnyClass() - case Seq(t) => TClass(t.ref, Seq()) - case other => TUnion(other.map(cls => TClass(cls.ref, Seq()))) + case Seq(t) => t + case other => TUnion(other) } case (TClass(_, _), TAnyClass()) | (TAnyClass(), TClass(_, _)) => From 3ce603d1a06ea90f7255b98caedaa2754d468e9a Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Fri, 26 Apr 2024 11:32:52 +0200 Subject: [PATCH 22/52] Change supports from Class to TClass --- src/col/vct/col/ast/Node.scala | 2 +- .../ast/declaration/global/ClassImpl.scala | 9 ++++---- .../ast/family/coercion/CoercionImpl.scala | 4 ++-- src/col/vct/col/ast/type/TClassImpl.scala | 2 +- src/col/vct/col/resolve/Resolve.scala | 3 --- .../systemctocol/engine/ClassTransformer.java | 4 ++-- .../engine/KnownTypeTransformer.java | 7 +++--- .../systemctocol/engine/MainTransformer.java | 6 ++--- .../vct/rewrite/lang/LangJavaToCol.scala | 22 +++++++++---------- .../vct/rewrite/lang/NoSupportSelfLoop.scala | 2 +- 10 files changed, 27 insertions(+), 34 deletions(-) diff --git a/src/col/vct/col/ast/Node.scala b/src/col/vct/col/ast/Node.scala index 1d1ae1a88a..39e8c05c4b 100644 --- a/src/col/vct/col/ast/Node.scala +++ b/src/col/vct/col/ast/Node.scala @@ -249,7 +249,7 @@ final case class ModelDo[G](model: Expr[G], perm: Expr[G], after: Expr[G], actio final class HeapVariable[G](val t: Type[G])(implicit val o: Origin) extends GlobalDeclaration[G] with HeapVariableImpl[G] final class SimplificationRule[G](val axiom: Expr[G])(implicit val o: Origin) extends GlobalDeclaration[G] with SimplificationRuleImpl[G] @scopes[Variable] final class AxiomaticDataType[G](val decls: Seq[ADTDeclaration[G]], val typeArgs: Seq[Variable[G]])(implicit val o: Origin) extends GlobalDeclaration[G] with AxiomaticDataTypeImpl[G] -final class Class[G](val typeArgs: Seq[Variable[G]], val decls: Seq[ClassDeclaration[G]], val supports: Seq[Ref[G, Class[G]]], val intrinsicLockInvariant: Expr[G])(implicit val o: Origin) extends GlobalDeclaration[G] with ClassImpl[G] +final class Class[G](val typeArgs: Seq[Variable[G]], val decls: Seq[ClassDeclaration[G]], val supports: Seq[Type[G]], val intrinsicLockInvariant: Expr[G])(implicit val o: Origin) extends GlobalDeclaration[G] with ClassImpl[G] final class Model[G](val declarations: Seq[ModelDeclaration[G]])(implicit val o: Origin) extends GlobalDeclaration[G] with Declarator[G] with ModelImpl[G] @scopes[LabelDecl] final class Function[G](val returnType: Type[G], val args: Seq[Variable[G]], val typeArgs: Seq[Variable[G]], val body: Option[Expr[G]], val contract: ApplicableContract[G], val inline: Boolean = false, val threadLocal: Boolean = false) diff --git a/src/col/vct/col/ast/declaration/global/ClassImpl.scala b/src/col/vct/col/ast/declaration/global/ClassImpl.scala index 3ab9d80a94..3c5b848d93 100644 --- a/src/col/vct/col/ast/declaration/global/ClassImpl.scala +++ b/src/col/vct/col/ast/declaration/global/ClassImpl.scala @@ -1,6 +1,6 @@ package vct.col.ast.declaration.global -import vct.col.ast.{Class, Declaration} +import vct.col.ast.{Class, Declaration, TClass} import vct.col.ast.util.Declarator import vct.col.print._ import vct.col.util.AstBuildHelpers.tt @@ -10,8 +10,8 @@ import vct.col.ast.ops.ClassOps trait ClassImpl[G] extends Declarator[G] with ClassOps[G] { this: Class[G] => protected def transSupportArrows(seen: Set[Class[G]]): Seq[(Class[G], Class[G])] = if(seen.contains(this)) Nil - else supports.map(other => (this, other.decl)) ++ - supports.flatMap(other => other.decl.transSupportArrows(Set(this) ++ seen)) + else supports.map(other => (this, other.asClass.get.cls.decl)) ++ + supports.flatMap(other => other.asClass.get.cls.decl.transSupportArrows(Set(this) ++ seen)) def transSupportArrows: Seq[(Class[G], Class[G])] = transSupportArrows(Set.empty) @@ -25,7 +25,8 @@ trait ClassImpl[G] extends Declarator[G] with ClassOps[G] { this: Class[G] => Group( Text("class") <+> ctx.name(this) <> (if (typeArgs.nonEmpty) Text("<") <> Doc.args(typeArgs) <> ">" else Empty) <> - (if(supports.isEmpty) Empty else Text(" implements") <+> Doc.args(supports.map(ctx.name).map(Text))) <+> + (if(supports.isEmpty) Empty else Text(" implements") <+> + Doc.args(supports.map(supp => ctx.name(supp.asClass.get.cls)).map(Text))) <+> "{" ) <>> Doc.stack(decls) <+/> diff --git a/src/col/vct/col/ast/family/coercion/CoercionImpl.scala b/src/col/vct/col/ast/family/coercion/CoercionImpl.scala index 4d077bad02..768278bfa0 100644 --- a/src/col/vct/col/ast/family/coercion/CoercionImpl.scala +++ b/src/col/vct/col/ast/family/coercion/CoercionImpl.scala @@ -35,7 +35,7 @@ trait CoercionImpl[G] extends CoercionFamilyOps[G] { this: Coercion[G] => case CoerceBoolResource() => true case CoerceNullRef() => true case CoerceNullArray(_) => true - case CoerceNullClass(_) => true + case CoerceNullClass(_, _) => true case CoerceNullJavaClass(_) => true case CoerceNullAnyClass() => true case CoerceNullPointer(_) => true @@ -51,7 +51,7 @@ trait CoercionImpl[G] extends CoercionFamilyOps[G] { this: Coercion[G] => case CoerceBoundIntZFrac(_) => true case CoerceSupports(_, _) => true case CoerceJavaSupports(_, _) => true - case CoerceClassAnyClass(_) => true + case CoerceClassAnyClass(_, _) => true case CoerceJavaClassAnyClass(_) => true case CoerceCPrimitiveToCol(_, _) => true case CoerceColToCPrimitive(_, _) => true diff --git a/src/col/vct/col/ast/type/TClassImpl.scala b/src/col/vct/col/ast/type/TClassImpl.scala index d59d0f74c6..8e73c26b41 100644 --- a/src/col/vct/col/ast/type/TClassImpl.scala +++ b/src/col/vct/col/ast/type/TClassImpl.scala @@ -9,7 +9,7 @@ import vct.result.VerificationError.Unreachable trait TClassImpl[G] extends TClassOps[G] { this: TClass[G] => def transSupportArrows: Seq[(TClass[G], TClass[G])] = cls.decl.transSupportArrows.map { // TODO (RR): To implement this, cls.supports needs to be TClass instead of Class - case (clsA, clsB) => (TClass(clsA.ref, Seq()) TClass(clsB.ref, Seq()) + case (clsA, clsB) => (TClass(clsA.ref, Seq()), TClass(clsB.ref, Seq())) } override def layout(implicit ctx: Ctx): Doc = Text(ctx.name(cls)) <> ( diff --git a/src/col/vct/col/resolve/Resolve.scala b/src/col/vct/col/resolve/Resolve.scala index e84b835918..4caef670cc 100644 --- a/src/col/vct/col/resolve/Resolve.scala +++ b/src/col/vct/col/resolve/Resolve.scala @@ -148,9 +148,6 @@ case object ResolveTypes { case t @ SilverPartialTAxiomatic(ref, partialTypeArgs) => ref.tryResolve(name => Spec.findAdt(name, ctx).getOrElse(throw NoSuchNameError("adt", name, t))) partialTypeArgs.foreach(mapping => mapping._1.tryResolve(name => Spec.findAdtTypeArg(ref.decl, name).getOrElse(throw NoSuchNameError("type variable", name, t)))) - case cls: Class[G] => - // PB: needs to be in ResolveTypes if we want to support method inheritance at some point. - cls.supports.foreach(_.tryResolve(name => Spec.findClass(name, ctx).getOrElse(throw NoSuchNameError("class", name, cls)))) case local: JavaLocal[G] => Java.findJavaName(local.name, fromStaticContext = false, ctx) match { case Some( diff --git a/src/parsers/vct/parsers/transform/systemctocol/engine/ClassTransformer.java b/src/parsers/vct/parsers/transform/systemctocol/engine/ClassTransformer.java index 99a6619b33..b23134a1df 100644 --- a/src/parsers/vct/parsers/transform/systemctocol/engine/ClassTransformer.java +++ b/src/parsers/vct/parsers/transform/systemctocol/engine/ClassTransformer.java @@ -76,7 +76,7 @@ public Class create_process_class(ProcessClass process) { declarations.addAll(generated_instance_methods); return new Class<>(Seqs.empty(), - List.from(CollectionConverters.asScala(declarations)), col_system.NO_CLS_REFS, col_system.TRUE, + List.from(CollectionConverters.asScala(declarations)), Seqs.empty(), col_system.TRUE, OriGen.create(create_name(process.get_generating_instance(), process.get_generating_function()))); } @@ -127,7 +127,7 @@ public Class create_state_class(StateClass state_class) { declarations.addAll(generated_instance_methods); return new Class<>(Seqs.empty(), - List.from(CollectionConverters.asScala(declarations)), col_system.NO_CLS_REFS, col_system.TRUE, + List.from(CollectionConverters.asScala(declarations)), Seqs.empty(), col_system.TRUE, OriGen.create(create_name(state_class.get_generating_instance()))); } diff --git a/src/parsers/vct/parsers/transform/systemctocol/engine/KnownTypeTransformer.java b/src/parsers/vct/parsers/transform/systemctocol/engine/KnownTypeTransformer.java index ac8000ccf1..3165bba55d 100644 --- a/src/parsers/vct/parsers/transform/systemctocol/engine/KnownTypeTransformer.java +++ b/src/parsers/vct/parsers/transform/systemctocol/engine/KnownTypeTransformer.java @@ -144,8 +144,7 @@ private Class transform_fifo(Origin o, Type t) { // Create the class java.util.List> declarations = java.util.List.of(m, buf, nr_read, written, constructor, fifo_read, fifo_write, fifo_update); - return new Class<>(List.from(CollectionConverters.asScala(new java.util.ArrayList(0))), - List.from(CollectionConverters.asScala(declarations)), col_system.NO_CLS_REFS, col_system.TRUE, o); + return new Class<>(Seqs.empty(), List.from(CollectionConverters.asScala(declarations)), Seqs.empty(), col_system.TRUE, o); } /** @@ -546,8 +545,8 @@ private Class transform_signal(Origin o, Type t) { // Create the class java.util.List> class_content = java.util.List.of(m, val, _val, constructor, signal_read, signal_write, signal_update); - return new Class<>(List.from(CollectionConverters.asScala(new java.util.ArrayList(0))), - List.from(CollectionConverters.asScala(class_content)), col_system.NO_CLS_REFS, col_system.TRUE, o); + return new Class<>(Seqs.empty(), + List.from(CollectionConverters.asScala(class_content)), Seqs.empty(), col_system.TRUE, o); } /** diff --git a/src/parsers/vct/parsers/transform/systemctocol/engine/MainTransformer.java b/src/parsers/vct/parsers/transform/systemctocol/engine/MainTransformer.java index c22e7d0cde..0cb5fea949 100644 --- a/src/parsers/vct/parsers/transform/systemctocol/engine/MainTransformer.java +++ b/src/parsers/vct/parsers/transform/systemctocol/engine/MainTransformer.java @@ -1234,10 +1234,8 @@ private void assemble_main() { new WritePerm<>(OriGen.create()), OriGen.create()); // Assemble class - Class main_class = new Class<>( - List.from(CollectionConverters.asScala(new java.util.ArrayList(0))), - List.from(CollectionConverters.asScala(declarations)), col_system.NO_CLS_REFS, - lock_invariant, OriGen.create("Main")); + Class main_class = new Class<>(Seqs.empty(), List.from(CollectionConverters.asScala(declarations)), + Seqs.empty(), lock_invariant, OriGen.create("Main")); // Register Main class in COL system context col_system.add_global_declaration(main_class); diff --git a/src/rewrite/vct/rewrite/lang/LangJavaToCol.scala b/src/rewrite/vct/rewrite/lang/LangJavaToCol.scala index 9be6c355e8..2c9b552d21 100644 --- a/src/rewrite/vct/rewrite/lang/LangJavaToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangJavaToCol.scala @@ -291,11 +291,6 @@ case class LangJavaToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) extends }) currentJavaClass.having(cls) { - val supports = cls.supports.map(rw.dispatch).flatMap { - case TClass(ref, Seq()) => Seq(ref) - case _ => ??? - } - val instDecls = cls.decls.filter(!isJavaStatic(_)) val staticDecls = cls.decls.filter(isJavaStatic) @@ -309,13 +304,16 @@ case class LangJavaToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) extends new Class[Post]( rw.variables.dispatch(cls.typeParams)(rw), rw.classDeclarations.collect { - makeJavaClass(cls.name, instDecls, javaInstanceClassSuccessor.ref(cls), isStaticPart = false) - cls match { - case cls: JavaClass[Pre] if BipComponent.get(cls).isDefined => - rw.bip.generateComponent(cls) - case _ => - } - }._1, supports, rw.dispatch(lockInvariant))(JavaInstanceClassOrigin(cls)) + makeJavaClass(cls.name, instDecls, javaInstanceClassSuccessor.ref(cls), isStaticPart = false) + cls match { + case cls: JavaClass[Pre] if BipComponent.get(cls).isDefined => + rw.bip.generateComponent(cls) + case _ => + } + }._1, + cls.supports.map(rw.dispatch), + rw.dispatch(lockInvariant) + )(JavaInstanceClassOrigin(cls)) } rw.globalDeclarations.declare(instanceClass) diff --git a/src/rewrite/vct/rewrite/lang/NoSupportSelfLoop.scala b/src/rewrite/vct/rewrite/lang/NoSupportSelfLoop.scala index 8b976a422e..b56fcacdf4 100644 --- a/src/rewrite/vct/rewrite/lang/NoSupportSelfLoop.scala +++ b/src/rewrite/vct/rewrite/lang/NoSupportSelfLoop.scala @@ -12,7 +12,7 @@ case object NoSupportSelfLoop extends RewriterBuilder { case class NoSupportSelfLoop[Pre <: Generation]() extends Rewriter[Pre] { override def dispatch(decl: Declaration[Pre]): Unit = decl match { case cls: Class[Pre] => - globalDeclarations.succeed(cls, cls.rewrite(supports = cls.supports.map(_.decl).filter(_ != cls).map(succ[Class[Post]]))) + globalDeclarations.succeed(cls, cls.rewrite(supports = cls.supports.filter(_.asClass.get.cls.decl != cls).map(_.rewriteDefault()))) case other => rewriteDefault(other) } } From 81bc9d389a9c8d7dfd5abb9c1159706d15073b16 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Fri, 26 Apr 2024 12:06:00 +0200 Subject: [PATCH 23/52] TClass arrows looks alright now --- .../col/ast/declaration/global/ClassImpl.scala | 16 ++++++++++------ src/col/vct/col/ast/type/TClassImpl.scala | 10 ++++++---- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/col/vct/col/ast/declaration/global/ClassImpl.scala b/src/col/vct/col/ast/declaration/global/ClassImpl.scala index 3c5b848d93..b84e7469bd 100644 --- a/src/col/vct/col/ast/declaration/global/ClassImpl.scala +++ b/src/col/vct/col/ast/declaration/global/ClassImpl.scala @@ -1,6 +1,6 @@ package vct.col.ast.declaration.global -import vct.col.ast.{Class, Declaration, TClass} +import vct.col.ast.{Class, Declaration, TClass, TVar} import vct.col.ast.util.Declarator import vct.col.print._ import vct.col.util.AstBuildHelpers.tt @@ -8,12 +8,16 @@ import vct.result.VerificationError.Unreachable import vct.col.ast.ops.ClassOps trait ClassImpl[G] extends Declarator[G] with ClassOps[G] { this: Class[G] => - protected def transSupportArrows(seen: Set[Class[G]]): Seq[(Class[G], Class[G])] = - if(seen.contains(this)) Nil - else supports.map(other => (this, other.asClass.get.cls.decl)) ++ - supports.flatMap(other => other.asClass.get.cls.decl.transSupportArrows(Set(this) ++ seen)) + def transSupportArrowsHelper(seen: Set[TClass[G]]): Seq[(TClass[G], TClass[G])] = { + val t: TClass[G] = TClass(this.ref, typeArgs.map(v => TVar(v.ref))) + if(seen.contains(t)) Nil + else supers.map(sup => (t, sup)) ++ + supers.flatMap(sup => sup.transSupportArrowsHelper(Set(t) ++ seen)) + } - def transSupportArrows: Seq[(Class[G], Class[G])] = transSupportArrows(Set.empty) + def transSupportArrows: Seq[(TClass[G], TClass[G])] = transSupportArrowsHelper(Set.empty) + + def supers: Seq[TClass[G]] = supports.map(_.asClass.get) override def declarations: Seq[Declaration[G]] = decls ++ typeArgs diff --git a/src/col/vct/col/ast/type/TClassImpl.scala b/src/col/vct/col/ast/type/TClassImpl.scala index 8e73c26b41..6cc09e175b 100644 --- a/src/col/vct/col/ast/type/TClassImpl.scala +++ b/src/col/vct/col/ast/type/TClassImpl.scala @@ -7,10 +7,12 @@ import vct.col.ref.Ref import vct.result.VerificationError.Unreachable trait TClassImpl[G] extends TClassOps[G] { this: TClass[G] => - def transSupportArrows: Seq[(TClass[G], TClass[G])] = cls.decl.transSupportArrows.map { - // TODO (RR): To implement this, cls.supports needs to be TClass instead of Class - case (clsA, clsB) => (TClass(clsA.ref, Seq()), TClass(clsB.ref, Seq())) - } + def transSupportArrowsHelper(seen: Set[TClass[G]]): Seq[(TClass[G], TClass[G])] = + cls.decl.transSupportArrowsHelper(seen).map { + case (clsA, clsB) => (instantiate(clsA).asClass.get, instantiate(clsB).asClass.get) + } + + def transSupportArrows(): Seq[(TClass[G], TClass[G])] = transSupportArrowsHelper(Set.empty) override def layout(implicit ctx: Ctx): Doc = Text(ctx.name(cls)) <> ( if (typeArgs.nonEmpty) Text("<") <> Doc.args(typeArgs) <> ">" else Empty) From f94dadb6092f91c3112057d88e693a7002b9ca03 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Fri, 26 Apr 2024 13:21:28 +0200 Subject: [PATCH 24/52] Fix ClassToRef --- src/rewrite/vct/rewrite/ClassToRef.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rewrite/vct/rewrite/ClassToRef.scala b/src/rewrite/vct/rewrite/ClassToRef.scala index 003308c74b..bc36a9e9ee 100644 --- a/src/rewrite/vct/rewrite/ClassToRef.scala +++ b/src/rewrite/vct/rewrite/ClassToRef.scala @@ -93,7 +93,7 @@ case class ClassToRef[Pre <: Generation]() extends Rewriter[Pre] { ((sub.get === const(0)) ==> tt) && foldAnd(typeNumberStore.map { case (cls, subNum) => - val supNums = (cls +: cls.transSupportArrows.map(_._2)).distinct.map(typeNumber) + val supNums = (cls +: cls.transSupportArrows.map(_._2.cls.decl)).distinct.map(typeNumber) (sub.get === const(subNum)) ==> foldOr(supNums.map(supNum => sup.get === const(supNum))) }.toSeq) ), From 42cf2354a549376a8bfe5cd771e88ddf936f9604 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Fri, 26 Apr 2024 14:31:29 +0200 Subject: [PATCH 25/52] Generalize typeEnv on applicable. Make genericChannel test case more complicated --- examples/concepts/generics/genericChannel.pvl | 16 ++++++++++- .../expr/apply/FunctionInvocationImpl.scala | 4 ++- .../InstanceFunctionInvocationImpl.scala | 5 +++- .../col/ast/expr/apply/InvocationImpl.scala | 5 ++-- .../ast/expr/apply/MethodInvocationImpl.scala | 7 ++--- .../expr/apply/ProcedureInvocationImpl.scala | 4 ++- .../col/ast/lang/pvl/PVLInvocationImpl.scala | 27 +++++++++---------- src/col/vct/col/ast/type/TClassImpl.scala | 4 +-- .../vct/col/ast/type/typeclass/TypeImpl.scala | 2 +- .../unsorted/ConstructorInvocationImpl.scala | 10 +++++-- 10 files changed, 53 insertions(+), 31 deletions(-) diff --git a/examples/concepts/generics/genericChannel.pvl b/examples/concepts/generics/genericChannel.pvl index 5ca1724a4d..2699fd812a 100644 --- a/examples/concepts/generics/genericChannel.pvl +++ b/examples/concepts/generics/genericChannel.pvl @@ -1,5 +1,5 @@ lock_invariant Perm(transfering, 1) ** Perm(exchangeValue,1); -class IntegerChannel { +class Chan { boolean transfering; T exchangeValue; @@ -43,3 +43,17 @@ class IntegerChannel { return m; } } + +void main() { + Chan intChan = new Chan(); + Chan boolChan = new Chan(); + Chan> intChanChan = new Chan>(); + + intChan.writeValue(5); + boolChan.writeValue(true); + intChanChan.writeValue(intChan); + + int i = intChan.readValue(); + int b = boolChan.readValue(); + int c = intChanChan.readValue(); +} \ No newline at end of file diff --git a/src/col/vct/col/ast/expr/apply/FunctionInvocationImpl.scala b/src/col/vct/col/ast/expr/apply/FunctionInvocationImpl.scala index 4ff5adc0ca..f0aeef2478 100644 --- a/src/col/vct/col/ast/expr/apply/FunctionInvocationImpl.scala +++ b/src/col/vct/col/ast/expr/apply/FunctionInvocationImpl.scala @@ -1,6 +1,6 @@ package vct.col.ast.expr.apply -import vct.col.ast.FunctionInvocation +import vct.col.ast.{FunctionInvocation, Variable, Type} import vct.col.print._ import vct.col.ast.ops.FunctionInvocationOps @@ -22,4 +22,6 @@ trait FunctionInvocationImpl[G] extends FunctionInvocationOps[G] { this: Functio case Ctx.Silver => layoutSilver case _ => layoutSpec } + + override def typeEnv: Map[Variable[G], Type[G]] = ref.decl.typeArgs.zip(typeArgs).toMap } diff --git a/src/col/vct/col/ast/expr/apply/InstanceFunctionInvocationImpl.scala b/src/col/vct/col/ast/expr/apply/InstanceFunctionInvocationImpl.scala index f869a8f17f..9cb8e81e06 100644 --- a/src/col/vct/col/ast/expr/apply/InstanceFunctionInvocationImpl.scala +++ b/src/col/vct/col/ast/expr/apply/InstanceFunctionInvocationImpl.scala @@ -1,6 +1,6 @@ package vct.col.ast.expr.apply -import vct.col.ast.InstanceFunctionInvocation +import vct.col.ast.{InstanceFunctionInvocation, Variable, Type} import vct.col.print.{Ctx, Doc, DocUtil, Empty, Group, Precedence, Text} import vct.col.ast.ops.InstanceFunctionInvocationOps @@ -14,4 +14,7 @@ trait InstanceFunctionInvocationImpl[G] extends InstanceFunctionInvocationOps[G] "(" ) <> Doc.args(args) <> ")" <> DocUtil.givenYields(givenMap, yields) ) + + override def typeEnv: Map[Variable[G], Type[G]] = + obj.t.asClass.get.typeEnv ++ ref.decl.typeArgs.zip(typeArgs).toMap } diff --git a/src/col/vct/col/ast/expr/apply/InvocationImpl.scala b/src/col/vct/col/ast/expr/apply/InvocationImpl.scala index bc552ab5f3..66ce34916b 100644 --- a/src/col/vct/col/ast/expr/apply/InvocationImpl.scala +++ b/src/col/vct/col/ast/expr/apply/InvocationImpl.scala @@ -1,7 +1,8 @@ package vct.col.ast.expr.apply -import vct.col.ast.{Invocation, Type} +import vct.col.ast.{Invocation, Type, Variable} trait InvocationImpl[G] extends ApplyImpl[G] { this: Invocation[G] => - override def t: Type[G] = super.t.particularize(ref.decl.typeArgs.zip(typeArgs).toMap) + override def t: Type[G] = super.t.particularize(typeEnv) + def typeEnv: Map[Variable[G], Type[G]] } \ No newline at end of file diff --git a/src/col/vct/col/ast/expr/apply/MethodInvocationImpl.scala b/src/col/vct/col/ast/expr/apply/MethodInvocationImpl.scala index e242bc9434..e984739352 100644 --- a/src/col/vct/col/ast/expr/apply/MethodInvocationImpl.scala +++ b/src/col/vct/col/ast/expr/apply/MethodInvocationImpl.scala @@ -1,6 +1,6 @@ package vct.col.ast.expr.apply -import vct.col.ast.{MethodInvocation, TClass, Type} +import vct.col.ast.{MethodInvocation, TClass, Type, Variable} import vct.col.print.{Ctx, Doc, DocUtil, Empty, Group, Precedence, Text} import vct.col.ast.ops.MethodInvocationOps import vct.col.ref.Ref @@ -17,8 +17,5 @@ trait MethodInvocationImpl[G] extends MethodInvocationOps[G] with InvocationImpl ) <> Doc.args(args ++ outArgs) <> ")" <> DocUtil.givenYields(givenMap, yields) ) - override def t: Type[G] = obj.t match { - case t: TClass[G] => t.instantiate(super.t) - case _ => super.t - } + override def typeEnv: Map[Variable[G], Type[G]] = ref.decl.typeArgs.zip(typeArgs).toMap ++ obj.t.asClass.get.typeEnv } diff --git a/src/col/vct/col/ast/expr/apply/ProcedureInvocationImpl.scala b/src/col/vct/col/ast/expr/apply/ProcedureInvocationImpl.scala index c1c6a8df05..bd6fc83b87 100644 --- a/src/col/vct/col/ast/expr/apply/ProcedureInvocationImpl.scala +++ b/src/col/vct/col/ast/expr/apply/ProcedureInvocationImpl.scala @@ -1,6 +1,6 @@ package vct.col.ast.expr.apply -import vct.col.ast.ProcedureInvocation +import vct.col.ast.{ProcedureInvocation, Type, Variable} import vct.col.print._ import vct.col.ast.ops.ProcedureInvocationOps @@ -15,4 +15,6 @@ trait ProcedureInvocationImpl[G] extends ProcedureInvocationOps[G] { this: Proce "(" ) <> Doc.args(args ++ outArgs) <> ")" <> DocUtil.givenYields(givenMap, yields) ) + + override def typeEnv: Map[Variable[G], Type[G]] = ref.decl.typeArgs.zip(typeArgs).toMap } \ No newline at end of file diff --git a/src/col/vct/col/ast/lang/pvl/PVLInvocationImpl.scala b/src/col/vct/col/ast/lang/pvl/PVLInvocationImpl.scala index 35a441abb7..55b8b73c5c 100644 --- a/src/col/vct/col/ast/lang/pvl/PVLInvocationImpl.scala +++ b/src/col/vct/col/ast/lang/pvl/PVLInvocationImpl.scala @@ -1,6 +1,6 @@ package vct.col.ast.lang.pvl -import vct.col.ast.{Applicable, PVLInvocation, TClass, TProcess, TResource, Type} +import vct.col.ast.{Applicable, ContractApplicable, PVLInvocation, TClass, TProcess, TResource, Type, Variable} import vct.col.print.{Ctx, Doc, DocUtil, Empty, Group, Text} import vct.col.resolve.ctx._ import vct.col.ast.ops.PVLInvocationOps @@ -11,20 +11,8 @@ trait PVLInvocationImpl[G] extends PVLInvocationOps[G] { this: PVLInvocation[G] case RefFunction(decl) => decl.returnType.particularize(decl.typeArgs.zip(typeArgs).toMap) case RefProcedure(decl) => decl.returnType case RefPredicate(_) => TResource() - case RefInstanceFunction(decl) => - val returnType = decl.returnType.particularize(decl.typeArgs.zip(typeArgs).toMap) - obj.flatMap { e => - e.t.asClass.map { tcls => - tcls.instantiate(returnType) - } - }.getOrElse(returnType) - case RefInstanceMethod(decl) => - val returnType = decl.returnType.particularize(decl.typeArgs.zip(typeArgs).toMap) - obj.flatMap { e => - e.t.asClass.map { tcls => - tcls.instantiate(returnType) - } - }.getOrElse(returnType) + case RefInstanceFunction(decl) => returnType(decl) + case RefInstanceMethod(decl) => returnType(decl) case RefInstancePredicate(_) => TResource() case RefADTFunction(decl) => decl.returnType case RefModelProcess(_) => TProcess() @@ -34,6 +22,15 @@ trait PVLInvocationImpl[G] extends PVLInvocationOps[G] { this: PVLInvocation[G] case BuiltinInstanceMethod(f) => f(obj.get)(args).t } + private def returnType(app: ContractApplicable[G]): Type[G] = + app.returnType.particularize(typeEnv(app)) + + // Take care to include type arguments of both the type of the object the method is called on, as well as + // the arguments given to the method call itself. + private def typeEnv(app: ContractApplicable[G]): Map[Variable[G], Type[G]] = + app.typeArgs.zip(typeArgs).toMap ++ + obj.flatMap(_.t.asClass).map(_.typeEnv).getOrElse(Map.empty) + override def layout(implicit ctx: Ctx): Doc = Group(Group(Group(obj.map(assoc(_) <> ".").getOrElse(Empty) <> method <> diff --git a/src/col/vct/col/ast/type/TClassImpl.scala b/src/col/vct/col/ast/type/TClassImpl.scala index 6cc09e175b..1cc807da7a 100644 --- a/src/col/vct/col/ast/type/TClassImpl.scala +++ b/src/col/vct/col/ast/type/TClassImpl.scala @@ -14,8 +14,8 @@ trait TClassImpl[G] extends TClassOps[G] { this: TClass[G] => def transSupportArrows(): Seq[(TClass[G], TClass[G])] = transSupportArrowsHelper(Set.empty) - override def layout(implicit ctx: Ctx): Doc = Text(ctx.name(cls)) <> ( - if (typeArgs.nonEmpty) Text("<") <> Doc.args(typeArgs) <> ">" else Empty) + override def layout(implicit ctx: Ctx): Doc = Group(Text(ctx.name(cls)) <> ( + if (typeArgs.nonEmpty) Text("<") <> Doc.args(typeArgs) <> ">" else Empty)) def typeEnv: Map[Variable[G], Type[G]] = cls.decl.typeArgs.zip(typeArgs).toMap diff --git a/src/col/vct/col/ast/type/typeclass/TypeImpl.scala b/src/col/vct/col/ast/type/typeclass/TypeImpl.scala index e958a24877..2ef0e5e572 100644 --- a/src/col/vct/col/ast/type/typeclass/TypeImpl.scala +++ b/src/col/vct/col/ast/type/typeclass/TypeImpl.scala @@ -44,7 +44,7 @@ trait TypeImpl[G] extends TypeFamilyOps[G] { this: Type[G] => override def succProvider: SuccessorsProvider[G, G] = IdentitySuccessorsProvider override def dispatch(t: Type[G]): Type[G] = t match { - case t @ TVar(Ref(v)) => substitutions.get(v).getOrElse(t) + case TVar(Ref(v)) => substitutions(v) case other => rewriteDefault(other) } } diff --git a/src/col/vct/col/ast/unsorted/ConstructorInvocationImpl.scala b/src/col/vct/col/ast/unsorted/ConstructorInvocationImpl.scala index e51feca90f..e526e9e0a3 100644 --- a/src/col/vct/col/ast/unsorted/ConstructorInvocationImpl.scala +++ b/src/col/vct/col/ast/unsorted/ConstructorInvocationImpl.scala @@ -1,11 +1,17 @@ package vct.col.ast.unsorted -import vct.col.ast.{ConstructorInvocation, TClass, Type} +import vct.col.ast.{ConstructorInvocation, TClass, Type, Variable, Class} import vct.col.ast.ops.ConstructorInvocationOps import vct.col.print._ trait ConstructorInvocationImpl[G] extends ConstructorInvocationOps[G] { this: ConstructorInvocation[G] => // override def layout(implicit ctx: Ctx): Doc = ??? - override def t: Type[G] = TClass(ref.decl.cls, classTypeArgs) + def cls: Class[G] = ref.decl.cls.decl + +// override def t: Type[G] = TClass(ref.decl.cls, classTypeArgs) + override def typeEnv: Map[Variable[G], Type[G]] = + (cls.typeArgs.zip(classTypeArgs) ++ ref.decl.typeArgs.zip(typeArgs)).toMap + + } From 2dfe6c1726302b6b8883960cbadce49c26fedd3b Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Fri, 26 Apr 2024 15:13:11 +0200 Subject: [PATCH 26/52] Generalize coerceArgs --- examples/concepts/generics/genericChannel.pvl | 4 +-- .../exceptional/InvokeMethodImpl.scala | 4 ++- .../exceptional/InvokeProcedureImpl.scala | 4 ++- .../ast/unsorted/InvokeConstructorImpl.scala | 7 +++- src/col/vct/col/resolve/lang/PVL.scala | 10 +++--- src/col/vct/col/resolve/lang/Util.scala | 35 ++++++++++++++----- .../vct/col/typerules/CoercingRewriter.scala | 23 ++++++------ 7 files changed, 55 insertions(+), 32 deletions(-) diff --git a/examples/concepts/generics/genericChannel.pvl b/examples/concepts/generics/genericChannel.pvl index 2699fd812a..2c1e27a6b2 100644 --- a/examples/concepts/generics/genericChannel.pvl +++ b/examples/concepts/generics/genericChannel.pvl @@ -54,6 +54,6 @@ void main() { intChanChan.writeValue(intChan); int i = intChan.readValue(); - int b = boolChan.readValue(); - int c = intChanChan.readValue(); + boolean b = boolChan.readValue(); + Chan c = intChanChan.readValue(); } \ No newline at end of file diff --git a/src/col/vct/col/ast/statement/exceptional/InvokeMethodImpl.scala b/src/col/vct/col/ast/statement/exceptional/InvokeMethodImpl.scala index 690085d91f..d8780fcae6 100644 --- a/src/col/vct/col/ast/statement/exceptional/InvokeMethodImpl.scala +++ b/src/col/vct/col/ast/statement/exceptional/InvokeMethodImpl.scala @@ -1,6 +1,6 @@ package vct.col.ast.statement.exceptional -import vct.col.ast.InvokeMethod +import vct.col.ast.{InvokeMethod, Variable, Type} import vct.col.print.{Ctx, Doc, DocUtil, Empty, Group, Text} import vct.col.ast.ops.InvokeMethodOps @@ -13,4 +13,6 @@ trait InvokeMethodImpl[G] extends InvokeMethodOps[G] { this: InvokeMethod[G] => "(" ) <> Doc.args(args ++ outArgs) <> ")" <> DocUtil.givenYields(givenMap, yields) <> ";" ) + + def typeEnv: Map[Variable[G], Type[G]] = ref.decl.typeArgs.zip(typeArgs).toMap ++ obj.t.asClass.get.typeEnv } diff --git a/src/col/vct/col/ast/statement/exceptional/InvokeProcedureImpl.scala b/src/col/vct/col/ast/statement/exceptional/InvokeProcedureImpl.scala index 8e2d2befea..41d32493d3 100644 --- a/src/col/vct/col/ast/statement/exceptional/InvokeProcedureImpl.scala +++ b/src/col/vct/col/ast/statement/exceptional/InvokeProcedureImpl.scala @@ -1,6 +1,6 @@ package vct.col.ast.statement.exceptional -import vct.col.ast.InvokeProcedure +import vct.col.ast.{InvokeProcedure, Variable, Type} import vct.col.print.{Ctx, Doc, DocUtil, Empty, Group, Text} import vct.col.ast.ops.InvokeProcedureOps @@ -21,4 +21,6 @@ trait InvokeProcedureImpl[G] extends InvokeProcedureOps[G] { this: InvokeProcedu case Ctx.Silver => layoutSilver case _ => layoutGeneric } + + def typeEnv: Map[Variable[G], Type[G]] = ref.decl.typeArgs.zip(typeArgs).toMap } diff --git a/src/col/vct/col/ast/unsorted/InvokeConstructorImpl.scala b/src/col/vct/col/ast/unsorted/InvokeConstructorImpl.scala index 9eed3639e1..96e38009da 100644 --- a/src/col/vct/col/ast/unsorted/InvokeConstructorImpl.scala +++ b/src/col/vct/col/ast/unsorted/InvokeConstructorImpl.scala @@ -1,9 +1,14 @@ package vct.col.ast.unsorted -import vct.col.ast.InvokeConstructor +import vct.col.ast.{Class, InvokeConstructor, Type, Variable} import vct.col.ast.ops.InvokeConstructorOps import vct.col.print._ trait InvokeConstructorImpl[G] extends InvokeConstructorOps[G] { this: InvokeConstructor[G] => // override def layout(implicit ctx: Ctx): Doc = ??? + + def cls: Class[G] = ref.decl.cls.decl + + def typeEnv: Map[Variable[G], Type[G]] = + (cls.typeArgs.zip(classTypeArgs) ++ ref.decl.typeArgs.zip(typeArgs)).toMap } diff --git a/src/col/vct/col/resolve/lang/PVL.scala b/src/col/vct/col/resolve/lang/PVL.scala index bf1a2f277f..0c2607860f 100644 --- a/src/col/vct/col/resolve/lang/PVL.scala +++ b/src/col/vct/col/resolve/lang/PVL.scala @@ -11,7 +11,7 @@ case object PVL { t match { case t @ TClass(Ref(cls), _) => val resolvedCons = cls.decls.collectFirst { - case cons: PVLConstructor[G] if Util.compat(args, cons.args, cons.typeArgs.zip(typeArgs).toMap ++ t.typeEnv) => RefPVLConstructor(cons) + case cons: PVLConstructor[G] if Util.compat(t.typeEnv, args, typeArgs, cons.args, cons.typeArgs) => RefPVLConstructor(cons) } args match { @@ -63,10 +63,10 @@ case object PVL { case ref: RefModelAction[G] if ref.name == method => ref case ref: RefModelProcess[G] if ref.name == method => ref }.orElse(Spec.builtinInstanceMethod(obj, method, blame)) - case TClass(ref, _) => ref.decl.declarations.flatMap(Referrable.from).collectFirst { - case ref: RefInstanceFunction[G] if ref.name == method && Util.compat(args, typeArgs, ref.decl) => ref - case ref: RefInstanceMethod[G] if ref.name == method && Util.compat(args, typeArgs, ref.decl) => ref - case ref: RefInstancePredicate[G] if ref.name == method && Util.compat(args, ref.decl.args) => ref + case t @ TClass(ref, _) => ref.decl.declarations.flatMap(Referrable.from).collectFirst { + case ref: RefInstanceFunction[G] if ref.name == method && Util.compat(t.typeEnv, args, typeArgs, ref.decl) => ref + case ref: RefInstanceMethod[G] if ref.name == method && Util.compat(t.typeEnv, args, typeArgs, ref.decl) => ref + case ref: RefInstancePredicate[G] if ref.name == method && Util.compat(t.typeEnv, args, ref.decl.args) => ref } case _ => PVL.builtinInstanceMethod(obj, method, args).orElse(Spec.builtinInstanceMethod(obj, method, blame)) } diff --git a/src/col/vct/col/resolve/lang/Util.scala b/src/col/vct/col/resolve/lang/Util.scala index bf927bc0e5..20627e0724 100644 --- a/src/col/vct/col/resolve/lang/Util.scala +++ b/src/col/vct/col/resolve/lang/Util.scala @@ -9,23 +9,40 @@ case object Util { def compat[G](args: Seq[Expr[G]], params: Seq[Variable[G]]): Boolean = compatTypes(args, params.map(_.t)) + def compat[G](typeEnv: Map[Variable[G], Type[G]], args: Seq[Expr[G]], params: Seq[Variable[G]]): Boolean = + compatTypes(typeEnv, args, params.map(_.t)) + def compatTypes[G](args: Seq[Expr[G]], types: Seq[Type[G]]): Boolean = + compatTypes[G](Map.empty[Variable[G], Type[G]], args, types) + + def compatTypes[G](typeEnv: Map[Variable[G], Type[G]], args: Seq[Expr[G]], types: Seq[Type[G]]): Boolean = args.size == types.size && types.zip(args).forall { - case (t, e) => t.superTypeOf(e.t) + case (t, e) => t.particularize(typeEnv).superTypeOf(e.t) } def compat[G](args: Seq[Expr[G]], typeArgs: Seq[Type[G]], genericInvokable: ContractApplicable[G]): Boolean = - compat(args, typeArgs, genericInvokable.args, genericInvokable.typeArgs) + compat[G](Map.empty[Variable[G], Type[G]], args, typeArgs, genericInvokable) + + def compat[G](typeEnv: Map[Variable[G], Type[G]], args: Seq[Expr[G]], typeArgs: Seq[Type[G]], genericInvokable: ContractApplicable[G]): Boolean = + compat(typeEnv, args, typeArgs, genericInvokable.args, genericInvokable.typeArgs) - def compat[G](args: Seq[Expr[G]], typeArgs: Seq[Type[G]], params: Seq[Variable[G]], typeParams: Seq[Variable[G]]): Boolean = + // TODO (RR): Clean up if not needed +// def compat[G](typeEnv: Map[Variable[G], Type[G]], args: Seq[Expr[G]], typeArgs: Seq[Type[G]], genericInvokable: ContractApplicable[G]): Boolean = +// compat(args, typeArgs, genericInvokable.args, genericInvokable.typeArgs) + + def compat[G](typeEnv: Map[Variable[G], Type[G]], args: Seq[Expr[G]], typeArgs: Seq[Type[G]], params: Seq[Variable[G]], typeParams: Seq[Variable[G]]): Boolean = { + val env = typeEnv ++ typeParams.zip(typeArgs).toMap args.size == params.size && typeArgs.size == typeParams.size && params.zip(args).forall { - case (v, e) => v.t.particularize(typeParams.zip(typeArgs).toMap).superTypeOf(e.t) + case (v, e) => v.t.particularize(env).superTypeOf(e.t) } + } - def compat[G](args: Seq[Expr[G]], params: Seq[Variable[G]], typeEnv: Map[Variable[G], Type[G]]): Boolean = - args.size == params.size && - params.zip(args).forall { - case (v, e) => v.t.particularize(typeEnv).superTypeOf(e.t) - } + // TODO (RR): Clean up if not needed + // def compat[G](typeEnv: Map[Variable[G], Type[G]], args: Seq[Expr[G]], typeArgs: Seq[Type[G]], genericInvokable: ContractApplicable[G]): Boolean = + // def compat[G](args: Seq[Expr[G]], params: Seq[Variable[G]], typeEnv: Map[Variable[G], Type[G]]): Boolean = +// args.size == params.size && +// params.zip(args).forall { +// case (v, e) => v.t.particularize(typeEnv).superTypeOf(e.t) +// } } diff --git a/src/col/vct/col/typerules/CoercingRewriter.scala b/src/col/vct/col/typerules/CoercingRewriter.scala index 005bc14ffa..023d3ff200 100644 --- a/src/col/vct/col/typerules/CoercingRewriter.scala +++ b/src/col/vct/col/typerules/CoercingRewriter.scala @@ -295,11 +295,9 @@ abstract class CoercingRewriter[Pre <: Generation]() extends BaseCoercingRewrite case (value, arg) => coerce(value, arg.t) } - def coerceArgs(args: Seq[Expr[Pre]], app: ContractApplicable[Pre], tArgs: Seq[Type[Pre]], tCls: Option[TClass[Pre]] = None, canCDemote: Boolean = false): Seq[Expr[Pre]] = + def coerceArgs(args: Seq[Expr[Pre]], app: ContractApplicable[Pre], typeEnv: Map[Variable[Pre], Type[Pre]] = Map.empty, canCDemote: Boolean = false): Seq[Expr[Pre]] = args.zip(app.args).map { - case (value, arg) => - val t = arg.t.particularize(app.typeArgs.zip(tArgs).toMap) - coerce(value, tCls.map(_.instantiate(t)).getOrElse(t), canCDemote) + case (value, arg) => coerce(value, arg.t.particularize(typeEnv), canCDemote) } def coerceGiven(givenMap: Seq[(Ref[Pre, Variable[Pre]], Expr[Pre])], canCDemote: Boolean = false): Seq[(Ref[Pre, Variable[Pre]], Expr[Pre])] = @@ -780,8 +778,7 @@ abstract class CoercingRewriter[Pre <: Generation]() extends BaseCoercingRewrite case StringConcat(left, right) => StringConcat(string(left), string(right)) case inv @ ConstructorInvocation(ref, classTypeArgs, args, outArgs, typeArgs, givenMap, yields) => - val tCls = TClass(ref.decl.cls, classTypeArgs) - ConstructorInvocation(ref, classTypeArgs, coerceArgs(args, ref.decl, typeArgs, Some(tCls), canCDemote = true), outArgs, typeArgs, coerceGiven(givenMap, canCDemote = true), coerceYields(yields, args.head))(inv.blame) + ConstructorInvocation(ref, classTypeArgs, coerceArgs(args, ref.decl, inv.typeEnv, canCDemote = true), outArgs, typeArgs, coerceGiven(givenMap, canCDemote = true), coerceYields(yields, args.head))(inv.blame) case acc @ CStructAccess(struct, field) => CStructAccess(struct, field)(acc.blame) case deref @ CStructDeref(struct, field) => @@ -829,7 +826,7 @@ abstract class CoercingRewriter[Pre <: Generation]() extends BaseCoercingRewrite case ForPermWithValue(binding, body) => ForPermWithValue(binding, bool(body)) case inv @ FunctionInvocation(ref, args, typeArgs, givenMap, yields) => - FunctionInvocation(ref, coerceArgs(args, ref.decl, typeArgs, canCDemote=true), typeArgs, coerceGiven(givenMap, canCDemote=true), coerceYields(yields, inv))(inv.blame) + FunctionInvocation(ref, coerceArgs(args, ref.decl, inv.typeEnv, canCDemote=true), typeArgs, coerceGiven(givenMap, canCDemote=true), coerceYields(yields, inv))(inv.blame) case get @ GetLeft(e) => GetLeft(either(e)._1)(get.blame) case get @ GetRight(e) => @@ -866,7 +863,7 @@ abstract class CoercingRewriter[Pre <: Generation]() extends BaseCoercingRewrite case InlinePattern(inner, parent, group) => InlinePattern(inner, parent, group) case inv @ InstanceFunctionInvocation(obj, ref, args, typeArgs, givenMap, yields) => - InstanceFunctionInvocation(cls(obj), ref, coerceArgs(args, ref.decl, typeArgs, obj.t.asClass, canCDemote=true), typeArgs, coerceGiven(givenMap, canCDemote=true), coerceYields(yields, inv))(inv.blame) + InstanceFunctionInvocation(cls(obj), ref, coerceArgs(args, ref.decl, inv.typeEnv, canCDemote=true), typeArgs, coerceGiven(givenMap, canCDemote=true), coerceYields(yields, inv))(inv.blame) case InstanceOf(value, typeValue) => InstanceOf(value, typeValue) case InstancePredicateApply(obj, ref, args, perm) => @@ -974,7 +971,7 @@ abstract class CoercingRewriter[Pre <: Generation]() extends BaseCoercingRewrite case MatrixSum(indices, mat) => MatrixSum(coerce(indices, TSeq[Pre](TInt())), coerce(mat, TSeq[Pre](TRational()))) case inv @ MethodInvocation(obj, ref, args, outArgs, typeArgs, givenMap, yields) => - MethodInvocation(obj, ref, coerceArgs(args, ref.decl, typeArgs, obj.t.asClass, canCDemote=true), outArgs, typeArgs, coerceGiven(givenMap, canCDemote=true), coerceYields(yields, inv))(inv.blame) + MethodInvocation(obj, ref, coerceArgs(args, ref.decl, inv.typeEnv, canCDemote=true), outArgs, typeArgs, coerceGiven(givenMap, canCDemote=true), coerceYields(yields, inv))(inv.blame) case Minus(left, right) => firstOk(e, s"Expected both operands to be numeric, but got ${left.t} and ${right.t}.", Minus(int(left), int(right)), @@ -1098,7 +1095,7 @@ abstract class CoercingRewriter[Pre <: Generation]() extends BaseCoercingRewrite case PredicateApply(ref, args, perm) => PredicateApply(ref, coerceArgs(args, ref.decl), rat(perm)) case inv @ ProcedureInvocation(ref, args, outArgs, typeArgs, givenMap, yields) => - ProcedureInvocation(ref, coerceArgs(args, ref.decl, typeArgs, canCDemote=true), outArgs, typeArgs, coerceGiven(givenMap, canCDemote=true), coerceYields(yields, inv))(inv.blame) + ProcedureInvocation(ref, coerceArgs(args, ref.decl, inv.typeEnv, canCDemote=true), outArgs, typeArgs, coerceGiven(givenMap, canCDemote=true), coerceYields(yields, inv))(inv.blame) case inv @ LlvmFunctionInvocation(ref, args, givenMap, yields) => LlvmFunctionInvocation(ref, args, givenMap, yields)(inv.blame) case inv @ LlvmAmbiguousFunctionInvocation(name, args, givenMap, yields) => @@ -1499,11 +1496,11 @@ abstract class CoercingRewriter[Pre <: Generation]() extends BaseCoercingRewrite case Instantiate(cls, dest) => Instantiate(cls, dest) case inv @ InvokeConstructor(ref, classTypeArgs, out, args, outArgs, typeArgs, givenMap, yields) => val cls = TClass(ref.decl.cls, classTypeArgs) - InvokeConstructor(ref, classTypeArgs, out, coerceArgs(args, ref.decl, typeArgs, Some(cls), canCDemote = true), outArgs, typeArgs, coerceGiven(givenMap, canCDemote = true), coerceYields(yields, args.head))(inv.blame) + InvokeConstructor(ref, classTypeArgs, out, coerceArgs(args, ref.decl, inv.typeEnv, canCDemote = true), outArgs, typeArgs, coerceGiven(givenMap, canCDemote = true), coerceYields(yields, args.head))(inv.blame) case inv @ InvokeProcedure(ref, args, outArgs, typeArgs, givenMap, yields) => - InvokeProcedure(ref, coerceArgs(args, ref.decl, typeArgs, canCDemote=true), outArgs, typeArgs, coerceGiven(givenMap,canCDemote=true), coerceYields(yields, args.head))(inv.blame) + InvokeProcedure(ref, coerceArgs(args, ref.decl, inv.typeEnv, canCDemote=true), outArgs, typeArgs, coerceGiven(givenMap,canCDemote=true), coerceYields(yields, args.head))(inv.blame) case inv @ InvokeMethod(obj, ref, args, outArgs, typeArgs, givenMap, yields) => - InvokeMethod(cls(obj), ref, coerceArgs(args, ref.decl, typeArgs, obj.t.asClass, canCDemote=true), outArgs, typeArgs, coerceGiven(givenMap,canCDemote=true), coerceYields(yields, args.head))(inv.blame) + InvokeMethod(cls(obj), ref, coerceArgs(args, ref.decl, inv.typeEnv, canCDemote=true), outArgs, typeArgs, coerceGiven(givenMap,canCDemote=true), coerceYields(yields, args.head))(inv.blame) case JavaLocalDeclarationStatement(decl) => JavaLocalDeclarationStatement(decl) case j @ Join(obj) => Join(cls(obj))(j.blame) case Label(decl, stat) => Label(decl, stat) From be70ec0f6e66c78fc362cb8e9eb80a7f8ee83ee7 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Fri, 26 Apr 2024 16:23:44 +0200 Subject: [PATCH 27/52] Add identiy type mappings for neutral type variables introduced by classes and adts --- examples/concepts/generics/genericChannel.pvl | 6 ++++++ src/col/vct/col/resolve/Resolve.scala | 6 ++++++ .../ctx/ReferenceResolutionContext.scala | 4 ++++ src/col/vct/col/resolve/lang/PVL.scala | 20 +++++++++---------- 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/examples/concepts/generics/genericChannel.pvl b/examples/concepts/generics/genericChannel.pvl index 2c1e27a6b2..cbab5a89e3 100644 --- a/examples/concepts/generics/genericChannel.pvl +++ b/examples/concepts/generics/genericChannel.pvl @@ -42,6 +42,12 @@ class Chan { return m; } + + context committed(this); + T noop(T t) { + writeValue(t); + return readValue(); + } } void main() { diff --git a/src/col/vct/col/resolve/Resolve.scala b/src/col/vct/col/resolve/Resolve.scala index 4caef670cc..398e92fc5e 100644 --- a/src/col/vct/col/resolve/Resolve.scala +++ b/src/col/vct/col/resolve/Resolve.scala @@ -284,6 +284,12 @@ case object ResolveReferences extends LazyLogging { case cls: Class[G] => ctx .copy(currentThis=Some(RefClass(cls))) .declare(cls.declarations) + // Ensure occurrences of type variables within the class that defines them are ignored when substituting + .appendTypeEnv(cls.typeArgs.map(v => (v, TVar[G](v.ref))).toMap) + case adt: AxiomaticDataType[G] => ctx + // Ensure occurrences of type variables within the adt that defines them are ignored when substituting + .declare(adt.declarations) + .appendTypeEnv(adt.typeArgs.map(v => (v, TVar[G](v.ref))).toMap) case seqProg: SeqProg[G] => ctx .copy(currentThis = Some(RefSeqProg(seqProg))) .declare(seqProg.decls) diff --git a/src/col/vct/col/resolve/ctx/ReferenceResolutionContext.scala b/src/col/vct/col/resolve/ctx/ReferenceResolutionContext.scala index e65fb99cdb..bda2b10f6e 100644 --- a/src/col/vct/col/resolve/ctx/ReferenceResolutionContext.scala +++ b/src/col/vct/col/resolve/ctx/ReferenceResolutionContext.scala @@ -27,6 +27,7 @@ case class ReferenceResolutionContext[G] javaBipGuards: ListMap[Expr[G], JavaMethod[G]] = ListMap[Expr[G], JavaMethod[G]](), // When true and resolving a local, guard names should also be considered javaBipGuardsEnabled: Boolean = false, + typeEnv: Map[Variable[G], Type[G]] = Map.empty[Variable[G], Type[G]], ) { def asTypeResolutionContext: TypeResolutionContext[G] = TypeResolutionContext(stack, currentJavaNamespace, None, Nil, externallyLoadedElements) @@ -58,4 +59,7 @@ case class ReferenceResolutionContext[G] def currentPkg: Option[JavaName[G]] = currentJavaNamespace.flatMap(_.pkg) def currentFqn: Option[JavaName[G]] = currentPkg.map(pkg => JavaName(pkg.names ++ currentJavaClass.map(cls => Seq(cls.name)).getOrElse(Seq()))(DiagnosticOrigin)) + + def appendTypeEnv(typeEnv: Map[Variable[G], Type[G]]): ReferenceResolutionContext[G] = + copy(typeEnv = this.typeEnv ++ typeEnv) } diff --git a/src/col/vct/col/resolve/lang/PVL.scala b/src/col/vct/col/resolve/lang/PVL.scala index 0c2607860f..9a75e57bb8 100644 --- a/src/col/vct/col/resolve/lang/PVL.scala +++ b/src/col/vct/col/resolve/lang/PVL.scala @@ -73,16 +73,16 @@ case object PVL { def findMethod[G](method: String, args: Seq[Expr[G]], typeArgs: Seq[Type[G]], ctx: ReferenceResolutionContext[G]): Option[PVLInvocationTarget[G]] = ctx.stack.flatten.collectFirst { - case ref: RefFunction[G] if ref.name == method && Util.compat(args, typeArgs, ref.decl) => ref - case ref: RefProcedure[G] if ref.name == method && Util.compat(args, typeArgs, ref.decl) => ref - case ref: RefPredicate[G] if ref.name == method && Util.compat(args, ref.decl.args) => ref - case ref: RefInstanceFunction[G] if ref.name == method && Util.compat(args, typeArgs, ref.decl) => ref - case ref: RefInstanceMethod[G] if ref.name == method && Util.compat(args, typeArgs, ref.decl) => ref - case ref: RefInstancePredicate[G] if ref.name == method && Util.compat(args, ref.decl.args) => ref - case ref: RefADTFunction[G] if ref.name == method && Util.compat(args, ref.decl.args) => ref - case ref: RefModelProcess[G] if ref.name == method && Util.compat(args, ref.decl.args) => ref - case ref: RefModelAction[G] if ref.name == method && Util.compat(args, ref.decl.args) => ref - case ref: RefProverFunction[G] if ref.name == method && Util.compat(args, ref.decl.args) => ref + case ref: RefFunction[G] if ref.name == method && Util.compat(ctx.typeEnv, args, typeArgs, ref.decl) => ref + case ref: RefProcedure[G] if ref.name == method && Util.compat(ctx.typeEnv, args, typeArgs, ref.decl) => ref + case ref: RefPredicate[G] if ref.name == method && Util.compat(ctx.typeEnv, args, ref.decl.args) => ref + case ref: RefInstanceFunction[G] if ref.name == method && Util.compat(ctx.typeEnv, args, typeArgs, ref.decl) => ref + case ref: RefInstanceMethod[G] if ref.name == method && Util.compat(ctx.typeEnv, args, typeArgs, ref.decl) => ref + case ref: RefInstancePredicate[G] if ref.name == method && Util.compat(ctx.typeEnv, args, ref.decl.args) => ref + case ref: RefADTFunction[G] if ref.name == method && Util.compat(ctx.typeEnv, args, ref.decl.args) => ref + case ref: RefModelProcess[G] if ref.name == method && Util.compat(ctx.typeEnv, args, ref.decl.args) => ref + case ref: RefModelAction[G] if ref.name == method && Util.compat(ctx.typeEnv, args, ref.decl.args) => ref + case ref: RefProverFunction[G] if ref.name == method && Util.compat(ctx.typeEnv, args, ref.decl.args) => ref } def builtinInstanceMethod[G](obj: Expr[G], method: String, args: Seq[Expr[G]]): Option[PVLBuiltinInstanceMethod[G]] = { From 19f897e4dcea02545ee172be9304f1c13fd8975c Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Fri, 26 Apr 2024 17:05:34 +0200 Subject: [PATCH 28/52] Fix breakage from merge --- src/parsers/antlr4/LangPVLParser.g4 | 4 ++-- src/parsers/vct/parsers/transform/PVLToCol.scala | 4 ---- src/rewrite/vct/rewrite/cfg/Utils.scala | 6 +++--- src/rewrite/vct/rewrite/rasi/AbstractProcess.scala | 2 +- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/parsers/antlr4/LangPVLParser.g4 b/src/parsers/antlr4/LangPVLParser.g4 index fa05651f42..155bcbe788 100644 --- a/src/parsers/antlr4/LangPVLParser.g4 +++ b/src/parsers/antlr4/LangPVLParser.g4 @@ -11,7 +11,7 @@ programDecl : valGlobalDeclaration | declClass | enumDecl | method | declVeyMont enumDecl : 'enum' identifier '{' identifierList? ','? '}' ; declClass - : contract 'class' identifier typeVars? '{' classDecl* '}' + : contract 'class' identifier declaredTypeArgs? '{' classDecl* '}' ; declVeyMontSeqProg : contract 'seq_program' identifier '(' args? ')' '{' seqProgDecl* '}'; @@ -34,7 +34,7 @@ field : finalFlag? type identifierList ';' ; method : contract valModifier* type identifier declaredTypeArgs? '(' args? ')' methodBody ; methodBody : ';' | block ; -constructor : contract 'constructor' typeVars? '(' args? ')' methodBody ; +constructor : contract 'constructor' declaredTypeArgs? '(' args? ')' methodBody ; runMethod : contract 'run' methodBody ; diff --git a/src/parsers/vct/parsers/transform/PVLToCol.scala b/src/parsers/vct/parsers/transform/PVLToCol.scala index baf5c499bb..a638d0a93e 100644 --- a/src/parsers/vct/parsers/transform/PVLToCol.scala +++ b/src/parsers/vct/parsers/transform/PVLToCol.scala @@ -90,10 +90,6 @@ case class PVLToCol[G](override val baseOrigin: Origin, })) } - def convert(implicit names: TypeVarsContext): Seq[Variable[G]] = names match { - case TypeVars0(_, names, _) => convertVars(names) - } - def typeVar(identifier: IdentifierContext): Variable[G] = new Variable(TType(TAnyValue()))(origin(identifier).sourceName(convert(identifier))) diff --git a/src/rewrite/vct/rewrite/cfg/Utils.scala b/src/rewrite/vct/rewrite/cfg/Utils.scala index 98720e81cc..ff00d577bd 100644 --- a/src/rewrite/vct/rewrite/cfg/Utils.scala +++ b/src/rewrite/vct/rewrite/cfg/Utils.scala @@ -15,8 +15,8 @@ object Utils { case Then(value, post) => find_all_subexpressions(value) :+ post case mi @ MethodInvocation(obj, ref, args, outArgs, typeArgs, givenMap, yields) => Seq(InvokeMethod(obj, ref, args, outArgs, typeArgs, givenMap, yields)(mi.blame)(mi.o)) - case ci @ ConstructorInvocation(ref, args, outArgs, typeArgs, givenMap, yields) => - Seq(InvokeConstructor(ref, get_out_variable(ref.decl.cls, ci.o), args, outArgs, typeArgs, givenMap, yields)(ci.blame)(ci.o)) + case ci @ ConstructorInvocation(ref, classTypeArgs, args, outArgs, typeArgs, givenMap, yields) => + Seq(InvokeConstructor(ref, classTypeArgs, get_out_variable(ref.decl.cls, ci.o), args, outArgs, typeArgs, givenMap, yields)(ci.blame)(ci.o)) case pi @ ProcedureInvocation(ref, args, outArgs, typeArgs, givenMap, yields) => Seq(InvokeProcedure(ref, args, outArgs, typeArgs, givenMap, yields)(pi.blame)(pi.o)) case no @ NewObject(cls) => Seq(Instantiate(cls, get_out_variable(cls, no.o))(no.o)) @@ -53,7 +53,7 @@ object Utils { case _ => expr.subnodes.collect{ case ex: Expr[G] => ex }.flatMap(e => find_all_subexpressions(e)) } - private def get_out_variable[G](cls: Ref[G, Class[G]], o: Origin): Local[G] = Local(new DirectRef[G, Variable[G]](new Variable(TClass(cls))(o)))(o) + private def get_out_variable[G](cls: Ref[G, Class[G]], o: Origin): Local[G] = Local(new DirectRef[G, Variable[G]](new Variable(TClass(cls, Seq()))(o)))(o) def find_all_cases[G](body: Statement[G], index: GlobalIndex[G]): Seq[(SwitchCase[G], GlobalIndex[G])] = body match { case Switch(_, _) => Seq() diff --git a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala index 0f9de6acc0..38f25963ec 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala @@ -61,7 +61,7 @@ case class AbstractProcess[G](obj: Expr[G]) { case Some(_) => viable_edges(succ, state).map(e => take_edge(e, state)) case None => viable_edges(succ, state).flatMap(e => state.with_postcondition(ref.decl.contract.ensures, Map.from(ref.decl.args.zip(args))).map(s => take_edge(e, s))) }) - case InvokeConstructor(ref, _, args, _, _, _, _) => (true, ref.decl.body match { + case InvokeConstructor(ref, _, _, args, _, _, _, _) => (true, ref.decl.body match { case Some(_) => viable_edges(succ, state).map(e => take_edge(e, state)) case None => viable_edges(succ, state).flatMap(e => state.with_postcondition(ref.decl.contract.ensures, Map.from(ref.decl.args.zip(args))).map(s => take_edge(e, s))) }) From 888bbbd1bc090cc8c3e3f2bc2ec89ba370f2c1c7 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Tue, 30 Apr 2024 11:22:10 +0200 Subject: [PATCH 29/52] Make typeenv optional for methodinvocation+class --- src/col/vct/col/ast/expr/apply/MethodInvocationImpl.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/col/vct/col/ast/expr/apply/MethodInvocationImpl.scala b/src/col/vct/col/ast/expr/apply/MethodInvocationImpl.scala index e984739352..4b9c90cf07 100644 --- a/src/col/vct/col/ast/expr/apply/MethodInvocationImpl.scala +++ b/src/col/vct/col/ast/expr/apply/MethodInvocationImpl.scala @@ -17,5 +17,8 @@ trait MethodInvocationImpl[G] extends MethodInvocationOps[G] with InvocationImpl ) <> Doc.args(args ++ outArgs) <> ")" <> DocUtil.givenYields(givenMap, yields) ) - override def typeEnv: Map[Variable[G], Type[G]] = ref.decl.typeArgs.zip(typeArgs).toMap ++ obj.t.asClass.get.typeEnv + override def typeEnv: Map[Variable[G], Type[G]] = ref.decl.typeArgs.zip(typeArgs).toMap ++ + // Optionally, if the obj is a class, include its typeenv. Optionality is required because in choreographies method invocations are also + // used, and choreographies do not have a typeenv (yet). This will be refactored into a separate node in the short term. + obj.t.asClass.map(_.typeEnv).getOrElse(Map.empty) } From 35575817d95f947edc985830107ad095fd9f51ca Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Tue, 30 Apr 2024 13:51:07 +0200 Subject: [PATCH 30/52] Make sure EStart/EEnd nodes are compared by reference. Ensure procedure return type is properly instantiated --- src/col/vct/col/print/Doc.scala | 16 ++++++++++++++-- .../rewrite/ResolveExpressionSideEffects.scala | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/col/vct/col/print/Doc.scala b/src/col/vct/col/print/Doc.scala index ed5fcedc93..e433e1dd54 100644 --- a/src/col/vct/col/print/Doc.scala +++ b/src/col/vct/col/print/Doc.scala @@ -122,8 +122,20 @@ sealed trait Doc extends Show { } case class EText(text: String) extends Elem case class ELine(indent: Int) extends Elem - case class EStart(node: Node[_]) extends Elem - case class EEnd(node: Node[_]) extends Elem + case class EStart(node: Node[_]) extends Elem { + override def hashCode(): Int = System.identityHashCode(node) + override def equals(obj: Any): Boolean = obj match { + case EStart(other) => node.eq(other) + case _ => false + } + } + case class EEnd(node: Node[_]) extends Elem { + override def hashCode(): Int = System.identityHashCode(node) ^ 1 + override def equals(obj: Any): Boolean = obj match { + case EEnd(other) => node.eq(other) + case _ => false + } + } def lazyListLen(list: LazyList[Elem]): Int = list match { diff --git a/src/rewrite/vct/rewrite/ResolveExpressionSideEffects.scala b/src/rewrite/vct/rewrite/ResolveExpressionSideEffects.scala index 3693421223..c0b0fcc48d 100644 --- a/src/rewrite/vct/rewrite/ResolveExpressionSideEffects.scala +++ b/src/rewrite/vct/rewrite/ResolveExpressionSideEffects.scala @@ -497,7 +497,7 @@ case class ResolveExpressionSideEffects[Pre <: Generation]() extends Rewriter[Pr givenMap.map { case (Ref(v), e) => (succ(v), inlined(e)) }, yields.map { case (e, Ref(v)) => (inlined(e), succ(v)) }, )(inv.blame)(e.o)) - stored(res.get(SideEffectOrigin), method.returnType) + stored(res.get(SideEffectOrigin), method.returnType.particularize(inv.typeEnv)) case inv @ ConstructorInvocation(Ref(cons), classTypeArgs, args, outArgs, typeArgs, givenMap, yields) => val typ = TClass[Post](succ(cons.cls.decl), classTypeArgs.map(dispatch)) val res = new Variable[Post](typ)(ResultVar) From a0ecf71055f3d7700efca66e353365270eeb6066 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Tue, 30 Apr 2024 14:55:07 +0200 Subject: [PATCH 31/52] Complete ancient renaming --- src/llvm/tools/vcllvm/VCLLVM.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/llvm/tools/vcllvm/VCLLVM.cpp b/src/llvm/tools/vcllvm/VCLLVM.cpp index f79d61b88a..9bf488a23c 100644 --- a/src/llvm/tools/vcllvm/VCLLVM.cpp +++ b/src/llvm/tools/vcllvm/VCLLVM.cpp @@ -31,7 +31,7 @@ col::Program sampleCol(bool returnBool) { col::BooleanValue *lockInvariant = vctClass->mutable_intrinsic_lock_invariant()->mutable_boolean_value(); lockInvariant->set_value(true); // class>method - col::ClassDeclaration *methodDeclaration = vctClass->add_declarations(); + col::ClassDeclaration *methodDeclaration = vctClass->add_decls(); col::InstanceMethod *method = methodDeclaration->mutable_instance_method(); llvm2Col::setColNodeId(method); // class>method>return_type From 0c7b5bb013e9d2571987f591ed12b1a2d907f93d Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Tue, 30 Apr 2024 15:20:32 +0200 Subject: [PATCH 32/52] Cover all examples --- .../test/integration/examples/TechnicalGenericsSpec.scala | 7 +++++++ .../test/integration/examples/TechnicalVeyMontSpec.scala | 2 ++ test/main/vct/test/integration/meta/ExampleCoverage.scala | 2 ++ 3 files changed, 11 insertions(+) create mode 100644 test/main/vct/test/integration/examples/TechnicalGenericsSpec.scala diff --git a/test/main/vct/test/integration/examples/TechnicalGenericsSpec.scala b/test/main/vct/test/integration/examples/TechnicalGenericsSpec.scala new file mode 100644 index 0000000000..068c427757 --- /dev/null +++ b/test/main/vct/test/integration/examples/TechnicalGenericsSpec.scala @@ -0,0 +1,7 @@ +package vct.test.integration.examples + +import vct.test.integration.helper.VercorsSpec + +class TechnicalGenericsSpec extends VercorsSpec { + vercors should verify using silicon example "technical/java/generics/MyRetention.java" +} diff --git a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala index 4ec9894d52..fe7122e44f 100644 --- a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala +++ b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala @@ -888,4 +888,6 @@ class TechnicalVeyMontSpec extends VercorsSpec { } } """) + + vercors should verify using silicon example "technical/veymont/genericEndpoints.pvl" } \ No newline at end of file diff --git a/test/main/vct/test/integration/meta/ExampleCoverage.scala b/test/main/vct/test/integration/meta/ExampleCoverage.scala index 64846eed90..cacebae9fb 100644 --- a/test/main/vct/test/integration/meta/ExampleCoverage.scala +++ b/test/main/vct/test/integration/meta/ExampleCoverage.scala @@ -20,6 +20,7 @@ class ExampleCoverage extends AnyFlatSpec { new FinalConstExprSpec(), new ExtractSpec(), new ForkJoinSpec(), + new GenericsExamplesSpec(), new GotoSpec(), new GpgpuSpec(), new JavaBipSpec(), @@ -46,6 +47,7 @@ class ExampleCoverage extends AnyFlatSpec { new TechnicalAbruptSpec(), new TechnicalEnumSpec(), new TechnicalFloatSpec(), + new TechnicalGenericsSpec(), new TechnicalJavaBipSpec(), new TechnicalJavaSpec(), new TechnicalSpec(), From 7a4db5ece98c837ecfb16578fc5f8d4ee3c34634 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Tue, 30 Apr 2024 15:40:47 +0200 Subject: [PATCH 33/52] Fix genericEndpoints.pvl --- examples/technical/veymont/genericEndpoints.pvl | 1 + src/col/vct/col/ast/lang/pvl/PVLInvocationImpl.scala | 4 ++-- src/col/vct/col/ast/lang/pvl/PVLLocalImpl.scala | 2 +- src/col/vct/col/ast/unsorted/PVLEndpointImpl.scala | 4 +++- src/col/vct/col/typerules/CoercingRewriter.scala | 2 +- .../vct/test/integration/examples/TechnicalVeyMontSpec.scala | 2 +- 6 files changed, 9 insertions(+), 6 deletions(-) diff --git a/examples/technical/veymont/genericEndpoints.pvl b/examples/technical/veymont/genericEndpoints.pvl index 874bf54447..0e5ce04d3f 100644 --- a/examples/technical/veymont/genericEndpoints.pvl +++ b/examples/technical/veymont/genericEndpoints.pvl @@ -14,6 +14,7 @@ seq_program genericTest() { endpoint a = Role(); endpoint b = Role(); + requires a != b; seq_run { a.i := a.toInt(a.t); communicate a.i -> b.i; diff --git a/src/col/vct/col/ast/lang/pvl/PVLInvocationImpl.scala b/src/col/vct/col/ast/lang/pvl/PVLInvocationImpl.scala index 55b8b73c5c..788fda9029 100644 --- a/src/col/vct/col/ast/lang/pvl/PVLInvocationImpl.scala +++ b/src/col/vct/col/ast/lang/pvl/PVLInvocationImpl.scala @@ -8,8 +8,8 @@ import vct.col.ref.Ref trait PVLInvocationImpl[G] extends PVLInvocationOps[G] { this: PVLInvocation[G] => override lazy val t: Type[G] = ref.get match { - case RefFunction(decl) => decl.returnType.particularize(decl.typeArgs.zip(typeArgs).toMap) - case RefProcedure(decl) => decl.returnType + case RefFunction(decl) => returnType(decl) + case RefProcedure(decl) => returnType(decl) case RefPredicate(_) => TResource() case RefInstanceFunction(decl) => returnType(decl) case RefInstanceMethod(decl) => returnType(decl) diff --git a/src/col/vct/col/ast/lang/pvl/PVLLocalImpl.scala b/src/col/vct/col/ast/lang/pvl/PVLLocalImpl.scala index 1ce667cc92..f22e9a1791 100644 --- a/src/col/vct/col/ast/lang/pvl/PVLLocalImpl.scala +++ b/src/col/vct/col/ast/lang/pvl/PVLLocalImpl.scala @@ -15,7 +15,7 @@ trait PVLLocalImpl[G] extends PVLLocalOps[G] { this: PVLLocal[G] => case ref: RefField[G] => ref.decl.t case ref: RefModelField[G] => ref.decl.t case ref: RefEndpoint[G] => ref.decl.t - case ref: RefPVLEndpoint[G] => TClass[G](ref.decl.cls.decl.ref, Seq()) + case ref: RefPVLEndpoint[G] => ref.decl.t case RefEnumConstant(enum, _) => TEnum(enum.get.ref) } diff --git a/src/col/vct/col/ast/unsorted/PVLEndpointImpl.scala b/src/col/vct/col/ast/unsorted/PVLEndpointImpl.scala index dda7fafa07..94a88bb08e 100644 --- a/src/col/vct/col/ast/unsorted/PVLEndpointImpl.scala +++ b/src/col/vct/col/ast/unsorted/PVLEndpointImpl.scala @@ -1,9 +1,11 @@ package vct.col.ast.unsorted -import vct.col.ast.PVLEndpoint +import vct.col.ast.{PVLEndpoint, TClass, Type} import vct.col.ast.ops.PVLEndpointOps import vct.col.print._ trait PVLEndpointImpl[G] extends PVLEndpointOps[G] { this: PVLEndpoint[G] => // override def layout(implicit ctx: Ctx): Doc = ??? + + def t: TClass[G] = TClass(cls, typeArgs) } diff --git a/src/col/vct/col/typerules/CoercingRewriter.scala b/src/col/vct/col/typerules/CoercingRewriter.scala index f1bbdb1f60..fc54d5a725 100644 --- a/src/col/vct/col/typerules/CoercingRewriter.scala +++ b/src/col/vct/col/typerules/CoercingRewriter.scala @@ -1546,7 +1546,7 @@ abstract class CoercingRewriter[Pre <: Generation]() extends BaseCoercingRewrite case c @ Communicate(r, s) if r.field.decl.t == s.field.decl.t => Communicate(r, s)(c.blame) case comm@Communicate(r, s) => throw IncoercibleExplanation(comm, s"The receiver should have type ${s.field.decl.t}, but actually has type ${r.field.decl.t}.") case a @ PVLSeqAssign(r, f, v) => - try { PVLSeqAssign(r, f, coerce(v, f.decl.t))(a.blame) } catch { + try { PVLSeqAssign(r, f, coerce(v, r.decl.t.instantiate(f.decl.t)))(a.blame) } catch { case err: Incoercible => println(err.text) throw err diff --git a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala index fe7122e44f..3d760fe1c0 100644 --- a/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala +++ b/test/main/vct/test/integration/examples/TechnicalVeyMontSpec.scala @@ -889,5 +889,5 @@ class TechnicalVeyMontSpec extends VercorsSpec { } """) - vercors should verify using silicon example "technical/veymont/genericEndpoints.pvl" + vercors should verify using silicon flag "--veymont-generate-permissions" example "technical/veymont/genericEndpoints.pvl" } \ No newline at end of file From f8051a980a31b4e9e797e1f7f7be20b560786794 Mon Sep 17 00:00:00 2001 From: Bob Rubbens Date: Tue, 30 Apr 2024 16:35:03 +0200 Subject: [PATCH 34/52] Reorder some cases to avoid lazyness inspection --- src/rewrite/vct/rewrite/lang/LangCPPToCol.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/rewrite/vct/rewrite/lang/LangCPPToCol.scala b/src/rewrite/vct/rewrite/lang/LangCPPToCol.scala index 34b970590a..3f3e8d82b3 100644 --- a/src/rewrite/vct/rewrite/lang/LangCPPToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangCPPToCol.scala @@ -1449,14 +1449,12 @@ case class LangCPPToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) extends L private def removeKernelClassInstancePermissions(e: Expr[Post]): Expr[Post] = { implicit val o = e.o e match { - case _ if e.t != TResource[Post]() => e case s@Starall(bindings, triggers, body) => Starall(bindings, triggers, removeKernelClassInstancePermissions(body))(s.blame) case ModelAbstractState(model, state) => ModelAbstractState(removeKernelClassInstancePermissions(model), removeKernelClassInstancePermissions(state)) case ModelState(model, perm, state) => ModelState(removeKernelClassInstancePermissions(model), removeKernelClassInstancePermissions(perm), removeKernelClassInstancePermissions(state)) case s@Scale(expr, res) => Scale(removeKernelClassInstancePermissions(expr), removeKernelClassInstancePermissions(res))(s.blame) case Star(left, right) => Star(removeKernelClassInstancePermissions(left), removeKernelClassInstancePermissions(right)) case Wand(left, right) => Wand(removeKernelClassInstancePermissions(left), removeKernelClassInstancePermissions(right)) - case Implies(left, right) => Implies(removeKernelClassInstancePermissions(left), removeKernelClassInstancePermissions(right)) case ActionPerm(Deref(obj, _), _) if obj.equals(this.currentThis.get) => tt case ModelPerm(Deref(obj, _), _) if obj.equals(this.currentThis.get) => tt @@ -1466,6 +1464,11 @@ case class LangCPPToCol[Pre <: Generation](rw: LangSpecificToCol[Pre]) extends L case Perm(AmbiguousLocation(ArraySubscript(Deref(obj, _), _)), _) if obj.equals(this.currentThis.get) => tt case PointsTo(AmbiguousLocation(ArraySubscript(Deref(obj, _), _)), _, _) if obj.equals(this.currentThis.get) => tt case Value(AmbiguousLocation(ArraySubscript(Deref(obj, _), _))) if obj.equals(this.currentThis.get) => tt + + // Inspecting the type of Expr[Post] is normally dangerous, as you might need to dereference LazyRefs. However, + // the type system here should guarantee e.t is either a TResource or a TBool, so it's safe to inspect it. + case Implies(left, right) if e.t == TResource[Post]() => Implies(removeKernelClassInstancePermissions(left), removeKernelClassInstancePermissions(right)) + case e => e } } From 59c7ae9670ce7d07c789813e93ca52d78fc2d022 Mon Sep 17 00:00:00 2001 From: Pieter Bos Date: Tue, 30 Apr 2024 16:50:57 +0200 Subject: [PATCH 35/52] fix path separator on windows --- mill-build/util/src/util/ReleaseModule.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mill-build/util/src/util/ReleaseModule.scala b/mill-build/util/src/util/ReleaseModule.scala index e47124dbdd..3afcb66e92 100644 --- a/mill-build/util/src/util/ReleaseModule.scala +++ b/mill-build/util/src/util/ReleaseModule.scala @@ -178,7 +178,7 @@ trait ReleaseModule extends JavaModule with SeparatePackedResourcesModule { os.walk(dest / "deps" / "unix", preOrder = false).foreach(os.remove) os.walk(dest / "deps" / "darwin", preOrder = false).foreach(os.remove) - os.write(dest / ".classpath", "-cp " + (jar +: res).map(_.relativeTo(dest)).map(_.toString).mkString(":")) + os.write(dest / ".classpath", "-cp " + (jar +: res).map(_.relativeTo(dest)).map(_.toString).mkString(";")) os.write(dest / winExecutableName(), s"""@echo off From d3f443884b0221fc01a882ec155401a5efe47979 Mon Sep 17 00:00:00 2001 From: Pieter Bos Date: Wed, 1 May 2024 12:54:50 +0200 Subject: [PATCH 36/52] add termination measure for set_choose functions --- res/universal/res/adt/set_compat.pvl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/res/universal/res/adt/set_compat.pvl b/res/universal/res/adt/set_compat.pvl index fa731b13e3..2fd1f26b1a 100644 --- a/res/universal/res/adt/set_compat.pvl +++ b/res/universal/res/adt/set_compat.pvl @@ -1,7 +1,9 @@ requires !xs.isEmpty; ensures \result \in xs; +decreases; pure T set_choose(set xs); requires !xs.isEmpty; ensures \result \in xs; +decreases; T set_choose_fresh(set xs); \ No newline at end of file From dae3571ff494a2d3ea9c7b92a91a91e649507320 Mon Sep 17 00:00:00 2001 From: Pieter Bos Date: Fri, 3 May 2024 17:12:47 +0200 Subject: [PATCH 37/52] switch all reasonable stuff to java.nio --- src/col/vct/col/origin/Origin.scala | 14 ----- src/col/vct/col/resolve/lang/Java.scala | 17 ++---- src/hre/hre/io/RWFile.scala | 14 +++-- src/hre/hre/io/Readable.scala | 5 +- src/hre/hre/perf/Profile.scala | 4 +- src/hre/hre/resource/ResourceUtil.scala | 5 +- src/hre/hre/util/Notifier.scala | 5 +- src/main/vct/importer/JavaLibraryLoader.scala | 4 +- src/main/vct/main/stages/Backend.scala | 16 ++--- src/main/vct/options/types/PathOrStd.scala | 9 +-- src/parsers/vct/parsers/ColCPPParser.scala | 60 ++++++++++--------- src/parsers/vct/parsers/ColCParser.scala | 59 +++++++++--------- .../protobuf/ForwardingByteString.scala | 2 +- src/viper/viper/api/SilverTreeCompare.scala | 38 +++++++----- .../viper/api/backend/SilverBackend.scala | 3 +- 15 files changed, 126 insertions(+), 129 deletions(-) diff --git a/src/col/vct/col/origin/Origin.scala b/src/col/vct/col/origin/Origin.scala index 53d40657b8..aed9697ab3 100644 --- a/src/col/vct/col/origin/Origin.scala +++ b/src/col/vct/col/origin/Origin.scala @@ -5,12 +5,8 @@ import hre.io.{LiteralReadable, Readable} import vct.result.HasContext import vct.result.Message.HR -import java.io.{Reader, StringReader} -import java.nio.file.Paths -import scala.annotation.tailrec import scala.collection.mutable.ArrayBuffer import scala.reflect.ClassTag -import scala.util.Try case object Origin { @@ -458,14 +454,4 @@ case class BlameCollector() extends Blame[VerificationFailure] { override def blame(error: VerificationFailure): Unit = errs += error -} - -case object RedirectOrigin { - case class StringReadable(data: String, fileName:String="") extends Readable { - override def isRereadable: Boolean = true - - override protected def getReader: Reader = - new StringReader(data) - } - } \ No newline at end of file diff --git a/src/col/vct/col/resolve/lang/Java.scala b/src/col/vct/col/resolve/lang/Java.scala index 4238dedc6f..804f84ab22 100644 --- a/src/col/vct/col/resolve/lang/Java.scala +++ b/src/col/vct/col/resolve/lang/Java.scala @@ -1,26 +1,21 @@ package vct.col.resolve.lang import com.typesafe.scalalogging.LazyLogging -import hre.io.RWFile import hre.util.FuncTools -import vct.col.ast.lang.java.JavaAnnotationEx import vct.col.ast.`type`.typeclass.TFloats -import vct.col.ast.{ADTFunction, ApplicableContract, AxiomaticDataType, BipPortType, Block, CType, EmptyProcess, Expr, JavaAnnotation, JavaAnnotationInterface, JavaClass, JavaClassDeclaration, JavaClassOrInterface, JavaConstructor, JavaFields, JavaFinal, JavaImport, JavaInterface, JavaMethod, JavaModifier, JavaName, JavaNamedType, JavaNamespace, JavaParam, JavaStatic, JavaTClass, JavaType, JavaVariableDeclaration, JavaWildcard, LiteralBag, LiteralMap, LiteralSeq, LiteralSet, Node, Null, OptNone, PVLType, TAnyClass, TArray, TAxiomatic, TBag, TBool, TBoundedInt, TChar, TClass, TEither, TEnum, TFloat, TFraction, TInt, TMap, TMatrix, TModel, TNotAValue, TNothing, TNull, TOption, TPointer, TProcess, TProverType, TRational, TRef, TResource, TSeq, TSet, TString, TTuple, TType, TUnion, TVar, TVoid, TZFraction, Type, UnitAccountedPredicate, Variable, Void} +import vct.col.ast.lang.java.JavaAnnotationEx +import vct.col.ast.{ApplicableContract, AxiomaticDataType, BipPortType, Block, EmptyProcess, Expr, JavaAnnotation, JavaAnnotationInterface, JavaClass, JavaClassDeclaration, JavaClassOrInterface, JavaConstructor, JavaFields, JavaFinal, JavaImport, JavaInterface, JavaMethod, JavaModifier, JavaName, JavaNamedType, JavaNamespace, JavaParam, JavaStatic, JavaTClass, JavaVariableDeclaration, JavaWildcard, LiteralBag, LiteralMap, LiteralSeq, LiteralSet, Node, Null, OptNone, TAnyClass, TArray, TBag, TBool, TChar, TClass, TEnum, TFloat, TInt, TMap, TModel, TNotAValue, TNull, TOption, TPointer, TProcess, TRational, TRef, TSeq, TSet, TString, TUnion, TVoid, TZFraction, Type, UnitAccountedPredicate, Variable, Void} import vct.col.origin._ import vct.col.ref.Ref +import vct.col.resolve.Resolve.{getLit, isBip} import vct.col.resolve.ResolveTypes.JavaClassPathEntry import vct.col.resolve._ import vct.col.resolve.ctx._ import vct.col.typerules.Types -import vct.col.resolve.lang.JavaAnnotationData.{BipComponent, BipData, BipGuard, BipInvariant, BipTransition} -import vct.col.resolve.Resolve.{getLit, isBip} -import vct.result.VerificationError.{Unreachable, UserError} import vct.col.util.AstBuildHelpers._ import vct.result.VerificationError.{Unreachable, UserError} -import java.io.File import java.lang.reflect.{Modifier, Parameter, TypeVariable} -import java.lang.reflect import java.nio.file.Path import scala.annotation.tailrec import scala.collection.mutable @@ -273,11 +268,11 @@ case object Java extends LazyLogging { ns <- ctx.namespace readable <- Some(ns.o.find[ReadableOrigin].get.readable) file <- readable.underlyingFile - baseFile <- ns.pkg.getOrElse(JavaName(Nil)).names.foldRight[Option[File]](Option(file.getParentFile)) { - case (name, Some(file)) if file.getName == name => Option(file.getParentFile) + baseFile <- ns.pkg.getOrElse(JavaName(Nil)).names.foldRight[Option[Path]](Option(file.getParent)) { + case (name, Some(file)) if file.getName == name => Option(file.getParent) case _ => None } - } yield baseFile.toPath + } yield baseFile case JavaClassPathEntry.Path(root) => Some(root) } diff --git a/src/hre/hre/io/RWFile.scala b/src/hre/hre/io/RWFile.scala index a8e42782c9..fc43fad470 100644 --- a/src/hre/hre/io/RWFile.scala +++ b/src/hre/hre/io/RWFile.scala @@ -1,16 +1,20 @@ package hre.io -import java.io.{File, FileReader, FileWriter, Reader, Writer} +import java.io.{Reader, Writer} import java.nio.charset.StandardCharsets +import java.nio.file.{Files, Path} -case class RWFile(file: File) extends InMemoryCachedReadable with Writeable { - override def underlyingFile: Option[File] = Some(file) +case class RWFile(file: Path) extends InMemoryCachedReadable with Writeable { + override def underlyingFile: Option[Path] = Some(file) override def fileName: String = file.toString override def isRereadable: Boolean = true override protected def getWriter: Writer = { invalidate() - new FileWriter(file, StandardCharsets.UTF_8) + Files.newBufferedWriter(file, StandardCharsets.UTF_8) + } + + override protected def getReaderImpl: Reader = { + Files.newBufferedReader(file, StandardCharsets.UTF_8) } - override protected def getReaderImpl: Reader = new FileReader(file, StandardCharsets.UTF_8) } diff --git a/src/hre/hre/io/Readable.scala b/src/hre/hre/io/Readable.scala index eb894200a5..0da6cf950b 100644 --- a/src/hre/hre/io/Readable.scala +++ b/src/hre/hre/io/Readable.scala @@ -1,13 +1,14 @@ package hre.io -import java.io.{File, Reader} +import java.io.Reader import java.nio.CharBuffer +import java.nio.file.{Files, Path} import java.util.Scanner import scala.collection.mutable trait Readable { def fileName: String - def underlyingFile: Option[File] = None + def underlyingFile: Option[Path] = None def isRereadable: Boolean protected def getReader: Reader diff --git a/src/hre/hre/perf/Profile.scala b/src/hre/hre/perf/Profile.scala index 24343932f7..045d6c1036 100644 --- a/src/hre/hre/perf/Profile.scala +++ b/src/hre/hre/perf/Profile.scala @@ -3,7 +3,7 @@ package hre.perf import com.google.perftools import com.google.perftools.profiles.{Sample, ValueType} -import java.io.FileOutputStream +import java.nio.file.{Files, Paths} import java.util.zip.GZIPOutputStream import scala.collection.mutable @@ -82,7 +82,7 @@ case class Profile() { timeNanos = epochStartNanos, defaultSampleType = builder.str("agg"), ) - val out = new GZIPOutputStream(new FileOutputStream("profile.pprof.gz")) + val out = new GZIPOutputStream(Files.newOutputStream(Paths.get("profile.pprof.gz"))) result.writeTo(out) out.close() } diff --git a/src/hre/hre/resource/ResourceUtil.scala b/src/hre/hre/resource/ResourceUtil.scala index 06b97b6c6a..a3b64881c0 100644 --- a/src/hre/hre/resource/ResourceUtil.scala +++ b/src/hre/hre/resource/ResourceUtil.scala @@ -3,9 +3,8 @@ package hre.resource import hre.platform.Platform import vct.result.VerificationError.SystemError -import java.io.File import java.net.URISyntaxException -import java.nio.file.Path +import java.nio.file.{Path, Paths} case object ResourceUtil { case class NoSuchResource(path: String) extends SystemError { @@ -14,7 +13,7 @@ case object ResourceUtil { def getResource(path: String): Path = try { Option(getClass.getResource(path)) match { - case Some(url) => new File(url.toURI).toPath + case Some(url) => Paths.get(url.toURI) case None => throw NoSuchResource(path) } } catch { diff --git a/src/hre/hre/util/Notifier.scala b/src/hre/hre/util/Notifier.scala index 112080dea7..4d5d2aa6e0 100644 --- a/src/hre/hre/util/Notifier.scala +++ b/src/hre/hre/util/Notifier.scala @@ -2,7 +2,8 @@ package hre.util import hre.platform.Platform -import java.io.{ByteArrayInputStream, File} +import java.io.{ByteArrayInputStream} +import java.io.File.{pathSeparator => PATH_SEPARATOR} import sys.process._ import scala.jdk.CollectionConverters._ import java.nio.file.{Files, Paths} @@ -86,7 +87,7 @@ object Notifier { def commandExists(cmd: String): Boolean = { System.getenv().asScala.getOrElse("PATH", "") - .split(File.pathSeparator) + .split(PATH_SEPARATOR) .exists(path => { val p = Paths.get(path).resolve(cmd) Files.exists(p) && !Files.isDirectory(p) && Files.isExecutable(p) diff --git a/src/main/vct/importer/JavaLibraryLoader.scala b/src/main/vct/importer/JavaLibraryLoader.scala index 3a03c9f3e9..42a6d7c27b 100644 --- a/src/main/vct/importer/JavaLibraryLoader.scala +++ b/src/main/vct/importer/JavaLibraryLoader.scala @@ -7,12 +7,12 @@ import vct.col.resolve.ExternalJavaLoader import vct.parsers.transform.BlameProvider import vct.parsers.{ColJavaParser, FileNotFound} -import java.io.File +import java.io.File.{separator => FILE_SEPARATOR} import java.nio.file.Path case class JavaLibraryLoader(blameProvider: BlameProvider) extends ExternalJavaLoader { override def load[G](base: Path, name: Seq[String]): Option[JavaNamespace[G]] = try { - val f = RWFile(base.resolve((name.init :+ name.last + ".java").mkString(File.separator)).toFile) + val f = RWFile(base.resolve((name.init :+ name.last + ".java").mkString(FILE_SEPARATOR))) ColJavaParser(Origin(Seq(ReadableOrigin(f))), blameProvider).parse[G](f).decls match { case Seq(ns: JavaNamespace[G]) => Some(ns) case _ => None diff --git a/src/main/vct/main/stages/Backend.scala b/src/main/vct/main/stages/Backend.scala index 70a87969bd..9b9622cf7d 100644 --- a/src/main/vct/main/stages/Backend.scala +++ b/src/main/vct/main/stages/Backend.scala @@ -13,10 +13,10 @@ import viper.api.{backend => viper} import viper.carbon.Carbon import viper.silicon.Silicon -import java.io.{FileInputStream, FileOutputStream} import java.nio.file.{Files, Path} import scala.collection.parallel.CollectionConverters.seqIsParallelizable import scala.runtime.ScalaRunTime +import scala.util.Using case object Backend { @@ -88,13 +88,13 @@ trait Backend extends Stage[Verification[_ <: Generation], Seq[ExpectedError]] { // it just means the cache will not work very well. val path = baseDir.resolve("%02x" format program.hashCode()) - val programFile = path.resolve("program.colpb").toFile + val programFile = path.resolve("program.colpb") if(Files.exists(path)) { // The result is potentially cached in programFile - val f = new FileInputStream(programFile) - val cachedProgram = vct.col.ast.serialize.Program.parseFrom(f) - f.close() + val cachedProgram = Using(Files.newInputStream(programFile)) { is => + vct.col.ast.serialize.Program.parseFrom(is) + } if(cachedProgram != program) { // Unlikely: in case of a hash collision, just run the verification (permanently unlucky) @@ -103,9 +103,9 @@ trait Backend extends Stage[Verification[_ <: Generation], Seq[ExpectedError]] { } else if (update) { // If the result is not even potentially cached, run update, and if the program definitely verifies, store the result. path.toFile.mkdirs() - val f = new FileOutputStream(programFile) - program.writeTo(f) - f.close() + Using(Files.newOutputStream(programFile)) { os => + program.writeTo(os) + } } } diff --git a/src/main/vct/options/types/PathOrStd.scala b/src/main/vct/options/types/PathOrStd.scala index 2a88181f86..f94ce30d5d 100644 --- a/src/main/vct/options/types/PathOrStd.scala +++ b/src/main/vct/options/types/PathOrStd.scala @@ -2,11 +2,12 @@ package vct.options.types import hre.io.{InMemoryCachedReadable, Writeable} -import java.io._ +import java.io.{InputStreamReader, OutputStreamWriter, Reader, Writer} import java.nio.charset.StandardCharsets +import java.nio.file.{Files, Path} sealed trait PathOrStd extends InMemoryCachedReadable with Writeable { - override def underlyingFile: Option[File] = this match { + override def underlyingFile: Option[Path] = this match { case PathOrStd.Path(path) => Some(path.toFile) case PathOrStd.StdInOrOut => None } @@ -22,14 +23,14 @@ sealed trait PathOrStd extends InMemoryCachedReadable with Writeable { } override protected def getReaderImpl: Reader = this match { - case PathOrStd.Path(path) => new FileReader(path.toFile, StandardCharsets.UTF_8) + case PathOrStd.Path(path) => Files.newBufferedReader(path, StandardCharsets.UTF_8) case PathOrStd.StdInOrOut => new InputStreamReader(System.in, StandardCharsets.UTF_8) } override protected def getWriter: Writer = { invalidate() this match { - case PathOrStd.Path(path) => new FileWriter(path.toFile, StandardCharsets.UTF_8) + case PathOrStd.Path(path) => Files.newBufferedWriter(path, StandardCharsets.UTF_8) case PathOrStd.StdInOrOut => new OutputStreamWriter(System.out, StandardCharsets.UTF_8) } } diff --git a/src/parsers/vct/parsers/ColCPPParser.scala b/src/parsers/vct/parsers/ColCPPParser.scala index d63c520566..98e42740db 100644 --- a/src/parsers/vct/parsers/ColCPPParser.scala +++ b/src/parsers/vct/parsers/ColCPPParser.scala @@ -8,9 +8,9 @@ import vct.parsers.CParser.PreprocessorError import vct.parsers.transform.BlameProvider import vct.result.VerificationError.{Unreachable, UserError} -import java.io._ +import java.io.{FileNotFoundException, InputStreamReader, OutputStreamWriter, StringWriter} import java.nio.charset.StandardCharsets -import java.nio.file.{Path, Paths} +import java.nio.file.{Files, Path, Paths} case object CPPParser { case class PreprocessorError(fileName: String, errorCode: Int, error: String) extends UserError { @@ -50,38 +50,40 @@ case class ColCPPParser(override val origin: Origin, override def parse[G](readable: Readable): ParseResult[G] = try { - val interpreted = File.createTempFile("vercors-interpreted-", ".ipp") - interpreted.deleteOnExit() - - val process = interpret( - localInclude=Option(Paths.get(readable.fileName).getParent).toSeq, - input="-", - output=interpreted.toString - ) - new Thread(() => { - val writer = new OutputStreamWriter(process.getOutputStream, StandardCharsets.UTF_8) - try { - readable.read { reader => - val written = reader.transferTo(writer) - logger.debug(s"Wrote $written bytes to clang++") + val interpreted = Files.createTempFile("vercors-interpreted-", ".ipp") + try { + val process = interpret( + localInclude = Option(Paths.get(readable.fileName).getParent).toSeq, + input = "-", + output = interpreted.toString + ) + new Thread(() => { + val writer = new OutputStreamWriter(process.getOutputStream, StandardCharsets.UTF_8) + try { + readable.read { reader => + val written = reader.transferTo(writer) + logger.debug(s"Wrote $written bytes to clang++") + } + } finally { + writer.close() } - } finally { + }, "[VerCors] clang stdout writer").start() + process.waitFor() + + if (process.exitValue() != 0) { + val writer = new StringWriter() + new InputStreamReader(process.getInputStream).transferTo(writer) + new InputStreamReader(process.getErrorStream).transferTo(writer) writer.close() + throw PreprocessorError(readable.fileName, process.exitValue(), writer.toString) } - }, "[VerCors] clang stdout writer").start() - process.waitFor() - if(process.exitValue() != 0) { - val writer = new StringWriter() - new InputStreamReader(process.getInputStream).transferTo(writer) - new InputStreamReader(process.getErrorStream).transferTo(writer) - writer.close() - throw PreprocessorError(readable.fileName, process.exitValue(), writer.toString) + val ireadable = RWFile(interpreted) + val result = ColIPPParser(Origin(Seq(ReadableOrigin(ireadable))), blameProvider, Some(origin)).parse[G](ireadable) + result + } finally { + Files.delete(interpreted) } - - val ireadable = RWFile(interpreted) - val result = ColIPPParser(Origin(Seq(ReadableOrigin(ireadable))), blameProvider, Some(origin)).parse[G](ireadable) - result } catch { case _: FileNotFoundException => throw FileNotFound(readable.fileName) } diff --git a/src/parsers/vct/parsers/ColCParser.scala b/src/parsers/vct/parsers/ColCParser.scala index 3e8652d33d..1085d82e46 100644 --- a/src/parsers/vct/parsers/ColCParser.scala +++ b/src/parsers/vct/parsers/ColCParser.scala @@ -7,9 +7,9 @@ import vct.parsers.CParser.PreprocessorError import vct.parsers.transform.BlameProvider import vct.result.VerificationError.{Unreachable, UserError} -import java.io.{File, FileNotFoundException, InputStreamReader, OutputStreamWriter, StringWriter} +import java.io.{FileNotFoundException, InputStreamReader, OutputStreamWriter, StringWriter} import java.nio.charset.StandardCharsets -import java.nio.file.{Path, Paths} +import java.nio.file.{Files, Path, Paths} case object CParser { case class PreprocessorError(fileName: String, errorCode: Int, error: String) extends UserError { @@ -48,38 +48,41 @@ case class ColCParser(override val origin: Origin, override def parse[G](readable: Readable): ParseResult[G] = try { - val interpreted = File.createTempFile("vercors-interpreted-", ".i") - interpreted.deleteOnExit() + val interpreted = Files.createTempFile("vercors-interpreted-", ".i") - val process = interpret( - localInclude=Option(Paths.get(readable.fileName).getParent).toSeq, - input="-", - output=interpreted.toString - ) - new Thread(() => { - val writer = new OutputStreamWriter(process.getOutputStream, StandardCharsets.UTF_8) - try { - readable.read { reader => - val written = reader.transferTo(writer) - logger.debug(s"Wrote $written bytes to clang") + try { + val process = interpret( + localInclude = Option(Paths.get(readable.fileName).getParent).toSeq, + input = "-", + output = interpreted.toString + ) + new Thread(() => { + val writer = new OutputStreamWriter(process.getOutputStream, StandardCharsets.UTF_8) + try { + readable.read { reader => + val written = reader.transferTo(writer) + logger.debug(s"Wrote $written bytes to clang") + } + } finally { + writer.close() } - } finally { + }, "[VerCors] clang stdout writer").start() + process.waitFor() + + if (process.exitValue() != 0) { + val writer = new StringWriter() + new InputStreamReader(process.getInputStream).transferTo(writer) + new InputStreamReader(process.getErrorStream).transferTo(writer) writer.close() + throw PreprocessorError(readable.fileName, process.exitValue(), writer.toString) } - }, "[VerCors] clang stdout writer").start() - process.waitFor() - if(process.exitValue() != 0) { - val writer = new StringWriter() - new InputStreamReader(process.getInputStream).transferTo(writer) - new InputStreamReader(process.getErrorStream).transferTo(writer) - writer.close() - throw PreprocessorError(readable.fileName, process.exitValue(), writer.toString) + val ireadable = RWFile(interpreted) + val result = ColIParser(Origin(Seq(ReadableOrigin(ireadable))), blameProvider, Some(origin)).parse[G](ireadable) + result + } finally { + Files.delete(interpreted) } - - val ireadable = RWFile(interpreted) - val result = ColIParser(Origin(Seq(ReadableOrigin(ireadable))), blameProvider, Some(origin)).parse[G](ireadable) - result } catch { case _: FileNotFoundException => throw FileNotFound(readable.fileName) } diff --git a/src/serialize/com/google/protobuf/ForwardingByteString.scala b/src/serialize/com/google/protobuf/ForwardingByteString.scala index d0e8e4ced5..04e298fc03 100644 --- a/src/serialize/com/google/protobuf/ForwardingByteString.scala +++ b/src/serialize/com/google/protobuf/ForwardingByteString.scala @@ -1,5 +1,5 @@ package com.google.protobuf -import java.io.{ByteArrayOutputStream, InputStream, OutputStream} +import java.io.{InputStream, OutputStream} import java.nio.ByteBuffer import java.nio.charset.Charset import java.util diff --git a/src/viper/viper/api/SilverTreeCompare.scala b/src/viper/viper/api/SilverTreeCompare.scala index fbf0c7d982..035d8307e8 100644 --- a/src/viper/viper/api/SilverTreeCompare.scala +++ b/src/viper/viper/api/SilverTreeCompare.scala @@ -1,10 +1,12 @@ package viper.api -import java.io.{File, FileOutputStream, OutputStreamWriter} import viper.silver.ast.{And, DomainFuncApp, LocalVarDecl, Node, Or, Seqn, Typed} import viper.silver.frontend.{SilFrontend, SilFrontendConfig} import viper.silver.verifier.{AbstractError, NoVerifier, Verifier} +import java.nio.file.Files +import scala.util.Using + object DummyFrontend extends SilFrontend() { val NO_VERIFIER = new NoVerifier @@ -18,23 +20,27 @@ object DummyFrontend extends SilFrontend() { */ object SilverTreeCompare { def syntacticSugarIssues(node: Node): Either[Seq[AbstractError], Seq[(Node, Node)]] = { - val tmp = File.createTempFile("vercors-output-", ".sil") - tmp.deleteOnExit() - val writer = new OutputStreamWriter(new FileOutputStream(tmp)) - writer.write(node.toString) - writer.close() + val tmp = Files.createTempFile("vercors-output-", ".sil") - val frontend = DummyFrontend - frontend.init(DummyFrontend.NO_VERIFIER) - frontend.reset(tmp.toPath) - frontend.parsing() - frontend.semanticAnalysis() - frontend.translation() + try { + Using(Files.newBufferedWriter(tmp)) { writer => + writer.write(node.toString()) + } - if(frontend.errors.nonEmpty) { - Left(frontend.errors) - } else { - Right(compare(node, frontend.translationResult)) + val frontend = DummyFrontend + frontend.init(DummyFrontend.NO_VERIFIER) + frontend.reset(tmp) + frontend.parsing() + frontend.semanticAnalysis() + frontend.translation() + + if (frontend.errors.nonEmpty) { + Left(frontend.errors) + } else { + Right(compare(node, frontend.translationResult)) + } + } finally { + Files.delete(tmp) } } diff --git a/src/viper/viper/api/backend/SilverBackend.scala b/src/viper/viper/api/backend/SilverBackend.scala index c46b34f572..d63f82c881 100644 --- a/src/viper/viper/api/backend/SilverBackend.scala +++ b/src/viper/viper/api/backend/SilverBackend.scala @@ -16,7 +16,6 @@ import viper.silver.verifier._ import viper.silver.verifier.errors._ import viper.silver.{ast => silver} -import java.io.{File, FileOutputStream} import java.nio.file.Path import scala.reflect.ClassTag import scala.util.{Try, Using} @@ -69,7 +68,7 @@ trait SilverBackend extends Backend[(silver.Program, Map[Int, col.Node[_]])] wit .replace("requires decreases", "decreases") .replace("invariant decreases", "decreases") - output.map(_.toFile).map(RWFile).foreach(_.write { writer => + output.map(RWFile).foreach(_.write { writer => writer.write(silverProgramString) }) From 33a3d618a38c9c8d4e8145783f0ecf07d45a7c0a Mon Sep 17 00:00:00 2001 From: Pieter Bos Date: Fri, 3 May 2024 17:21:07 +0200 Subject: [PATCH 38/52] switch over some things to RWFile that should be tracked --- src/col/vct/col/resolve/lang/Java.scala | 1 + src/main/vct/importer/Util.scala | 2 +- .../viper/api/transform/SilverParserDummyFrontend.scala | 7 +++---- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/col/vct/col/resolve/lang/Java.scala b/src/col/vct/col/resolve/lang/Java.scala index 804f84ab22..8db40a6cca 100644 --- a/src/col/vct/col/resolve/lang/Java.scala +++ b/src/col/vct/col/resolve/lang/Java.scala @@ -15,6 +15,7 @@ import vct.col.typerules.Types import vct.col.util.AstBuildHelpers._ import vct.result.VerificationError.{Unreachable, UserError} +import java.lang.reflect import java.lang.reflect.{Modifier, Parameter, TypeVariable} import java.nio.file.Path import scala.annotation.tailrec diff --git a/src/main/vct/importer/Util.scala b/src/main/vct/importer/Util.scala index 8566cce59e..1d8d84baef 100644 --- a/src/main/vct/importer/Util.scala +++ b/src/main/vct/importer/Util.scala @@ -31,7 +31,7 @@ case object Util { val pinnedLibrary = cacheDir.resolve("library.in") val result = cacheDir.resolve("library.colpb") - if(!Files.exists(cacheDir) || RWFile(pinnedLibrary.toFile).readToCompletion() != text) { + if(!Files.exists(cacheDir) || RWFile(pinnedLibrary).readToCompletion() != text) { val res = ColPVLParser(Origin(Seq(ReadableOrigin(readable))), ConstantBlameProvider(LibraryFileBlame)).parse(readable) val context = Resolution(ConstantBlameProvider(LibraryFileBlame)).run(res) val unambiguousProgram: Program[_] = Disambiguate().dispatch(context.tasks.head.program) diff --git a/src/viper/viper/api/transform/SilverParserDummyFrontend.scala b/src/viper/viper/api/transform/SilverParserDummyFrontend.scala index ba70e3cf9c..9b09f29fb5 100644 --- a/src/viper/viper/api/transform/SilverParserDummyFrontend.scala +++ b/src/viper/viper/api/transform/SilverParserDummyFrontend.scala @@ -1,14 +1,13 @@ package viper.api.transform -import hre.io.Readable +import hre.io.{RWFile, Readable} import viper.silver.ast.Program import viper.silver.frontend.{DefaultStates, SilFrontend, SilFrontendConfig} import viper.silver.reporter.Reporter import viper.silver.verifier.{AbstractError, Verifier} -import java.nio.file.{Files, Path, Paths} +import java.nio.file.{Path, Paths} import scala.annotation.nowarn -import scala.io.Source // We can only refactor this once silver starts using trait parameters (or the suggested workaround) // So we silence the warning because it is not useful. @@ -43,7 +42,7 @@ case class SilverParserDummyFrontend() extends { } def parse(path: Path): Either[Seq[AbstractError], Program] = - parse(Source.fromInputStream(Files.newInputStream(path)).mkString, path) + parse(RWFile(path).readToCompletion(), path) def parse(readable: Readable): Either[Seq[AbstractError], Program] = parse(readable.readToCompletion(), Paths.get(readable.fileName)) From c6a72d638c4c16e2511c7c6ac22b1845d56fed82 Mon Sep 17 00:00:00 2001 From: Pieter Bos Date: Mon, 6 May 2024 09:44:28 +0200 Subject: [PATCH 39/52] final syntax tidbits --- src/col/vct/col/resolve/lang/Java.scala | 2 +- src/main/vct/importer/PathAdtImporter.scala | 2 +- src/main/vct/main/stages/Output.scala | 4 ++-- src/main/vct/options/types/PathOrStd.scala | 2 +- src/rewrite/vct/rewrite/cfg/CFGPrinter.scala | 2 +- src/rewrite/vct/rewrite/rasi/Utils.scala | 2 +- src/viper/viper/api/backend/SilverBackend.scala | 13 +++++++------ src/viper/viper/api/transform/SilverToCol.scala | 4 ++-- 8 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/col/vct/col/resolve/lang/Java.scala b/src/col/vct/col/resolve/lang/Java.scala index 8db40a6cca..a63e245666 100644 --- a/src/col/vct/col/resolve/lang/Java.scala +++ b/src/col/vct/col/resolve/lang/Java.scala @@ -270,7 +270,7 @@ case object Java extends LazyLogging { readable <- Some(ns.o.find[ReadableOrigin].get.readable) file <- readable.underlyingFile baseFile <- ns.pkg.getOrElse(JavaName(Nil)).names.foldRight[Option[Path]](Option(file.getParent)) { - case (name, Some(file)) if file.getName == name => Option(file.getParent) + case (name, Some(file)) if file.getFileName.toString == name => Option(file.getParent) case _ => None } } yield baseFile diff --git a/src/main/vct/importer/PathAdtImporter.scala b/src/main/vct/importer/PathAdtImporter.scala index 27ebb792ce..eb23bf72b6 100644 --- a/src/main/vct/importer/PathAdtImporter.scala +++ b/src/main/vct/importer/PathAdtImporter.scala @@ -8,5 +8,5 @@ import java.nio.file.Path case class PathAdtImporter(basePath: Path) extends ImportADTImporter { override def loadAdt[G](name: String): Program[G] = - Util.loadPVLLibraryFile(RWFile(basePath.resolve(name + ".pvl").toFile)) + Util.loadPVLLibraryFile(RWFile(basePath.resolve(name + ".pvl"))) } diff --git a/src/main/vct/main/stages/Output.scala b/src/main/vct/main/stages/Output.scala index f5c2fea3c0..ca808b0149 100644 --- a/src/main/vct/main/stages/Output.scala +++ b/src/main/vct/main/stages/Output.scala @@ -37,12 +37,12 @@ case class Output(out: Path, syntax: Ctx.Syntax) extends Stage[Node[_ <: Generat in.asInstanceOf[Program[G]].declarations.zipWithIndex.foreach { case (decl, i) => val name = names.getOrElse(decl, s"unknown$i") val f = out.resolve(name + ".pvl") - hre.io.RWFile(f.toFile).write(w => decl.write(w)(ctx)) + hre.io.RWFile(f).write(w => decl.write(w)(ctx)) } } // Otherwise create one big program from the parse result and write it to the provided file directly else { - hre.io.RWFile(out.toFile).write(w => in.write(w)(ctx)) + hre.io.RWFile(out).write(w => in.write(w)(ctx)) } } } \ No newline at end of file diff --git a/src/main/vct/options/types/PathOrStd.scala b/src/main/vct/options/types/PathOrStd.scala index f94ce30d5d..2bbc6b45eb 100644 --- a/src/main/vct/options/types/PathOrStd.scala +++ b/src/main/vct/options/types/PathOrStd.scala @@ -8,7 +8,7 @@ import java.nio.file.{Files, Path} sealed trait PathOrStd extends InMemoryCachedReadable with Writeable { override def underlyingFile: Option[Path] = this match { - case PathOrStd.Path(path) => Some(path.toFile) + case PathOrStd.Path(path) => Some(path) case PathOrStd.StdInOrOut => None } diff --git a/src/rewrite/vct/rewrite/cfg/CFGPrinter.scala b/src/rewrite/vct/rewrite/cfg/CFGPrinter.scala index 465b81792d..adbbac2a36 100644 --- a/src/rewrite/vct/rewrite/cfg/CFGPrinter.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGPrinter.scala @@ -33,7 +33,7 @@ case class CFGPrinter[G]() { def print_ast_as_cfg(entry_point: InstanceMethod[G], path: Path): Unit = { val cfg_root: CFGNode[G] = CFGGenerator().generate(entry_point) find_all_nodes(cfg_root) - RWFile(path.toFile).write(w => print_cfg(w)) + RWFile(path).write(w => print_cfg(w)) } private def find_all_nodes(node: CFGEntry[G]): Unit = { diff --git a/src/rewrite/vct/rewrite/rasi/Utils.scala b/src/rewrite/vct/rewrite/rasi/Utils.scala index 892b70e45b..0ec4ad4497 100644 --- a/src/rewrite/vct/rewrite/rasi/Utils.scala +++ b/src/rewrite/vct/rewrite/rasi/Utils.scala @@ -34,7 +34,7 @@ case object Utils { def print[G](states: Seq[AbstractState[G]], edges: Seq[(AbstractState[G], AbstractState[G])], out: Path): Unit = { val node_names: Map[AbstractState[G], String] = Map.from(states.zipWithIndex.map(t => (t._1, s"n${t._2}"))) - RWFile(out.toFile).write(w => print_state_space(node_names, edges, w, states.head.to_expression.toInlineString.length > 50)) + RWFile(out).write(w => print_state_space(node_names, edges, w, states.head.to_expression.toInlineString.length > 50)) } private def print_state_space[G](names: Map[AbstractState[G], String], diff --git a/src/viper/viper/api/backend/SilverBackend.scala b/src/viper/viper/api/backend/SilverBackend.scala index d63f82c881..6917c3c9ae 100644 --- a/src/viper/viper/api/backend/SilverBackend.scala +++ b/src/viper/viper/api/backend/SilverBackend.scala @@ -16,7 +16,7 @@ import viper.silver.verifier._ import viper.silver.verifier.errors._ import viper.silver.{ast => silver} -import java.nio.file.Path +import java.nio.file.{Files, Path} import scala.reflect.ClassTag import scala.util.{Try, Using} @@ -81,12 +81,13 @@ trait SilverBackend extends Backend[(silver.Program, Map[Int, col.Node[_]])] wit next() - val f = File.createTempFile("vercors-", ".sil") - f.deleteOnExit() - Using(new FileOutputStream(f)) { out => - out.write(silverProgramString.getBytes()) + val f = Files.createTempFile("vercors-", ".sil") + try { + Using(Files.newBufferedWriter(f))(_.write(silverProgramString)) + } finally { + Files.delete(f) } - SilverParserDummyFrontend().parse(f.toPath) match { + SilverParserDummyFrontend().parse(f) match { case Left(errors) => logger.warn("Possible viper bug: silver AST does not reparse when printing as text") for(error <- errors) { diff --git a/src/viper/viper/api/transform/SilverToCol.scala b/src/viper/viper/api/transform/SilverToCol.scala index 45534b9f8f..6c0f9ffb85 100644 --- a/src/viper/viper/api/transform/SilverToCol.scala +++ b/src/viper/viper/api/transform/SilverToCol.scala @@ -24,12 +24,12 @@ case object SilverToCol { val end = position.end.getOrElse(start) Origin(Seq( PositionRange(start.line-1, end.line-1, Some((start.column-1, end.column-1))), - ReadableOrigin(RWFile(position.file.toFile)), + ReadableOrigin(RWFile(position.file)), )) case FilePosition(file, vline, col) => Origin(Seq( PositionRange(vline, vline, Some((col, col))), - ReadableOrigin(RWFile(file.toFile)), + ReadableOrigin(RWFile(file)), )) case LineColumnPosition(line, column) => Origin(Seq( From bf80a8a8b92a1c976178bcafaac28e6c80b87b9f Mon Sep 17 00:00:00 2001 From: Pieter Bos Date: Mon, 6 May 2024 15:49:42 +0200 Subject: [PATCH 40/52] make System.in reads interruptible --- src/hre/hre/io/InterruptibleInputStream.scala | 127 ++++++++++++++++++ src/hre/hre/io/LiteralReadable.scala | 4 + src/hre/hre/io/RWFile.scala | 4 + src/hre/hre/io/Readable.scala | 1 + src/hre/hre/io/Watch.scala | 46 +++++++ src/main/vct/main/Main.scala | 5 + src/main/vct/main/stages/Resolution.scala | 19 +-- src/main/vct/options/types/PathOrStd.scala | 7 +- .../integration/helper/ExampleFiles.scala | 22 +-- .../integration/meta/ExampleCoverage.scala | 2 +- 10 files changed, 213 insertions(+), 24 deletions(-) create mode 100644 src/hre/hre/io/InterruptibleInputStream.scala create mode 100644 src/hre/hre/io/Watch.scala diff --git a/src/hre/hre/io/InterruptibleInputStream.scala b/src/hre/hre/io/InterruptibleInputStream.scala new file mode 100644 index 0000000000..0c517e769b --- /dev/null +++ b/src/hre/hre/io/InterruptibleInputStream.scala @@ -0,0 +1,127 @@ +package hre.io + +import java.io.InputStream + +object InterruptibleInputStream { + val BLOCK_SIZE = 1024 + val BUFFER_BLOCKS = 2 +} + +class InterruptibleInputStream(val underlying: InputStream) extends InputStream { outer => + import InterruptibleInputStream._ + + private def BUFFER_SIZE = BLOCK_SIZE * BUFFER_BLOCKS + private val buffer = new Array[Byte](BUFFER_SIZE) + // When writeCursor and readCursor overlap, the buffer is empty. There is + // thus one "dead" byte in the buffer. + private var readCursor = 0 + private var writeCursor = 0 + private var eof = false + + private val transferThread = new TransferThread + transferThread.setDaemon(true) + transferThread.setName("[VerCors] I/O transfer thread") + transferThread.start() + + class TransferThread extends Thread { + override def run(): Unit = + while(!eof) { + if(outer.synchronized(spaceAvailable) >= BLOCK_SIZE) { + val toRead = outer.synchronized(Math.min(BLOCK_SIZE, BUFFER_SIZE - writeCursor)) + val res = underlying.read(buffer, writeCursor, toRead) + + if(res == -1) { + eof = true + } else { + writeCursor = (writeCursor + res) % BUFFER_SIZE + } + + outer.synchronized(outer.notifyAll()) + } else { + outer.synchronized(outer.wait()) + } + } + } + + override final def read: Int = synchronized { + while(true) { + if(available > 0) { + val result = buffer(readCursor) + readCursor = (readCursor + 1) % BUFFER_SIZE + notifyAll() + return result + } else if(eof) { + return -1 + } else { + wait() + } + } + + ??? + } + + override def read(bufferOut: Array[Byte], off: Int, len: Int): Int = synchronized { + var written = 0 + + while(written < len) { + if(writeCursor > readCursor) { + val toCopy = Math.min(writeCursor - readCursor, len - written) + System.arraycopy(buffer, readCursor, bufferOut, off + written, toCopy) + readCursor += toCopy + notifyAll() + written += toCopy + } else if(writeCursor < readCursor) { + val toCopy = Math.min(BUFFER_SIZE - readCursor, len - written) + System.arraycopy(buffer, readCursor, bufferOut, off + written, toCopy) + readCursor = (readCursor + toCopy) % BUFFER_SIZE + notifyAll() + written += toCopy + } else if(eof) { + return if (written == 0) -1 else written + } else if(written > 0) { + return written + } else { + wait() + } + } + + written + } + + override def skip(n: Long): Long = { + var skipped = 0L + + while(skipped < n) { + val avail = available + if(avail > 0 && avail < n - skipped) { + skipped += avail + readCursor = writeCursor + notifyAll() + } else if(avail > 0) { + readCursor = (readCursor + (n - skipped).toInt) % BUFFER_SIZE + notifyAll() + skipped = n + } else if(eof) { + return skipped + } else { + wait() + } + } + + skipped + } + + override def available: Int = + if(writeCursor >= readCursor) + writeCursor - readCursor + else + (writeCursor - readCursor) + BUFFER_SIZE + + def spaceAvailable: Int = + if(readCursor > writeCursor) + readCursor - writeCursor - 1 + else + (readCursor - writeCursor - 1) + BUFFER_SIZE + + override def close(): Unit = underlying.close() +} diff --git a/src/hre/hre/io/LiteralReadable.scala b/src/hre/hre/io/LiteralReadable.scala index 00609ba60a..132b0896fb 100644 --- a/src/hre/hre/io/LiteralReadable.scala +++ b/src/hre/hre/io/LiteralReadable.scala @@ -7,4 +7,8 @@ case class LiteralReadable(fileName: String, data: String) extends Readable { override protected def getReader: Reader = new StringReader(data) + + override def enroll(watch: Watch): Unit = { + // A literal string cannot change, so there is nothing to do. + } } diff --git a/src/hre/hre/io/RWFile.scala b/src/hre/hre/io/RWFile.scala index fc43fad470..d7548d11bc 100644 --- a/src/hre/hre/io/RWFile.scala +++ b/src/hre/hre/io/RWFile.scala @@ -17,4 +17,8 @@ case class RWFile(file: Path) extends InMemoryCachedReadable with Writeable { override protected def getReaderImpl: Reader = { Files.newBufferedReader(file, StandardCharsets.UTF_8) } + + override def enroll(watch: Watch): Unit = { + watch.enroll(file) + } } diff --git a/src/hre/hre/io/Readable.scala b/src/hre/hre/io/Readable.scala index 0da6cf950b..1401a9cd14 100644 --- a/src/hre/hre/io/Readable.scala +++ b/src/hre/hre/io/Readable.scala @@ -11,6 +11,7 @@ trait Readable { def underlyingFile: Option[Path] = None def isRereadable: Boolean protected def getReader: Reader + def enroll(watch: Watch): Unit def read[T](f: Reader => T): T = { val r = getReader diff --git a/src/hre/hre/io/Watch.scala b/src/hre/hre/io/Watch.scala new file mode 100644 index 0000000000..7b040dc9a3 --- /dev/null +++ b/src/hre/hre/io/Watch.scala @@ -0,0 +1,46 @@ +package hre.io + +import java.nio.file.{FileSystem, FileSystems, Path, StandardWatchEventKinds, WatchEvent, WatchService} + +/** + * Watch facilitates that VerCors can automatically re-run when external input + * changes. Implementors of [[Readable]] can vary how we detect external + * changes, but typically we just listen to the change of a file just before + * VerCors reads it. + */ +object Watch { + private var currentWatches: Seq[Watch] = Nil + + def booleanWithWatch[T](doWatch: Boolean)(f: => T): T = + if(doWatch) { + currentWatches = new Watch +: currentWatches + try { + f + } finally { + currentWatches = currentWatches.tail + } + } else { + f + } + + def read(readable: Readable): Unit = { + currentWatches.foreach { watch => + readable.enroll(watch) + } + } +} + +class Watch { + private val watchService = FileSystems.getDefault.newWatchService() + + def enroll(path: Path): Unit = { + path.register( + watchService, + StandardWatchEventKinds.ENTRY_CREATE, + StandardWatchEventKinds.ENTRY_DELETE, + StandardWatchEventKinds.ENTRY_MODIFY, + ) + + System.console().readLine() + } +} \ No newline at end of file diff --git a/src/main/vct/main/Main.scala b/src/main/vct/main/Main.scala index 9ea260cafb..6db4f3c4ba 100644 --- a/src/main/vct/main/Main.scala +++ b/src/main/vct/main/Main.scala @@ -2,6 +2,7 @@ package vct.main import ch.qos.logback.classic.{Level, Logger} import com.typesafe.scalalogging.LazyLogging +import hre.io.InterruptibleInputStream import hre.perf.Profile import hre.progress.Progress import org.slf4j.LoggerFactory @@ -73,6 +74,10 @@ case object Main extends LazyLogging { }) } + // Make it so read calls to System.in may be interrupted with Thread.interrupt() + // This causes data read between the start of reading and the interrupt to be lost. + System.setIn(new InterruptibleInputStream(System.in)) + Progress.install(options.progress, options.profile) Runtime.getRuntime.addShutdownHook(new Thread("[VerCors] Shutdown hook to abort progress on exit") { diff --git a/src/main/vct/main/stages/Resolution.scala b/src/main/vct/main/stages/Resolution.scala index 3b588b3626..645a3e0f09 100644 --- a/src/main/vct/main/stages/Resolution.scala +++ b/src/main/vct/main/stages/Resolution.scala @@ -1,6 +1,7 @@ package vct.main.stages import com.typesafe.scalalogging.LazyLogging +import hre.io.LiteralReadable import hre.stages.Stage import vct.col.ast.{AddrOf, ApplicableContract, CGlobalDeclaration, Expr, GlobalDeclaration, LlvmFunctionContract, LlvmGlobal, Program, Refute, Verification, VerificationContext} import org.antlr.v4.runtime.CharStreams @@ -8,7 +9,7 @@ import vct.col.ast._ import vct.col.check.CheckError import vct.col.origin.{FileSpanningOrigin, InlineBipContext, Origin, OriginFilename, ReadableOrigin} import vct.col.resolve.{Resolve, ResolveReferences, ResolveTypes} -import vct.col.rewrite.{Generation} +import vct.col.rewrite.Generation import vct.col.rewrite.bip.IsolateBipGlue import vct.rewrite.lang.{LangSpecificToCol, LangTypesToCol} import vct.importer.JavaLibraryLoader @@ -41,12 +42,6 @@ case object Resolution { ) } -case class StringReadable(data: String, fileName: String = "") extends hre.io.Readable { - override def isRereadable: Boolean = true - - override protected def getReader: Reader = new java.io.StringReader(data) -} - case class SpecExprParseError(msg: String) extends UserError { override def code: String = "specExprParseError" @@ -55,7 +50,7 @@ case class SpecExprParseError(msg: String) extends UserError { case class MyLocalJavaParser(blameProvider: BlameProvider) extends Resolve.SpecExprParser { override def parse[G](input: String, o: Origin): Expr[G] = { - val sr = StringReadable(input) + val sr = LiteralReadable("", input) val cjp = ColJavaParser(o.withContent(InlineBipContext(input)), blameProvider) val x = try { sr.read { reader => @@ -74,8 +69,8 @@ case class MyLocalJavaParser(blameProvider: BlameProvider) extends Resolve.SpecE case class MyLocalLLVMSpecParser(blameProvider: BlameProvider) extends Resolve.SpecContractParser { override def parse[G](input: LlvmFunctionContract[G], o: Origin): ApplicableContract[G] = { val originProvider = Origin(Seq(ReadableOrigin(input.o.find[OriginFilename] match { - case Some(OriginFilename(filename)) => StringReadable(input.value, filename) - case _ => StringReadable(input.value) + case Some(OriginFilename(filename)) => LiteralReadable(filename, input.value) + case _ => LiteralReadable("", input.value) }))) val charStream = CharStreams.fromString(input.value) ColLLVMParser(originProvider, blameProvider, null) @@ -84,8 +79,8 @@ case class MyLocalLLVMSpecParser(blameProvider: BlameProvider) extends Resolve.S override def parse[G](input: LlvmGlobal[G], o: Origin): GlobalDeclaration[G] = { val originProvider = Origin(Seq(ReadableOrigin(input.o.find[OriginFilename] match { - case Some(OriginFilename(filename)) => StringReadable(input.value, filename) - case _ => StringReadable(input.value) + case Some(OriginFilename(filename)) => LiteralReadable(filename, input.value) + case _ => LiteralReadable("", input.value) }))) val charStream = CharStreams.fromString(input.value) ColLLVMParser(originProvider, blameProvider, null) diff --git a/src/main/vct/options/types/PathOrStd.scala b/src/main/vct/options/types/PathOrStd.scala index 2bbc6b45eb..90efba360e 100644 --- a/src/main/vct/options/types/PathOrStd.scala +++ b/src/main/vct/options/types/PathOrStd.scala @@ -1,6 +1,6 @@ package vct.options.types -import hre.io.{InMemoryCachedReadable, Writeable} +import hre.io.{InMemoryCachedReadable, Watch, Writeable} import java.io.{InputStreamReader, OutputStreamWriter, Reader, Writer} import java.nio.charset.StandardCharsets @@ -34,6 +34,11 @@ sealed trait PathOrStd extends InMemoryCachedReadable with Writeable { case PathOrStd.StdInOrOut => new OutputStreamWriter(System.out, StandardCharsets.UTF_8) } } + + override def enroll(watch: Watch): Unit = this match { + case PathOrStd.Path(path) => watch.enroll(path) + case PathOrStd.StdInOrOut => // do nothing + } } case object PathOrStd { diff --git a/test/main/vct/test/integration/helper/ExampleFiles.scala b/test/main/vct/test/integration/helper/ExampleFiles.scala index 342b8766e9..bd3b3b8549 100644 --- a/test/main/vct/test/integration/helper/ExampleFiles.scala +++ b/test/main/vct/test/integration/helper/ExampleFiles.scala @@ -1,7 +1,8 @@ package vct.test.integration.helper import java.io.File -import java.nio.file.Paths +import java.nio.file.{Files, Path, Paths} +import scala.jdk.StreamConverters._ case object ExampleFiles { val IGNORE_DIRS: Seq[String] = Seq( @@ -31,18 +32,19 @@ case object ExampleFiles { "examples/concepts/openmp/test-other.c", ).map(_.replaceAll("/", File.separator)) - val EXCLUSIONS: Seq[File => Boolean] = Seq( + val EXCLUSIONS: Seq[Path => Boolean] = Seq( f => IGNORE_DIRS.exists(dir => f.toString.startsWith(dir)), f => MAIN_FILES.contains(f.toString), - f => IGNORE_FILES.contains(f.getName), - f => IGNORE_EXTS.exists(ext => f.getName.endsWith(ext)), + f => IGNORE_FILES.contains(f.getFileName.toString), + f => IGNORE_EXTS.exists(ext => f.getFileName.toString.endsWith(ext)), ) - val FILES: Seq[File] = find(Paths.get("examples").toFile) + val FILES: Seq[Path] = find(Paths.get("examples")) - def find(directory: File): Seq[File] = - Option(directory.listFiles()) match { - case Some(files) => files.toSeq.filterNot(f => EXCLUSIONS.exists(_(f))).sortBy(_.getName).flatMap(f => if(f.isDirectory) find(f) else Seq(f)) - case None => Nil - } + def find(directory: Path): Seq[Path] = + Files.list(directory) + .toScala(Seq) + .filterNot(f => EXCLUSIONS.exists(_(f))) + .sortBy(_.getFileName.toString) + .flatMap(f => if(Files.isDirectory(f)) find(f) else Seq(f)) } diff --git a/test/main/vct/test/integration/meta/ExampleCoverage.scala b/test/main/vct/test/integration/meta/ExampleCoverage.scala index cacebae9fb..5dbbd6746c 100644 --- a/test/main/vct/test/integration/meta/ExampleCoverage.scala +++ b/test/main/vct/test/integration/meta/ExampleCoverage.scala @@ -64,7 +64,7 @@ class ExampleCoverage extends AnyFlatSpec { new WandSpec(), ) - val testedFiles = specs.flatMap(_.coveredExamples).map(_.toFile).toSet + val testedFiles = specs.flatMap(_.coveredExamples).toSet var shouldFail = false From 14ce75a25971f6356582314b2228d2fe7664dce3 Mon Sep 17 00:00:00 2001 From: Pieter Bos Date: Mon, 6 May 2024 16:41:16 +0200 Subject: [PATCH 41/52] add watch loop --- src/hre/hre/io/Readable.scala | 1 + src/hre/hre/io/Watch.scala | 135 ++++++++++++++++-- src/main/vct/main/Main.scala | 42 +++--- src/main/vct/options/Options.scala | 4 + .../viper/api/backend/SilverBackend.scala | 35 ++--- 5 files changed, 165 insertions(+), 52 deletions(-) diff --git a/src/hre/hre/io/Readable.scala b/src/hre/hre/io/Readable.scala index 1401a9cd14..0f89359b9e 100644 --- a/src/hre/hre/io/Readable.scala +++ b/src/hre/hre/io/Readable.scala @@ -14,6 +14,7 @@ trait Readable { def enroll(watch: Watch): Unit def read[T](f: Reader => T): T = { + Watch.enroll(this) val r = getReader try { f(r) diff --git a/src/hre/hre/io/Watch.scala b/src/hre/hre/io/Watch.scala index 7b040dc9a3..20cee66b88 100644 --- a/src/hre/hre/io/Watch.scala +++ b/src/hre/hre/io/Watch.scala @@ -1,6 +1,9 @@ package hre.io -import java.nio.file.{FileSystem, FileSystems, Path, StandardWatchEventKinds, WatchEvent, WatchService} +import com.typesafe.scalalogging.LazyLogging +import sun.misc.{Signal, SignalHandler} + +import java.nio.file.{ClosedWatchServiceException, FileSystem, FileSystems, Path, StandardWatchEventKinds, WatchEvent, WatchService} /** * Watch facilitates that VerCors can automatically re-run when external input @@ -9,38 +12,140 @@ import java.nio.file.{FileSystem, FileSystems, Path, StandardWatchEventKinds, Wa * VerCors reads it. */ object Watch { - private var currentWatches: Seq[Watch] = Nil + private var currentWatch: Option[Watch] = None - def booleanWithWatch[T](doWatch: Boolean)(f: => T): T = + def booleanWithWatch[T](doWatch: Boolean, default: T)(f: => T): T = if(doWatch) { - currentWatches = new Watch +: currentWatches + val watch = new Watch(Thread.currentThread()) + currentWatch = Some(watch) + val sigint = new Signal("INT") + val previousHandler = Signal.handle(sigint, _ => Watch.currentWatch.get.mainThread.interrupt()) + + var result: T = default + try { - f + while(true) { + try { + result = f + } catch { + case _: InterruptedException => + // f was interrupted - wait until something changes. + } + watch.await() + } + + ??? + } catch { + case _: InterruptedException => + // the watch loop was interrupted - finish up. + result } finally { - currentWatches = currentWatches.tail + Signal.handle(sigint, previousHandler) + currentWatch = None } } else { f } - def read(readable: Readable): Unit = { - currentWatches.foreach { watch => - readable.enroll(watch) - } + def enroll(readable: Readable): Unit = { + currentWatch.foreach { watch => readable.enroll(watch) } } } -class Watch { - private val watchService = FileSystems.getDefault.newWatchService() +class Watch(val mainThread: Thread) extends LazyLogging { watch => + private var fileWatchService = FileSystems.getDefault.newWatchService() + + private var triggered: Boolean = false + + private var threads: Seq[Thread] = Nil + + private var interrupts = 0 def enroll(path: Path): Unit = { - path.register( - watchService, + path.getParent.register( + fileWatchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY, ) + } + + private def fileWatchThread() = new Thread { + override def run(): Unit = try { + fileWatchService.take() + watch.signal() + } catch { + case _: InterruptedException => // do nothing + case _: ClosedWatchServiceException => // do nothing + } + } + + private def stdinWatchThread() = new Thread { + override def run(): Unit = try { + var res = System.in.read() + + while(res != -1 && res != '\n'.toInt) { + res = System.in.read() + } + + if(res == '\n'.toInt) { + watch.signal() + } + } catch { + case _: InterruptedException => // do nothing + } + } + + private def addThread(t: Thread): Unit = { + threads = t +: threads + t.setDaemon(true) + t.start() + } + + private def signal(): Unit = synchronized { + triggered = true + notifyAll() + } + + private def reset(): Unit = { + fileWatchService.close() + fileWatchService = FileSystems.getDefault.newWatchService() + + for(thread <- threads) { + thread.interrupt() + } + + for(thread <- threads) { + thread.join() + } + + threads = Nil + + triggered = false + + interrupts = 0 + } + + def await(): Unit = synchronized { + addThread(fileWatchThread()) + addThread(stdinWatchThread()) + + logger.info(s"[Waiting for input files to change - press ENTER to run again manually]") + + while(!triggered) { + try { + wait() + } catch { + case _: InterruptedException => + if(interrupts == 0) { + logger.info(s"[Press Ctrl+C again to exit VerCors]") + interrupts = 1 + } else { + throw new InterruptedException() + } + } + } - System.console().readLine() + reset() } } \ No newline at end of file diff --git a/src/main/vct/main/Main.scala b/src/main/vct/main/Main.scala index 6db4f3c4ba..8a7aee82ce 100644 --- a/src/main/vct/main/Main.scala +++ b/src/main/vct/main/Main.scala @@ -2,7 +2,7 @@ package vct.main import ch.qos.logback.classic.{Level, Logger} import com.typesafe.scalalogging.LazyLogging -import hre.io.InterruptibleInputStream +import hre.io.{InterruptibleInputStream, Watch} import hre.perf.Profile import hre.progress.Progress import org.slf4j.LoggerFactory @@ -85,27 +85,29 @@ case object Main extends LazyLogging { }) try { - options.mode match { - case Mode.Verify => - logger.info(s"Starting verification at ${hre.util.Time.formatTime()}") - Verify.runOptions(options) - case Mode.HelpVerifyPasses => - logger.info("Available passes:") - Transformation.ofOptions(options).passes.foreach { pass => - logger.info(s" - ${pass.key}") - logger.info(s" ${pass.desc}") + Watch.booleanWithWatch(options.watch, default = EXIT_CODE_SUCCESS) { + options.mode match { + case Mode.Verify => + logger.info(s"Starting verification at ${hre.util.Time.formatTime()}") + Verify.runOptions(options) + case Mode.HelpVerifyPasses => + logger.info("Available passes:") + Transformation.ofOptions(options).passes.foreach { pass => + logger.info(s" - ${pass.key}") + logger.info(s" ${pass.desc}") + } + EXIT_CODE_SUCCESS + case Mode.VeyMont => VeyMont.runOptions(options) + case Mode.VeSUV => { + logger.info("Starting transformation") + VeSUV.runOptions(options) } - EXIT_CODE_SUCCESS - case Mode.VeyMont => VeyMont.runOptions(options) - case Mode.VeSUV => { - logger.info("Starting transformation") - VeSUV.runOptions(options) - } - case Mode.CFG => { - logger.info("Starting control flow graph transformation") - CFG.runOptions(options) + case Mode.CFG => { + logger.info("Starting control flow graph transformation") + CFG.runOptions(options) + } + case Mode.BatchTest => ??? } - case Mode.BatchTest => ??? } } finally { Progress.finish() diff --git a/src/main/vct/options/Options.scala b/src/main/vct/options/Options.scala index 29a70727d2..62ab84f778 100644 --- a/src/main/vct/options/Options.scala +++ b/src/main/vct/options/Options.scala @@ -71,6 +71,9 @@ case object Options { opt[Unit]("profile") .action((_, c) => c.copy(profile = true)) .text("Output profiling information in the current directory in the pprof format (https://github.com/google/pprof)"), + opt[Unit]("watch") + .action((_, c) => c.copy(watch = true)) + .text("Run VerCors in an infinite loop, waiting for external changes between each run."), opt[(String, Verbosity)]("dev-log-verbosity").unbounded().maybeHidden().keyValueName("", "") .action((tup, c) => c.copy(logLevels = c.logLevels :+ tup)) @@ -359,6 +362,7 @@ case class Options ), progress: Boolean = false, profile: Boolean = false, + watch: Boolean = false, more: Boolean = false, // Verify Options diff --git a/src/viper/viper/api/backend/SilverBackend.scala b/src/viper/viper/api/backend/SilverBackend.scala index 6917c3c9ae..cca0039434 100644 --- a/src/viper/viper/api/backend/SilverBackend.scala +++ b/src/viper/viper/api/backend/SilverBackend.scala @@ -84,26 +84,27 @@ trait SilverBackend extends Backend[(silver.Program, Map[Int, col.Node[_]])] wit val f = Files.createTempFile("vercors-", ".sil") try { Using(Files.newBufferedWriter(f))(_.write(silverProgramString)) + + SilverParserDummyFrontend().parse(f) match { + case Left(errors) => + logger.warn("Possible viper bug: silver AST does not reparse when printing as text") + for(error <- errors) { + logger.warn(error.toString) + } + case Right(reparsedProgram) => + SilverTreeCompare.compare(silverProgram, reparsedProgram) match { + case Nil => + case diffs => + logger.debug("Possible VerCors bug: reparsing the silver AST as text causes the AST to be different:") + for((left, right) <- diffs) { + logger.debug(s" - Left: ${left.getClass.getSimpleName}: $left") + logger.debug(s" - Right: ${right.getClass.getSimpleName}: $right") + } + } + } } finally { Files.delete(f) } - SilverParserDummyFrontend().parse(f) match { - case Left(errors) => - logger.warn("Possible viper bug: silver AST does not reparse when printing as text") - for(error <- errors) { - logger.warn(error.toString) - } - case Right(reparsedProgram) => - SilverTreeCompare.compare(silverProgram, reparsedProgram) match { - case Nil => - case diffs => - logger.debug("Possible VerCors bug: reparsing the silver AST as text causes the AST to be different:") - for((left, right) <- diffs) { - logger.debug(s" - Left: ${left.getClass.getSimpleName}: $left") - logger.debug(s" - Right: ${right.getClass.getSimpleName}: $right") - } - } - } (silverProgram, nodeFromUniqueId) } From b3cded61ab780341f0a1df9a2bb8c2f75c7577c7 Mon Sep 17 00:00:00 2001 From: Pieter Bos Date: Mon, 6 May 2024 17:06:58 +0200 Subject: [PATCH 42/52] make VerCors interruptible, invalidate all relevant readables for repeat success --- src/hre/hre/io/InMemoryCachedReadable.scala | 2 +- src/hre/hre/io/RWFile.scala | 1 + src/hre/hre/io/Readable.scala | 1 + src/hre/hre/io/Watch.scala | 12 +++++ src/hre/hre/progress/task/AbstractTask.scala | 4 ++ src/hre/hre/util/Interrupt.scala | 8 ++++ src/main/vct/main/Main.scala | 47 ++++++++++---------- src/main/vct/options/Options.scala | 2 +- 8 files changed, 52 insertions(+), 25 deletions(-) create mode 100644 src/hre/hre/util/Interrupt.scala diff --git a/src/hre/hre/io/InMemoryCachedReadable.scala b/src/hre/hre/io/InMemoryCachedReadable.scala index 699721943c..09f147ad51 100644 --- a/src/hre/hre/io/InMemoryCachedReadable.scala +++ b/src/hre/hre/io/InMemoryCachedReadable.scala @@ -34,7 +34,7 @@ trait InMemoryCachedReadable extends Readable { linesCache.get } - protected def invalidate(): Unit = { + override def invalidate(): Unit = { cache = None linesCache = None } diff --git a/src/hre/hre/io/RWFile.scala b/src/hre/hre/io/RWFile.scala index d7548d11bc..9e540efd4f 100644 --- a/src/hre/hre/io/RWFile.scala +++ b/src/hre/hre/io/RWFile.scala @@ -20,5 +20,6 @@ case class RWFile(file: Path) extends InMemoryCachedReadable with Writeable { override def enroll(watch: Watch): Unit = { watch.enroll(file) + watch.invalidate(this) } } diff --git a/src/hre/hre/io/Readable.scala b/src/hre/hre/io/Readable.scala index 0f89359b9e..0931f9eb33 100644 --- a/src/hre/hre/io/Readable.scala +++ b/src/hre/hre/io/Readable.scala @@ -12,6 +12,7 @@ trait Readable { def isRereadable: Boolean protected def getReader: Reader def enroll(watch: Watch): Unit + def invalidate(): Unit = {} def read[T](f: Reader => T): T = { Watch.enroll(this) diff --git a/src/hre/hre/io/Watch.scala b/src/hre/hre/io/Watch.scala index 20cee66b88..e717aa386f 100644 --- a/src/hre/hre/io/Watch.scala +++ b/src/hre/hre/io/Watch.scala @@ -55,6 +55,8 @@ object Watch { class Watch(val mainThread: Thread) extends LazyLogging { watch => private var fileWatchService = FileSystems.getDefault.newWatchService() + private var toInvalidate: Seq[Readable] = Nil + private var triggered: Boolean = false private var threads: Seq[Thread] = Nil @@ -70,6 +72,10 @@ class Watch(val mainThread: Thread) extends LazyLogging { watch => ) } + def invalidate(readable: Readable): Unit = { + toInvalidate = readable +: toInvalidate + } + private def fileWatchThread() = new Thread { override def run(): Unit = try { fileWatchService.take() @@ -111,6 +117,12 @@ class Watch(val mainThread: Thread) extends LazyLogging { watch => fileWatchService.close() fileWatchService = FileSystems.getDefault.newWatchService() + for(inv <- toInvalidate) { + inv.invalidate() + } + + toInvalidate = Nil + for(thread <- threads) { thread.interrupt() } diff --git a/src/hre/hre/progress/task/AbstractTask.scala b/src/hre/hre/progress/task/AbstractTask.scala index 70f43a02c2..ce82a35291 100644 --- a/src/hre/hre/progress/task/AbstractTask.scala +++ b/src/hre/hre/progress/task/AbstractTask.scala @@ -2,6 +2,7 @@ package hre.progress.task import hre.perf.ResourceUsage import hre.progress.{Progress, ProgressLogicError, ProgressRender, TaskRegistry} +import hre.util.Interrupt import scala.collection.mutable.ArrayBuffer @@ -72,6 +73,7 @@ abstract class AbstractTask { TaskRegistry.push(this) Progress.update() + Interrupt.check() } def end(): Unit = { @@ -94,6 +96,8 @@ abstract class AbstractTask { usageReported = ResourceUsage.zero startUsage = None ownerThread = -1L + + Interrupt.check() } def abort(): Unit = { diff --git a/src/hre/hre/util/Interrupt.scala b/src/hre/hre/util/Interrupt.scala new file mode 100644 index 0000000000..172efd625b --- /dev/null +++ b/src/hre/hre/util/Interrupt.scala @@ -0,0 +1,8 @@ +package hre.util + +object Interrupt { + def check(): Unit = + if(Thread.interrupted()) { + throw new InterruptedException() + } +} diff --git a/src/main/vct/main/Main.scala b/src/main/vct/main/Main.scala index 8a7aee82ce..0e91098053 100644 --- a/src/main/vct/main/Main.scala +++ b/src/main/vct/main/Main.scala @@ -78,40 +78,41 @@ case object Main extends LazyLogging { // This causes data read between the start of reading and the interrupt to be lost. System.setIn(new InterruptibleInputStream(System.in)) - Progress.install(options.progress, options.profile) - Runtime.getRuntime.addShutdownHook(new Thread("[VerCors] Shutdown hook to abort progress on exit") { override def run(): Unit = Progress.abort() }) try { Watch.booleanWithWatch(options.watch, default = EXIT_CODE_SUCCESS) { - options.mode match { - case Mode.Verify => - logger.info(s"Starting verification at ${hre.util.Time.formatTime()}") - Verify.runOptions(options) - case Mode.HelpVerifyPasses => - logger.info("Available passes:") - Transformation.ofOptions(options).passes.foreach { pass => - logger.info(s" - ${pass.key}") - logger.info(s" ${pass.desc}") + Progress.install(options.progress, options.profile) + try { + options.mode match { + case Mode.Verify => + logger.info(s"Starting verification at ${hre.util.Time.formatTime()}") + Verify.runOptions(options) + case Mode.HelpVerifyPasses => + logger.info("Available passes:") + Transformation.ofOptions(options).passes.foreach { pass => + logger.info(s" - ${pass.key}") + logger.info(s" ${pass.desc}") + } + EXIT_CODE_SUCCESS + case Mode.VeyMont => VeyMont.runOptions(options) + case Mode.VeSUV => { + logger.info("Starting transformation") + VeSUV.runOptions(options) } - EXIT_CODE_SUCCESS - case Mode.VeyMont => VeyMont.runOptions(options) - case Mode.VeSUV => { - logger.info("Starting transformation") - VeSUV.runOptions(options) - } - case Mode.CFG => { - logger.info("Starting control flow graph transformation") - CFG.runOptions(options) + case Mode.CFG => { + logger.info("Starting control flow graph transformation") + CFG.runOptions(options) + } + case Mode.BatchTest => ??? } - case Mode.BatchTest => ??? + } finally { + Progress.finish() } } } finally { - Progress.finish() - val thisThread = Thread.currentThread() Thread.getAllStackTraces.keySet() .stream() diff --git a/src/main/vct/options/Options.scala b/src/main/vct/options/Options.scala index 62ab84f778..a7eeccc233 100644 --- a/src/main/vct/options/Options.scala +++ b/src/main/vct/options/Options.scala @@ -71,7 +71,7 @@ case object Options { opt[Unit]("profile") .action((_, c) => c.copy(profile = true)) .text("Output profiling information in the current directory in the pprof format (https://github.com/google/pprof)"), - opt[Unit]("watch") + opt[Unit]("watch").abbr("w") .action((_, c) => c.copy(watch = true)) .text("Run VerCors in an infinite loop, waiting for external changes between each run."), From 3017c590efad4db798a223b944bebfa91046deeb Mon Sep 17 00:00:00 2001 From: Pieter Bos Date: Mon, 6 May 2024 17:15:05 +0200 Subject: [PATCH 43/52] invalidate PathOrStd as well; debounce ctrl-c only for a short while --- src/hre/hre/io/Watch.scala | 19 +++++++++++++++---- src/main/vct/options/types/PathOrStd.scala | 4 +++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/hre/hre/io/Watch.scala b/src/hre/hre/io/Watch.scala index e717aa386f..355e13414e 100644 --- a/src/hre/hre/io/Watch.scala +++ b/src/hre/hre/io/Watch.scala @@ -61,7 +61,7 @@ class Watch(val mainThread: Thread) extends LazyLogging { watch => private var threads: Seq[Thread] = Nil - private var interrupts = 0 + private var debounceInterrupt = true def enroll(path: Path): Unit = { path.getParent.register( @@ -102,6 +102,15 @@ class Watch(val mainThread: Thread) extends LazyLogging { watch => } } + private def disableDebounceThread() = new Thread { + override def run(): Unit = try { + Thread.sleep(1000) + debounceInterrupt = false + } catch { + case _: InterruptedException => // do nothing + } + } + private def addThread(t: Thread): Unit = { threads = t +: threads t.setDaemon(true) @@ -135,13 +144,15 @@ class Watch(val mainThread: Thread) extends LazyLogging { watch => triggered = false - interrupts = 0 + debounceInterrupt = true } def await(): Unit = synchronized { addThread(fileWatchThread()) addThread(stdinWatchThread()) + addThread(disableDebounceThread()) + logger.info(s"[Waiting for input files to change - press ENTER to run again manually]") while(!triggered) { @@ -149,9 +160,9 @@ class Watch(val mainThread: Thread) extends LazyLogging { watch => wait() } catch { case _: InterruptedException => - if(interrupts == 0) { + if(debounceInterrupt) { logger.info(s"[Press Ctrl+C again to exit VerCors]") - interrupts = 1 + debounceInterrupt = false } else { throw new InterruptedException() } diff --git a/src/main/vct/options/types/PathOrStd.scala b/src/main/vct/options/types/PathOrStd.scala index 90efba360e..ff454507d5 100644 --- a/src/main/vct/options/types/PathOrStd.scala +++ b/src/main/vct/options/types/PathOrStd.scala @@ -36,7 +36,9 @@ sealed trait PathOrStd extends InMemoryCachedReadable with Writeable { } override def enroll(watch: Watch): Unit = this match { - case PathOrStd.Path(path) => watch.enroll(path) + case PathOrStd.Path(path) => + watch.enroll(path) + watch.invalidate(this) case PathOrStd.StdInOrOut => // do nothing } } From 68018fd44391029d90e1e63d17b0184316f01fe1 Mon Sep 17 00:00:00 2001 From: Pieter Bos Date: Mon, 6 May 2024 17:31:48 +0200 Subject: [PATCH 44/52] add java.nio NoSuchFileException in relevant places --- src/main/vct/main/stages/Resolution.scala | 3 ++- src/parsers/vct/parsers/ColCPPParser.scala | 4 ++-- src/parsers/vct/parsers/ColCParser.scala | 4 ++-- src/parsers/vct/parsers/Parser.scala | 3 ++- test/main/vct/test/integration/helper/JavaBipSpecHelper.scala | 4 ++-- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/vct/main/stages/Resolution.scala b/src/main/vct/main/stages/Resolution.scala index 645a3e0f09..fd76b16a40 100644 --- a/src/main/vct/main/stages/Resolution.scala +++ b/src/main/vct/main/stages/Resolution.scala @@ -22,6 +22,7 @@ import vct.resources.Resources import vct.result.VerificationError.UserError import java.io.{FileNotFoundException, Reader} +import java.nio.file.NoSuchFileException case object Resolution { case class InputResolutionError(errors: Seq[CheckError]) extends UserError { @@ -57,7 +58,7 @@ case class MyLocalJavaParser(blameProvider: BlameProvider) extends Resolve.SpecE cjp.parseExpr[G](CharStreams.fromReader(reader, sr.fileName), false) } } catch { - case _: FileNotFoundException => throw FileNotFound(sr.fileName) + case _: FileNotFoundException | _: NoSuchFileException => throw FileNotFound(sr.fileName) } if (x._2.nonEmpty) { throw SpecExprParseError("...") diff --git a/src/parsers/vct/parsers/ColCPPParser.scala b/src/parsers/vct/parsers/ColCPPParser.scala index 98e42740db..5e58b7e3a0 100644 --- a/src/parsers/vct/parsers/ColCPPParser.scala +++ b/src/parsers/vct/parsers/ColCPPParser.scala @@ -10,7 +10,7 @@ import vct.result.VerificationError.{Unreachable, UserError} import java.io.{FileNotFoundException, InputStreamReader, OutputStreamWriter, StringWriter} import java.nio.charset.StandardCharsets -import java.nio.file.{Files, Path, Paths} +import java.nio.file.{Files, NoSuchFileException, Path, Paths} case object CPPParser { case class PreprocessorError(fileName: String, errorCode: Int, error: String) extends UserError { @@ -85,6 +85,6 @@ case class ColCPPParser(override val origin: Origin, Files.delete(interpreted) } } catch { - case _: FileNotFoundException => throw FileNotFound(readable.fileName) + case _: FileNotFoundException | _: NoSuchFileException => throw FileNotFound(readable.fileName) } } diff --git a/src/parsers/vct/parsers/ColCParser.scala b/src/parsers/vct/parsers/ColCParser.scala index 1085d82e46..7d97c6904e 100644 --- a/src/parsers/vct/parsers/ColCParser.scala +++ b/src/parsers/vct/parsers/ColCParser.scala @@ -9,7 +9,7 @@ import vct.result.VerificationError.{Unreachable, UserError} import java.io.{FileNotFoundException, InputStreamReader, OutputStreamWriter, StringWriter} import java.nio.charset.StandardCharsets -import java.nio.file.{Files, Path, Paths} +import java.nio.file.{Files, NoSuchFileException, Path, Paths} case object CParser { case class PreprocessorError(fileName: String, errorCode: Int, error: String) extends UserError { @@ -84,6 +84,6 @@ case class ColCParser(override val origin: Origin, Files.delete(interpreted) } } catch { - case _: FileNotFoundException => throw FileNotFound(readable.fileName) + case _: FileNotFoundException | _: NoSuchFileException => throw FileNotFound(readable.fileName) } } diff --git a/src/parsers/vct/parsers/Parser.scala b/src/parsers/vct/parsers/Parser.scala index 1aefb24c35..41abbaa818 100644 --- a/src/parsers/vct/parsers/Parser.scala +++ b/src/parsers/vct/parsers/Parser.scala @@ -8,6 +8,7 @@ import vct.parsers.transform.{BlameProvider, OriginProvider} import vct.result.VerificationError.UserError import java.io.FileNotFoundException +import java.nio.file.NoSuchFileException import scala.jdk.CollectionConverters._ abstract class Parser(val origin: Origin, val blameProvider: BlameProvider) { @@ -74,6 +75,6 @@ abstract class Parser(val origin: Origin, val blameProvider: BlameProvider) { parse(CharStreams.fromReader(reader, readable.fileName)) } } catch { - case _: FileNotFoundException => throw FileNotFound(readable.fileName) + case _: FileNotFoundException | _: NoSuchFileException => throw FileNotFound(readable.fileName) } } \ No newline at end of file diff --git a/test/main/vct/test/integration/helper/JavaBipSpecHelper.scala b/test/main/vct/test/integration/helper/JavaBipSpecHelper.scala index a90df61f2a..d7faf16d0e 100644 --- a/test/main/vct/test/integration/helper/JavaBipSpecHelper.scala +++ b/test/main/vct/test/integration/helper/JavaBipSpecHelper.scala @@ -2,7 +2,7 @@ package vct.test.integration.helper import vct.options.types.{Backend, PathOrStd} -import java.nio.file.Paths +import java.nio.file.{NoSuchFileException, Paths} import org.scalactic.source import vct.col.rewrite.bip.BIP.Standalone.VerificationReport @@ -33,7 +33,7 @@ abstract class JavaBipSpecHelper extends VercorsSpec { case (expectedErr, actualErr) => s"Expected ${expectedErr.mkString(", ")}, but got ${actualErr.mkString(", ")}." } - val strReport = try { Right(reportPath.readToCompletion()) } catch { case e: FileNotFoundException => Left(e) } + val strReport = try { Right(reportPath.readToCompletion()) } catch { case e: FileNotFoundException | _: NoSuchFileException => Left(e) } val expectedReport = strReport.flatMap(strReport => VerificationReport.fromJson(strReport)) // getOrElse(fail(s"Parse error, or could not find report at $reportPath")) val reportCheck = (report, expectedReport) match { case (_, Left(err)) => s"The expected report could not be parsed, since ${err.getMessage()}" From cddbaba982736801949dbfd1d12736345fda1f87 Mon Sep 17 00:00:00 2001 From: Pieter Bos Date: Tue, 7 May 2024 10:51:31 +0200 Subject: [PATCH 45/52] case alternatives cannot contain bound variables --- .../vct/test/integration/helper/JavaBipSpecHelper.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/main/vct/test/integration/helper/JavaBipSpecHelper.scala b/test/main/vct/test/integration/helper/JavaBipSpecHelper.scala index d7faf16d0e..2356df072c 100644 --- a/test/main/vct/test/integration/helper/JavaBipSpecHelper.scala +++ b/test/main/vct/test/integration/helper/JavaBipSpecHelper.scala @@ -33,7 +33,12 @@ abstract class JavaBipSpecHelper extends VercorsSpec { case (expectedErr, actualErr) => s"Expected ${expectedErr.mkString(", ")}, but got ${actualErr.mkString(", ")}." } - val strReport = try { Right(reportPath.readToCompletion()) } catch { case e: FileNotFoundException | _: NoSuchFileException => Left(e) } + val strReport = try { + Right(reportPath.readToCompletion()) + } catch { + case e: FileNotFoundException => Left(e) + case e: NoSuchFileException => Left(e) + } val expectedReport = strReport.flatMap(strReport => VerificationReport.fromJson(strReport)) // getOrElse(fail(s"Parse error, or could not find report at $reportPath")) val reportCheck = (report, expectedReport) match { case (_, Left(err)) => s"The expected report could not be parsed, since ${err.getMessage()}" From c00b546615b2eefb6a6e29c0d7edca03afd470dc Mon Sep 17 00:00:00 2001 From: Pieter Bos Date: Tue, 7 May 2024 11:56:50 +0200 Subject: [PATCH 46/52] use file granularity, do not assume all files are in the same filesystem --- src/hre/hre/io/Watch.scala | 84 ++++++++++++++++++++++++++++++++------ 1 file changed, 71 insertions(+), 13 deletions(-) diff --git a/src/hre/hre/io/Watch.scala b/src/hre/hre/io/Watch.scala index 355e13414e..9a57daa7e8 100644 --- a/src/hre/hre/io/Watch.scala +++ b/src/hre/hre/io/Watch.scala @@ -3,7 +3,11 @@ package hre.io import com.typesafe.scalalogging.LazyLogging import sun.misc.{Signal, SignalHandler} -import java.nio.file.{ClosedWatchServiceException, FileSystem, FileSystems, Path, StandardWatchEventKinds, WatchEvent, WatchService} +import java.nio.file.{ClosedWatchServiceException, FileSystem, FileSystems, Path, StandardWatchEventKinds, WatchEvent, WatchKey, WatchService} +import java.util.concurrent.TimeUnit +import scala.collection.mutable +import scala.jdk.CollectionConverters.CollectionHasAsScala +import scala.util.control.Breaks.{break, breakable} /** * Watch facilitates that VerCors can automatically re-run when external input @@ -53,7 +57,9 @@ object Watch { } class Watch(val mainThread: Thread) extends LazyLogging { watch => - private var fileWatchService = FileSystems.getDefault.newWatchService() + private val watchServices = mutable.Map[FileSystem, WatchService]() + private val watchedDirs = mutable.Set[Path]() + private val watchedFiles = mutable.Set[Path]() private var toInvalidate: Seq[Readable] = Nil @@ -63,22 +69,68 @@ class Watch(val mainThread: Thread) extends LazyLogging { watch => private var debounceInterrupt = true - def enroll(path: Path): Unit = { - path.getParent.register( - fileWatchService, - StandardWatchEventKinds.ENTRY_CREATE, - StandardWatchEventKinds.ENTRY_DELETE, - StandardWatchEventKinds.ENTRY_MODIFY, - ) + def watchCount: Int = + watchedFiles.size + + def enroll(dirtyPath: Path): Unit = { + val path = dirtyPath.toAbsolutePath.normalize() + val parent = Option(path.getParent).getOrElse(path) + if(!watchedDirs.contains(parent)) { + watchedDirs += parent + val fs = parent.getFileSystem + val ws = watchServices.getOrElseUpdate(fs, fs.newWatchService()) + parent.register( + ws, + StandardWatchEventKinds.ENTRY_CREATE, + StandardWatchEventKinds.ENTRY_DELETE, + StandardWatchEventKinds.ENTRY_MODIFY, + ) + } + + watchedFiles += path } def invalidate(readable: Readable): Unit = { toInvalidate = readable +: toInvalidate } + private def isWatchedFile(key: WatchKey): Boolean = { + val parent = key.watchable().asInstanceOf[Path] + val events = key.pollEvents().asScala.toSeq + logger.warn(s"Events: ${events.map(_.context()).mkString(", ")}") + key.reset() + for(event <- events) { + val relPath = event.context().asInstanceOf[Path] + + if(watchedFiles.contains(parent.resolve(relPath)) || watchedFiles.contains(parent)) + return true + } + + false + } + private def fileWatchThread() = new Thread { override def run(): Unit = try { - fileWatchService.take() + watchServices.values.toSeq match { + case Nil => return + case Seq(ws) => + // Typical: normally all plain files belong to one file system. + while(!isWatchedFile(ws.take())) {} + case watchServices => + breakable { + while (true) { + // We spend approximately 0.1s per pass over all [[WatchService]]s + for (ws <- watchServices) { + val key = Option(ws.poll(100_000 / watchServices.size, TimeUnit.MICROSECONDS)) + + key match { + case Some(key) if isWatchedFile(key) => break() + case _ => // continue + } + } + } + } + } watch.signal() } catch { case _: InterruptedException => // do nothing @@ -113,6 +165,7 @@ class Watch(val mainThread: Thread) extends LazyLogging { watch => private def addThread(t: Thread): Unit = { threads = t +: threads + t.setName("[VerCors] Watch loop") t.setDaemon(true) t.start() } @@ -123,8 +176,13 @@ class Watch(val mainThread: Thread) extends LazyLogging { watch => } private def reset(): Unit = { - fileWatchService.close() - fileWatchService = FileSystems.getDefault.newWatchService() + for(ws <- watchServices.values) { + ws.close() + } + + watchServices.clear() + watchedDirs.clear() + watchedFiles.clear() for(inv <- toInvalidate) { inv.invalidate() @@ -153,7 +211,7 @@ class Watch(val mainThread: Thread) extends LazyLogging { watch => addThread(disableDebounceThread()) - logger.info(s"[Waiting for input files to change - press ENTER to run again manually]") + logger.info(s"[Waiting for ${watchedFiles.mkString("[", ", ", "]")} to change - press ENTER to run again manually]") while(!triggered) { try { From a8a99a6007f977318e6ca729333f7be4be9a5c1c Mon Sep 17 00:00:00 2001 From: Pieter Bos Date: Tue, 7 May 2024 14:56:34 +0200 Subject: [PATCH 47/52] adjustments to middleware to make them reset properly --- src/hre/hre/perf/Profile.scala | 4 +++- src/hre/hre/progress/Progress.scala | 30 ++++++++++++++++++----------- src/main/vct/main/Main.scala | 6 ++++-- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/hre/hre/perf/Profile.scala b/src/hre/hre/perf/Profile.scala index 045d6c1036..43e2ce8ada 100644 --- a/src/hre/hre/perf/Profile.scala +++ b/src/hre/hre/perf/Profile.scala @@ -16,8 +16,10 @@ case object Profile { def update(stack: Seq[String], ownUsage: ResourceUsage, doUpdateChildUsage: Boolean): Unit = currentProfile.foreach(_.update(stack, ownUsage, doUpdateChildUsage)) - def finish(): Unit = + def finish(): Unit = { currentProfile.foreach(_.finish()) + currentProfile = None + } } case class Profile() { diff --git a/src/hre/hre/progress/Progress.scala b/src/hre/hre/progress/Progress.scala index d735e9a752..1a5f1c7f2b 100644 --- a/src/hre/hre/progress/Progress.scala +++ b/src/hre/hre/progress/Progress.scala @@ -9,31 +9,39 @@ case object Progress { val UPDATE_INTERVAL_MS: Int = 100 val UPDATE_INTERAL_LONG_MS: Int = 700 - def install(forceProgress: Boolean, profile: Boolean): Unit = { + def install(profile: Boolean): Unit = { + blockLayoutUpdateTimer = Some(new Timer("[VerCors] Block layout updates")) + blockLayoutUpdate = false + newLayoutAfterTimeout = false TaskRegistry.install() - Layout.install(forceProgress) Profile.install(profile) } - def finish(): Unit = { + def finish(): Unit = synchronized { blockLayoutUpdateTask.foreach(_.cancel()) - blockLayoutUpdateTimer.purge() - blockLayoutUpdateTimer.cancel() + blockLayoutUpdateTimer.foreach { timer => + timer.purge() + timer.cancel() + } + blockLayoutUpdateTimer = None TaskRegistry.finish() Layout.update() Profile.finish() } - def abort(): Unit = { + def abort(): Unit = synchronized { blockLayoutUpdateTask.foreach(_.cancel()) - blockLayoutUpdateTimer.purge() - blockLayoutUpdateTimer.cancel() + blockLayoutUpdateTimer.foreach { timer => + timer.purge() + timer.cancel() + } + blockLayoutUpdateTimer = None TaskRegistry.abort() Layout.update() Profile.finish() } - private val blockLayoutUpdateTimer = new Timer("[VerCors] Block layout updates") + private var blockLayoutUpdateTimer: Option[Timer] = None private var blockLayoutUpdateTask: Option[TimerTask] = None private var blockLayoutUpdate = false private var newLayoutAfterTimeout = false @@ -41,7 +49,7 @@ case object Progress { private def delayNextUpdate(longDelay: Boolean): Unit = { blockLayoutUpdate = true blockLayoutUpdateTask.foreach(_.cancel()) - blockLayoutUpdateTimer.purge() + blockLayoutUpdateTimer.get.purge() blockLayoutUpdateTask = Some(new TimerTask { override def run(): Unit = Progress.synchronized { if (newLayoutAfterTimeout) { @@ -54,7 +62,7 @@ case object Progress { } }) val updateInterval = if(longDelay) UPDATE_INTERAL_LONG_MS else UPDATE_INTERVAL_MS - blockLayoutUpdateTimer.schedule(blockLayoutUpdateTask.get, updateInterval) + blockLayoutUpdateTimer.get.schedule(blockLayoutUpdateTask.get, updateInterval) } def update(): Unit = Progress.synchronized { diff --git a/src/main/vct/main/Main.scala b/src/main/vct/main/Main.scala index 0e91098053..f4736f6edc 100644 --- a/src/main/vct/main/Main.scala +++ b/src/main/vct/main/Main.scala @@ -4,7 +4,7 @@ import ch.qos.logback.classic.{Level, Logger} import com.typesafe.scalalogging.LazyLogging import hre.io.{InterruptibleInputStream, Watch} import hre.perf.Profile -import hre.progress.Progress +import hre.progress.{Layout, Progress} import org.slf4j.LoggerFactory import scopt.OParser import vct.col.ast.Node @@ -74,6 +74,8 @@ case object Main extends LazyLogging { }) } + Layout.install(options.progress) + // Make it so read calls to System.in may be interrupted with Thread.interrupt() // This causes data read between the start of reading and the interrupt to be lost. System.setIn(new InterruptibleInputStream(System.in)) @@ -84,7 +86,7 @@ case object Main extends LazyLogging { try { Watch.booleanWithWatch(options.watch, default = EXIT_CODE_SUCCESS) { - Progress.install(options.progress, options.profile) + Progress.install(options.profile) try { options.mode match { case Mode.Verify => From d759d0cf00171df87f8aeb35ce471c3e6c86b88f Mon Sep 17 00:00:00 2001 From: Pieter Bos Date: Tue, 7 May 2024 15:38:50 +0200 Subject: [PATCH 48/52] also consider RWFile in watching, do not watch cache entries --- src/hre/hre/io/InMemoryCachedReadable.scala | 1 + src/hre/hre/io/RWFile.scala | 4 ++-- src/hre/hre/io/Watch.scala | 6 ++++-- src/main/vct/importer/Util.scala | 2 +- src/viper/viper/api/backend/SilverBackend.scala | 4 ++-- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/hre/hre/io/InMemoryCachedReadable.scala b/src/hre/hre/io/InMemoryCachedReadable.scala index 09f147ad51..16692895ca 100644 --- a/src/hre/hre/io/InMemoryCachedReadable.scala +++ b/src/hre/hre/io/InMemoryCachedReadable.scala @@ -10,6 +10,7 @@ trait InMemoryCachedReadable extends Readable { private def ensureCache(): Unit = if (cache.isEmpty) { + Watch.enroll(this) val scanner = new Scanner(getReaderImpl) scanner.useDelimiter("\\A") cache = Some(if (scanner.hasNext()) scanner.next() else "") diff --git a/src/hre/hre/io/RWFile.scala b/src/hre/hre/io/RWFile.scala index 9e540efd4f..a0e6f100c1 100644 --- a/src/hre/hre/io/RWFile.scala +++ b/src/hre/hre/io/RWFile.scala @@ -4,7 +4,7 @@ import java.io.{Reader, Writer} import java.nio.charset.StandardCharsets import java.nio.file.{Files, Path} -case class RWFile(file: Path) extends InMemoryCachedReadable with Writeable { +case class RWFile(file: Path, doWatch: Boolean = true) extends InMemoryCachedReadable with Writeable { override def underlyingFile: Option[Path] = Some(file) override def fileName: String = file.toString override def isRereadable: Boolean = true @@ -19,7 +19,7 @@ case class RWFile(file: Path) extends InMemoryCachedReadable with Writeable { } override def enroll(watch: Watch): Unit = { - watch.enroll(file) + if(doWatch) watch.enroll(file) watch.invalidate(this) } } diff --git a/src/hre/hre/io/Watch.scala b/src/hre/hre/io/Watch.scala index 9a57daa7e8..6f9cc294dd 100644 --- a/src/hre/hre/io/Watch.scala +++ b/src/hre/hre/io/Watch.scala @@ -101,9 +101,11 @@ class Watch(val mainThread: Thread) extends LazyLogging { watch => key.reset() for(event <- events) { val relPath = event.context().asInstanceOf[Path] - - if(watchedFiles.contains(parent.resolve(relPath)) || watchedFiles.contains(parent)) + val path = parent.resolve(relPath) + if(watchedFiles.contains(path) || watchedFiles.contains(parent)) { + logger.info(s"Path $path changed") return true + } } false diff --git a/src/main/vct/importer/Util.scala b/src/main/vct/importer/Util.scala index 1d8d84baef..c81d2a8b70 100644 --- a/src/main/vct/importer/Util.scala +++ b/src/main/vct/importer/Util.scala @@ -31,7 +31,7 @@ case object Util { val pinnedLibrary = cacheDir.resolve("library.in") val result = cacheDir.resolve("library.colpb") - if(!Files.exists(cacheDir) || RWFile(pinnedLibrary).readToCompletion() != text) { + if(!Files.exists(cacheDir) || RWFile(pinnedLibrary, doWatch = false).readToCompletion() != text) { val res = ColPVLParser(Origin(Seq(ReadableOrigin(readable))), ConstantBlameProvider(LibraryFileBlame)).parse(readable) val context = Resolution(ConstantBlameProvider(LibraryFileBlame)).run(res) val unambiguousProgram: Program[_] = Disambiguate().dispatch(context.tasks.head.program) diff --git a/src/viper/viper/api/backend/SilverBackend.scala b/src/viper/viper/api/backend/SilverBackend.scala index cca0039434..10178ef97e 100644 --- a/src/viper/viper/api/backend/SilverBackend.scala +++ b/src/viper/viper/api/backend/SilverBackend.scala @@ -68,7 +68,7 @@ trait SilverBackend extends Backend[(silver.Program, Map[Int, col.Node[_]])] wit .replace("requires decreases", "decreases") .replace("invariant decreases", "decreases") - output.map(RWFile).foreach(_.write { writer => + output.map(RWFile(_)).foreach(_.write { writer => writer.write(silverProgramString) }) @@ -85,7 +85,7 @@ trait SilverBackend extends Backend[(silver.Program, Map[Int, col.Node[_]])] wit try { Using(Files.newBufferedWriter(f))(_.write(silverProgramString)) - SilverParserDummyFrontend().parse(f) match { + SilverParserDummyFrontend().parse(RWFile(f, doWatch = false)) match { case Left(errors) => logger.warn("Possible viper bug: silver AST does not reparse when printing as text") for(error <- errors) { From 5ed5751e0c2550e5a54195d9b982f28e0b85b1d1 Mon Sep 17 00:00:00 2001 From: Pieter Bos Date: Tue, 7 May 2024 15:44:03 +0200 Subject: [PATCH 49/52] clean up some superfluous logging lines --- src/hre/hre/io/Watch.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/hre/hre/io/Watch.scala b/src/hre/hre/io/Watch.scala index 6f9cc294dd..4b53d6f645 100644 --- a/src/hre/hre/io/Watch.scala +++ b/src/hre/hre/io/Watch.scala @@ -97,13 +97,11 @@ class Watch(val mainThread: Thread) extends LazyLogging { watch => private def isWatchedFile(key: WatchKey): Boolean = { val parent = key.watchable().asInstanceOf[Path] val events = key.pollEvents().asScala.toSeq - logger.warn(s"Events: ${events.map(_.context()).mkString(", ")}") key.reset() for(event <- events) { val relPath = event.context().asInstanceOf[Path] val path = parent.resolve(relPath) if(watchedFiles.contains(path) || watchedFiles.contains(parent)) { - logger.info(s"Path $path changed") return true } } @@ -213,7 +211,7 @@ class Watch(val mainThread: Thread) extends LazyLogging { watch => addThread(disableDebounceThread()) - logger.info(s"[Waiting for ${watchedFiles.mkString("[", ", ", "]")} to change - press ENTER to run again manually]") + logger.info(s"[Waiting for $watchCount external inputs to change - press ENTER to run again manually]") while(!triggered) { try { From 39bda1acbdef6605e274bb3cc85b4e8dde55e29d Mon Sep 17 00:00:00 2001 From: Pieter Bos Date: Tue, 7 May 2024 15:52:33 +0200 Subject: [PATCH 50/52] fix one more watch cycle --- src/parsers/vct/parsers/ColCPPParser.scala | 2 +- src/parsers/vct/parsers/ColCParser.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/parsers/vct/parsers/ColCPPParser.scala b/src/parsers/vct/parsers/ColCPPParser.scala index 5e58b7e3a0..d37e743cf0 100644 --- a/src/parsers/vct/parsers/ColCPPParser.scala +++ b/src/parsers/vct/parsers/ColCPPParser.scala @@ -78,7 +78,7 @@ case class ColCPPParser(override val origin: Origin, throw PreprocessorError(readable.fileName, process.exitValue(), writer.toString) } - val ireadable = RWFile(interpreted) + val ireadable = RWFile(interpreted, doWatch = false) val result = ColIPPParser(Origin(Seq(ReadableOrigin(ireadable))), blameProvider, Some(origin)).parse[G](ireadable) result } finally { diff --git a/src/parsers/vct/parsers/ColCParser.scala b/src/parsers/vct/parsers/ColCParser.scala index 7d97c6904e..85f6e9fa5e 100644 --- a/src/parsers/vct/parsers/ColCParser.scala +++ b/src/parsers/vct/parsers/ColCParser.scala @@ -77,7 +77,7 @@ case class ColCParser(override val origin: Origin, throw PreprocessorError(readable.fileName, process.exitValue(), writer.toString) } - val ireadable = RWFile(interpreted) + val ireadable = RWFile(interpreted, doWatch = false) val result = ColIParser(Origin(Seq(ReadableOrigin(ireadable))), blameProvider, Some(origin)).parse[G](ireadable) result } finally { From 5ce884ff5a0f0df0bbb97a383c28a541309096b3 Mon Sep 17 00:00:00 2001 From: Pieter Bos Date: Tue, 7 May 2024 15:54:14 +0200 Subject: [PATCH 51/52] . --- test/main/vct/test/integration/meta/OldExampleFileHeader.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/main/vct/test/integration/meta/OldExampleFileHeader.scala b/test/main/vct/test/integration/meta/OldExampleFileHeader.scala index 6e0b6bab82..aff1de95ed 100644 --- a/test/main/vct/test/integration/meta/OldExampleFileHeader.scala +++ b/test/main/vct/test/integration/meta/OldExampleFileHeader.scala @@ -9,7 +9,7 @@ class OldExampleFileHeader extends AnyFlatSpec { // PB TODO: We should clean up the example description at some point, but it's not a big priority. cancel() - val wrongFiles = ExampleFiles.FILES.map(RWFile).filter(_.readLines().exists(_.stripLeading().startsWith("//::"))) + val wrongFiles = ExampleFiles.FILES.map(RWFile(_)).filter(_.readLines().exists(_.stripLeading().startsWith("//::"))) for(f <- wrongFiles) { println(s"File ${f.fileName} contains a line starting with `//::`. This is the old syntax to enter a file into the test suite, but this is no longer used.") From 32e9dbfd59b7b09ad23b874588fc0d94cd6bf4fa Mon Sep 17 00:00:00 2001 From: Pieter Bos Date: Tue, 7 May 2024 17:53:11 +0200 Subject: [PATCH 52/52] make system errors much clearer, prompt user to report bug --- res/hre/logback.xml | 7 ++ src/hre/hre/log/LogHistory.scala | 23 +++++++ src/hre/hre/progress/Layout.scala | 14 ++-- src/main/vct/debug/CrashReport.scala | 98 +++++++++++++++++++++++++++ src/main/vct/main/Main.scala | 28 ++++++-- src/main/vct/main/modes/CFG.scala | 4 +- src/main/vct/main/modes/VeSUV.scala | 4 +- src/main/vct/main/modes/Verify.scala | 3 +- src/main/vct/main/modes/VeyMont.scala | 4 +- src/main/vct/options/Options.scala | 43 ------------ src/main/vct/options/types/Mode.scala | 1 - 11 files changed, 169 insertions(+), 60 deletions(-) create mode 100644 src/hre/hre/log/LogHistory.scala create mode 100644 src/main/vct/debug/CrashReport.scala diff --git a/res/hre/logback.xml b/res/hre/logback.xml index 39c59219b4..b9af617e96 100644 --- a/res/hre/logback.xml +++ b/res/hre/logback.xml @@ -8,7 +8,14 @@ + + + %d{HH:mm:ss.SSS} [%p] %m%n + + + + \ No newline at end of file diff --git a/src/hre/hre/log/LogHistory.scala b/src/hre/hre/log/LogHistory.scala new file mode 100644 index 0000000000..259f4165df --- /dev/null +++ b/src/hre/hre/log/LogHistory.scala @@ -0,0 +1,23 @@ +package hre.log + +import ch.qos.logback.classic.spi.ILoggingEvent +import ch.qos.logback.core.AppenderBase +import ch.qos.logback.core.encoder.Encoder + +import java.nio.charset.StandardCharsets + +object LogHistory { + val messages = new StringBuffer +} + +class LogHistory extends AppenderBase[ILoggingEvent] { + override def append(e: ILoggingEvent): Unit = { + if(!e.getMessage.isBlank) + LogHistory.messages.append(new String(enc.encode(e), StandardCharsets.UTF_8)) + } + + var enc: Encoder[ILoggingEvent] = null + + def setEncoder(encoder: Encoder[ILoggingEvent]): Unit = + enc = encoder +} diff --git a/src/hre/hre/progress/Layout.scala b/src/hre/hre/progress/Layout.scala index eae3b037a3..2960b555ef 100644 --- a/src/hre/hre/progress/Layout.scala +++ b/src/hre/hre/progress/Layout.scala @@ -30,13 +30,19 @@ case object Layout extends LazyLogging { def maxHeight: Int = 32 - private def esc(command: Char, args: String = ""): String = + def esc(command: Char, args: String = ""): String = "\u001b[" + args + command - private def upBy(n: Int): String = if(n==0) "" else esc('A', n.toString) + def osc(command: String, arg: String): String = + "\u001b]" + command + ";" + arg + "\u001b\\" - private def clearLine: String = esc('K') - private def clearToEnd: String = esc('J', "0") + def upBy(n: Int): String = if(n==0) "" else esc('A', n.toString) + + def clearLine: String = esc('K') + def clearToEnd: String = esc('J', "0") + + def link(uri: String, label: String): String = + osc("8", ";" + uri) + label + osc("8", ";") private var printedLines = 0 private var currentProgressMessage = "" diff --git a/src/main/vct/debug/CrashReport.scala b/src/main/vct/debug/CrashReport.scala new file mode 100644 index 0000000000..71bcd2275d --- /dev/null +++ b/src/main/vct/debug/CrashReport.scala @@ -0,0 +1,98 @@ +package vct.debug + +import hre.io.{CollectString, Readable} +import hre.log.LogHistory +import vct.main.BuildInfo +import vct.options.Options + +import java.net.{URI, URL, URLEncoder} +import java.nio.charset.StandardCharsets + +object CrashReport { + val GITHUB_URI = "https://github.com/utwente-fmt/vercors/issues/new" + + private def enc(s: String): String = URLEncoder.encode(s, StandardCharsets.UTF_8) + + def makeGithubLink(err: Throwable, args: Array[String], options: Options): String = { + var (title, body) = make(err, args, options) + + var uri: String = "" + var lastBody: String = "" + + do { + val params = s"labels=${enc("A-Bug")}&title=${enc(title)}&body=${enc(body)}" + uri = GITHUB_URI + "?" + params + lastBody = body + body = body.replaceFirst("\n[^\n]*$", "") + } while(uri.length > 8000 && body.length < lastBody.length) + + uri + } + + def make(err: Throwable, args: Array[String], options: Options): (String, String) = + s"Crash report: ${err.getMessage.take(100)}" -> ( + makeError(err) + + makeVersion() + + makeOptions(args) + + makeInputs(options) + + makeLog() + ) + + def makeError(err: Throwable): String = + s"""# Crash Message + |``` + |${err.getMessage} + |${makeStackTrace(err.getStackTrace)} + |``` + | + |""".stripMargin + + def makeStackTrace(elems: Array[StackTraceElement]): String = { + if(elems.length < 15) + elems.map(elem => s" at $elem").mkString("\n") + else + (elems.take(12).map(elem => s" at $elem") ++ Seq(" ...") ++ elems.takeRight(2).map(elem => s" at $elem")).mkString("\n") + } + + def makeVersion(): String = + s"""# Version Information + |* ${BuildInfo.name} version `${BuildInfo.version}` + |* At commit ${BuildInfo.currentCommit} from branch `${BuildInfo.currentBranch}` (changes=${BuildInfo.gitHasChanges}) + | + |""".stripMargin + + def makeOptions(args: Array[String]): String = + s"""# Arguments + |${args.mkString("`", "` `", "`")} + | + |""".stripMargin + + def makeInputs(options: Options): String = + s"""# File Inputs + |${options.inputs.map(makeInput).mkString("\n")} + | + |""".stripMargin + + def makeInput(readable: Readable): String = + if(readable.isRereadable) + s"""
+ |${readable.fileName} + | + |```${readable.fileName.split('.').last} + |${readable.readToCompletion()} + |``` + |
+ |""".stripMargin + else + s"`${readable.fileName}` cannot be read" + + def makeLog(): String = + s"""# Full Log + |
+ | + |``` + |${LogHistory.messages.toString} + |``` + |
+ |""".stripMargin +} diff --git a/src/main/vct/main/Main.scala b/src/main/vct/main/Main.scala index f4736f6edc..10d9335542 100644 --- a/src/main/vct/main/Main.scala +++ b/src/main/vct/main/Main.scala @@ -8,12 +8,15 @@ import hre.progress.{Layout, Progress} import org.slf4j.LoggerFactory import scopt.OParser import vct.col.ast.Node +import vct.debug.CrashReport import vct.main.modes.{CFG, VeSUV, Verify, VeyMont} import vct.main.stages.Transformation import vct.options.types.{Mode, Verbosity} import vct.options.Options import vct.result.VerificationError.UserError +import scala.util.control.NonFatal + case object Main extends LazyLogging { val EXIT_CODE_SUCCESS = 0 val EXIT_CODE_VERIFICATION_FAILURE = 1 @@ -37,7 +40,20 @@ case object Main extends LazyLogging { def main(args: Array[String]): Unit = try { Options.parse(args) match { case None => System.exit(EXIT_CODE_ERROR) - case Some(options) => System.exit(runOptions(options)) + case Some(options) => + try { + System.exit(runOptions(options)) + } catch { + case NonFatal(err) => + logger.error(err.getMessage) + logger.error("!*!*!*!*!*!*!*!*!*!*!*!") + logger.error("! VerCors has crashed !") + logger.error("!*!*!*!*!*!*!*!*!*!*!*!") + logger.error("") + logger.error("Please report this as a bug here:") + logger.error(CrashReport.makeGithubLink(err, args, options)) + System.exit(EXIT_CODE_ERROR) + } } } catch { case t: Throwable => @@ -99,16 +115,14 @@ case object Main extends LazyLogging { logger.info(s" ${pass.desc}") } EXIT_CODE_SUCCESS - case Mode.VeyMont => VeyMont.runOptions(options) - case Mode.VeSUV => { + case Mode.VeyMont => + VeyMont.runOptions(options) + case Mode.VeSUV => logger.info("Starting transformation") VeSUV.runOptions(options) - } - case Mode.CFG => { + case Mode.CFG => logger.info("Starting control flow graph transformation") CFG.runOptions(options) - } - case Mode.BatchTest => ??? } } finally { Progress.finish() diff --git a/src/main/vct/main/modes/CFG.scala b/src/main/vct/main/modes/CFG.scala index 9fc7cf7eee..f28983a80a 100644 --- a/src/main/vct/main/modes/CFG.scala +++ b/src/main/vct/main/modes/CFG.scala @@ -6,13 +6,15 @@ import vct.main.Main.{EXIT_CODE_ERROR, EXIT_CODE_SUCCESS} import vct.main.stages.Stages import vct.options.Options import vct.parsers.transform.ConstantBlameProvider +import vct.result.VerificationError.{SystemError, UserError} case object CFG extends LazyLogging { def runOptions(options: Options) : Int = { val collector = BlameCollector() val stages = Stages.cfgTransformationOfOptions(options, ConstantBlameProvider(collector)) stages.run(options.inputs) match { - case Left(_) => EXIT_CODE_ERROR + case Left(err: UserError) => EXIT_CODE_ERROR + case Left(err: SystemError) => throw err case Right(()) => logger.info("Transformation complete") EXIT_CODE_SUCCESS diff --git a/src/main/vct/main/modes/VeSUV.scala b/src/main/vct/main/modes/VeSUV.scala index ece778b0df..e55f034f3a 100644 --- a/src/main/vct/main/modes/VeSUV.scala +++ b/src/main/vct/main/modes/VeSUV.scala @@ -6,13 +6,15 @@ import vct.main.Main.{EXIT_CODE_ERROR, EXIT_CODE_SUCCESS} import vct.main.stages.Stages import vct.options.Options import vct.parsers.transform.ConstantBlameProvider +import vct.result.VerificationError.{SystemError, UserError} case object VeSUV extends LazyLogging { def runOptions(options: Options) : Int = { val collector = BlameCollector() val stages = Stages.vesuvOfOptions(options, ConstantBlameProvider(collector)) stages.run(options.inputs) match { - case Left(_) => EXIT_CODE_ERROR + case Left(_: UserError) => EXIT_CODE_ERROR + case Left(err: SystemError) => throw err case Right(()) => logger.info("Transformation complete") EXIT_CODE_SUCCESS diff --git a/src/main/vct/main/modes/Verify.scala b/src/main/vct/main/modes/Verify.scala index 3fc5cbd8fd..2307279432 100644 --- a/src/main/vct/main/modes/Verify.scala +++ b/src/main/vct/main/modes/Verify.scala @@ -82,8 +82,7 @@ case object Verify extends LazyLogging { logger.error(err.text) EXIT_CODE_ERROR case Left(err: VerificationError.SystemError) => - logger.error(CollectString(s => err.printStackTrace(s))) - EXIT_CODE_ERROR + throw err case Right((Nil, report)) => if(options.skipBackend){ logger.info("Verification skipped.") diff --git a/src/main/vct/main/modes/VeyMont.scala b/src/main/vct/main/modes/VeyMont.scala index cb51f3a472..cde35416aa 100644 --- a/src/main/vct/main/modes/VeyMont.scala +++ b/src/main/vct/main/modes/VeyMont.scala @@ -8,6 +8,7 @@ import vct.main.stages.Stages import vct.options.Options import vct.options.types.PathOrStd import vct.parsers.transform.ConstantBlameProvider +import vct.result.VerificationError.{SystemError, UserError} import viper.carbon.boogie.Implicits.lift object VeyMont extends LazyLogging { @@ -17,7 +18,8 @@ object VeyMont extends LazyLogging { val stages = Stages.veymontTransformationOfOptions(options, ConstantBlameProvider(collector)) logger.debug("Stages: " ++ stages.flatNames.map(_._1).mkString(", ")) stages.run(inputs) match { - case Left(value) => logger.error(value.text) + case Left(err: UserError) => logger.error(err.text) + case Left(err: SystemError) => throw err case Right(()) => logger.info("VeyMont terminated successfully.") } diff --git a/src/main/vct/options/Options.scala b/src/main/vct/options/Options.scala index a7eeccc233..9c225a318a 100644 --- a/src/main/vct/options/Options.scala +++ b/src/main/vct/options/Options.scala @@ -295,38 +295,6 @@ case object Options { .text("Output file for the control flow graph in .dot format") ), - note(""), - note("Batch Testing Mode"), - opt[Unit]("test") - .action((_, c) => c.copy(mode = Mode.BatchTest)) - .text("Enable batch testing mode: execute all tests in a directory") - .children( - opt[Path]("test-dir").required().valueName("") - .action((path, c) => c.copy(testDir = path)) - .text("The directory from which to run all tests"), - opt[Seq[Backend]]("test-filter-backend").valueName(",...") - .action((backends, c) => c.copy(testFilterBackend = Some(backends))), - opt[Seq[String]]("test-filter-include-suite").valueName(",...") - .action((suites, c) => c.copy(testFilterIncludeOnlySuites = Some(suites))), - opt[Seq[String]]("test-filter-exclude-suite").valueName(",...") - .action((suites, c) => c.copy(testFilterExcludeSuites = Some(suites))), - opt[Int]("test-workers") - .action((n, c) => c.copy(testWorkers = n)) - .text("Number of threads to start to run tests (default: 1)"), - opt[Unit]("test-coverage") - .action((_, c) => c.copy(testCoverage = true)) - .text("Generate a coverage report"), - opt[Unit]("test-failing-first") - .action((_, c) => c.copy(testFailingFirst = true)) - .text("When run twice with this option, VerCors will run the tests that failed the previous time first (cancelling a run is safe)"), - opt[Unit]("test-generate-failing-run-configs") - .action((_, c) => c.copy(testGenerateFailingRunConfigs = true)) - .text("Generates Intellij IDEA run configurations for tests that fail (and deletes recovered tests, cancelling a run is safe)"), - opt[Unit]("test-ci-output") - .action((_, c) => c.copy(testCIOutput = true)) - .text("Tailor the logging output for a CI run") - ), - note(""), note(""), arg[PathOrStd]("...").unbounded().optional() @@ -436,15 +404,4 @@ case class Options // Control flow graph options cfgOutput: Path = null, - - // Batch test options - testDir: Path = null, // required - testFilterBackend: Option[Seq[Backend]] = None, - testFilterIncludeOnlySuites: Option[Seq[String]] = None, - testFilterExcludeSuites: Option[Seq[String]] = None, - testWorkers: Int = 1, - testCoverage: Boolean = false, - testFailingFirst: Boolean = false, - testGenerateFailingRunConfigs: Boolean = false, - testCIOutput: Boolean = false, ) \ No newline at end of file diff --git a/src/main/vct/options/types/Mode.scala b/src/main/vct/options/types/Mode.scala index addf6b3c88..5405e70dc0 100644 --- a/src/main/vct/options/types/Mode.scala +++ b/src/main/vct/options/types/Mode.scala @@ -9,5 +9,4 @@ case object Mode { case object VeyMont extends Mode case object VeSUV extends Mode case object CFG extends Mode - case object BatchTest extends Mode }