diff --git a/Source/DafnyCore/CounterExampleGeneration/Constraint.cs b/Source/DafnyCore/CounterExampleGeneration/Constraint.cs new file mode 100644 index 0000000000..190b21089c --- /dev/null +++ b/Source/DafnyCore/CounterExampleGeneration/Constraint.cs @@ -0,0 +1,595 @@ +// Copyright by the contributors to the Dafny Project +// SPDX-License-Identifier: MIT +#nullable enable + +using System.Collections.Generic; +using System.Linq; +using Microsoft.Boogie; + +namespace Microsoft.Dafny; + +/// +/// This class represents constraints over partial values in the counterexample model. +/// A constraint is a Boolean expression over partial values. +/// +public abstract class Constraint { + + // We cannot add a constraint to the counterexample assumption until we know how to refer to each of the + // partial values referenced by the constraint: + private readonly List referencedValues; + public IEnumerable ReferencedValues => referencedValues.AsEnumerable(); + + protected Constraint(IEnumerable referencedValues, bool isWellFormedNessConstraint = false) { + this.referencedValues = referencedValues.ToList(); + if (isWellFormedNessConstraint) { + return; + } + foreach (var partialValue in this.referencedValues) { + partialValue.Constraints.Add(this); + } + } + + /// Return the Dafny expression corresponding to the constraint. + /// Maps a partial value to a Dafny expression by which we can refer to this value. + /// + public Expression? AsExpression(Dictionary definitions) { + if (referencedValues.Any(value => !definitions.ContainsKey(value))) { + return null; + } + var expression = AsExpressionHelper(definitions); + expression.Type = Type.Bool; + return expression; + } + + /// This is intended for debugging as we don't know apriori how to refer to partial values + public override string ToString() { + var temporaryIds = new Dictionary(); + foreach (var partialValue in referencedValues) { + temporaryIds[partialValue] = new IdentifierExpr(Token.NoToken, "E#" + partialValue.Element.Id); + } + if (this is DefinitionConstraint definitionConstraint) { + return definitionConstraint.RightHandSide(temporaryIds).ToString() ?? ""; + } + return AsExpression(temporaryIds)?.ToString() ?? ""; + } + + protected abstract Expression AsExpressionHelper(Dictionary definitions); + + + /// + /// Take a list of constraints and a dictionary of known ways to refer to partial values. + /// Update the dictionary according to the constraints in the list and return an ordered list of constraints that + /// can form a counterexample assumption. + /// + /// + /// + /// If false, do not allow new referring to partial values by identifiers + /// if True, remove constraints over literal values, since those should be self-evident + /// that are not already in knownDefinitions map + public static List ResolveAndOrder( + Dictionary knownDefinitions, + List constraints, + bool allowNewIdentifiers, + bool prune) { + Constraint? newConstraint = null; + var oldConstraints = new List(); + oldConstraints.AddRange(constraints.Where(constraint => + allowNewIdentifiers || constraint is not IdentifierExprConstraint)); + var newConstraints = new List(); + var knownAsLiteral = new HashSet(); + do { + if (newConstraint != null) { + oldConstraints.Remove(newConstraint); + } + + // Prioritize literals and display expressions + var displayConstraint = oldConstraints + .OfType() + .Where(constraint => constraint is LiteralExprConstraint || + constraint is SeqDisplayConstraint || + constraint is DatatypeValueConstraint) + .FirstOrDefault(constraint => !knownDefinitions.ContainsKey(constraint.DefinedValue) + && constraint.ReferencedValues.All(knownDefinitions.ContainsKey)); + if (displayConstraint != null) { + knownAsLiteral.Add(displayConstraint.DefinedValue); + knownDefinitions[displayConstraint.DefinedValue] = displayConstraint.RightHandSide(knownDefinitions); + knownDefinitions[displayConstraint.DefinedValue].Type = displayConstraint.DefinedValue.Type; + newConstraint = displayConstraint; + continue; + } + + // Add all constrains where we know how to refer to each referenced value + newConstraint = oldConstraints.Where(constraint => + constraint is not DefinitionConstraint && + constraint.ReferencedValues.All(knownDefinitions.ContainsKey)) + .FirstOrDefault(constraint => !prune || constraint is IdentifierExprConstraint || constraint.ReferencedValues.Any(value => !knownAsLiteral.Contains(value))); + if (newConstraint != null) { + newConstraints.Add(newConstraint); + continue; + } + + // update knownDefinitions map with new definitions + var definition = oldConstraints.OfType().FirstOrDefault(definition => + !knownDefinitions.ContainsKey(definition.DefinedValue) && + definition.ReferencedValues.All(knownDefinitions.ContainsKey)); + if (definition != null) { + if (!prune || definition is FunctionCallConstraint || + definition.referencedValues.Any(value => !knownAsLiteral.Contains(value))) { + newConstraints.AddRange(definition.WellFormed); + } + knownDefinitions[definition.DefinedValue] = definition.RightHandSide(knownDefinitions); + knownDefinitions[definition.DefinedValue].Type = definition.DefinedValue.Type; + newConstraint = definition; + continue; + } + + // append all other constraints to the end + newConstraint = oldConstraints.FirstOrDefault(constraint => !prune || constraint is FunctionCallConstraint || constraint is IdentifierExprConstraint || constraint.referencedValues.Any(value => !knownAsLiteral.Contains(value))); + if (newConstraint != null) { + if (newConstraint is DefinitionConstraint definitionConstraint) { + newConstraints.AddRange(definitionConstraint.WellFormed); + } + + newConstraints.Add(newConstraint); + } + } while (newConstraint != null); + + return newConstraints; + } + +} + +/// +/// Definition Constraint is a constraint that identifies a partial value with an expression over other partial values +/// +public abstract class DefinitionConstraint : Constraint { + + public readonly PartialValue DefinedValue; + public readonly List WellFormed; + + protected DefinitionConstraint( + IEnumerable referencedValues, + PartialValue definedValue, + List wellFormed) : base(referencedValues) { + DefinedValue = definedValue; + DefinedValue.Constraints.Add(this); + WellFormed = wellFormed; + } + + public abstract Expression RightHandSide(Dictionary definitions); + + protected override Expression AsExpressionHelper(Dictionary definitions) { + var expression = RightHandSide(definitions); + expression.Type = DefinedValue.Type; + var binaryExpr = new BinaryExpr(Token.NoToken, BinaryExpr.Opcode.Eq, definitions[DefinedValue], expression) { + Type = Type.Bool + }; + return binaryExpr; + } +} + +public class IdentifierExprConstraint : DefinitionConstraint { + private readonly string name; + + public IdentifierExprConstraint(PartialValue definedValue, string name) + : base(new List(), definedValue, new List()) { + this.name = name; + } + + public override Expression RightHandSide(Dictionary definitions) { + return new IdentifierExpr(Token.NoToken, name); + } +} + +public class ArraySelectionConstraint : DefinitionConstraint { + public PartialValue Array; + public List indices; + + public ArraySelectionConstraint(PartialValue definedValue, PartialValue array, List indices) + : base(new List() { array }, definedValue, + new List() { new ArrayLengthConstraint(array, indices) }) { + Array = array; + this.indices = indices; + } + + public override Expression RightHandSide(Dictionary definitions) { + if (indices.Count == 1) { + return new SeqSelectExpr(Token.NoToken, true, definitions[Array], indices.First(), null, Token.NoToken); + } + return new MultiSelectExpr(Token.NoToken, definitions[Array], indices.OfType().ToList()); + } +} + +public class LiteralExprConstraint : DefinitionConstraint { + + public readonly Expression LiteralExpr; + public LiteralExprConstraint(PartialValue definedValue, Expression literalExpr) + : base(new List(), definedValue, new List()) { + LiteralExpr = literalExpr; + } + + public override Expression RightHandSide(Dictionary definitions) { + return LiteralExpr; + } +} + +public abstract class MemberSelectExprConstraint : DefinitionConstraint { + + public readonly PartialValue Obj; + public readonly string MemberName; + + protected MemberSelectExprConstraint( + PartialValue definedValue, + PartialValue obj, + string memberName, + List constraint) : base(new List { obj }, definedValue, constraint) { + Obj = obj; + MemberName = memberName; + } + + public override Expression RightHandSide(Dictionary definitions) { + return new MemberSelectExpr(Token.NoToken, definitions[Obj], MemberName); + } +} + +public class MemberSelectExprDatatypeConstraint : MemberSelectExprConstraint { + public MemberSelectExprDatatypeConstraint(PartialValue definedValue, PartialValue obj, string memberName) + : base(definedValue, obj, memberName, new List()) { } +} + +public class MemberSelectExprClassConstraint : MemberSelectExprConstraint { + public MemberSelectExprClassConstraint(PartialValue definedValue, PartialValue obj, string memberName) + : base(definedValue, obj, memberName, new List { new NotNullConstraint(obj) }) { + } +} + +public class DatatypeValueConstraint : DefinitionConstraint { + + public readonly IEnumerable UnnamedDestructors; + private readonly string constructorName; + private readonly string datatypeName; + + public DatatypeValueConstraint( + PartialValue definedValue, + string datatypeName, + string constructorName, + IReadOnlyCollection unnamedDestructors) + : base(unnamedDestructors, definedValue, new List()) { + UnnamedDestructors = unnamedDestructors; + this.constructorName = constructorName; + this.datatypeName = datatypeName; + } + + public override Expression RightHandSide(Dictionary definitions) { + return new DatatypeValue(Token.NoToken, datatypeName, + constructorName, + UnnamedDestructors.Select(destructor => definitions[destructor]).ToList()); + } +} + +public class SeqSelectExprConstraint : DefinitionConstraint { + + public readonly PartialValue Seq; + public readonly PartialValue Index; + + + public SeqSelectExprConstraint(PartialValue definedValue, PartialValue seq, PartialValue index) : base( + new List { seq, index }, definedValue, + new List { new CardinalityGtThanConstraint(seq, index) }) { + Seq = seq; + Index = index; + } + + public override Expression RightHandSide(Dictionary definitions) { + return new SeqSelectExpr( + Token.NoToken, + true, + definitions[Seq], + definitions[Index], + null, + Token.NoToken); + } +} + +public class MapSelectExprConstraint : DefinitionConstraint { + + public readonly PartialValue Map; + public readonly PartialValue Key; + + + public MapSelectExprConstraint(PartialValue definedValue, PartialValue map, PartialValue key) : base( + new List { map, key }, definedValue, new List { + new ContainmentConstraint(key, map, true) + }) { + Map = map; + Key = key; + } + + public override Expression RightHandSide(Dictionary definitions) { + return new SeqSelectExpr(Token.NoToken, true, definitions[Map], definitions[Key], null, Token.NoToken); + } +} + +public class SeqSelectExprWithLiteralConstraint : DefinitionConstraint { + + public readonly PartialValue Seq; + public readonly LiteralExpr Index; + + + public SeqSelectExprWithLiteralConstraint(PartialValue definedValue, PartialValue seq, LiteralExpr index) : base( + new List { seq }, definedValue, + new List { new CardinalityGtThanLiteralConstraint(seq, index) }) { + Seq = seq; + Index = index; + } + + public override Expression RightHandSide(Dictionary definitions) { + return new SeqSelectExpr(Token.NoToken, true, definitions[Seq], Index, null, Token.NoToken); + } +} + +public class CardinalityConstraint : DefinitionConstraint { + + public readonly PartialValue Collection; + + + public CardinalityConstraint(PartialValue definedValue, PartialValue collection) : base( + new List { collection }, definedValue, new List()) { + Collection = collection; + } + + public override Expression RightHandSide(Dictionary definitions) { + return new UnaryOpExpr(Token.NoToken, UnaryOpExpr.Opcode.Cardinality, definitions[Collection]); + } +} + +public class SeqDisplayConstraint : DefinitionConstraint { + private readonly List elements; + + + public SeqDisplayConstraint(PartialValue definedValue, List elements) : base(elements, definedValue, + new List()) { + this.elements = elements; + } + + public override Expression RightHandSide(Dictionary definitions) { + return new SeqDisplayExpr(Token.NoToken, elements.ConvertAll(element => definitions[element])); + } +} + +public class SetDisplayConstraint : Constraint { + private readonly List elements; + private readonly PartialValue set; + + + public SetDisplayConstraint(PartialValue set, List elements) : base(elements.Append(set)) { + this.elements = elements; + this.set = set; + } + + protected override Expression AsExpressionHelper(Dictionary definitions) { + var setDisplayExpr = new SetDisplayExpr(Token.NoToken, true, elements.ConvertAll(element => definitions[element])); + setDisplayExpr.Type = set.Type; + return new BinaryExpr(Token.NoToken, BinaryExpr.Opcode.Eq, definitions[set], setDisplayExpr); + } +} + +public class MapKeysDisplayConstraint : Constraint { + private readonly List elements; + private readonly PartialValue map; + + + public MapKeysDisplayConstraint(PartialValue map, List elements) : base(elements.Append(map)) { + this.elements = elements; + this.map = map; + } + + protected override Expression AsExpressionHelper(Dictionary definitions) { + var setDisplayExpr = new SetDisplayExpr(Token.NoToken, true, elements.ConvertAll(element => definitions[element])); + setDisplayExpr.Type = new SetType(true, map.Type.TypeArgs[0]); + var memberSelectExpr = new MemberSelectExpr(Token.NoToken, definitions[map], "Keys"); + memberSelectExpr.Type = new SetType(true, map.Type.TypeArgs[0]); + return new BinaryExpr(Token.NoToken, BinaryExpr.Opcode.Eq, memberSelectExpr, setDisplayExpr); + } +} + +public class FunctionCallConstraint : DefinitionConstraint { + private readonly List args; + private readonly PartialValue receiver; + private readonly string functionName; + + + public FunctionCallConstraint( + PartialValue definedValue, + PartialValue receiver, + List args, + string functionName, + bool receiverIsReferenceType) : base(args.Append(receiver), definedValue, + receiverIsReferenceType + ? new List + { new NotNullConstraint(receiver), new FunctionCallRequiresConstraint(receiver, args, functionName) } + : new List { new FunctionCallRequiresConstraint(receiver, args, functionName) }) { + this.args = args; + this.receiver = receiver; + this.functionName = functionName; + } + + public override Expression RightHandSide(Dictionary definitions) { + return new ApplySuffix( + Token.NoToken, + null, + new ExprDotName(Token.NoToken, definitions[receiver], functionName, null), + args.Select(formal => + new ActualBinding(null, definitions[formal])).ToList(), + Token.NoToken); + } +} + +public class FunctionCallRequiresConstraint : Constraint { + private readonly List args; + private readonly PartialValue receiver; + private readonly string functionName; + + + public FunctionCallRequiresConstraint(PartialValue receiver, List args, string functionName) + : base(args.Append(receiver), true) { + this.args = args; + this.receiver = receiver; + this.functionName = functionName; + } + + protected override Expression AsExpressionHelper(Dictionary definitions) { + return new ApplySuffix( + Token.NoToken, + null, + new ExprDotName(Token.NoToken, definitions[receiver], functionName + ".requires", null), + args.Select(formal => + new ActualBinding(null, definitions[formal])).ToList(), + Token.NoToken); + } +} + +public class ContainmentConstraint : Constraint { + + public readonly PartialValue Element, Set; + public readonly bool IsIn; + public ContainmentConstraint(PartialValue element, PartialValue set, bool isIn) + : base(new List { element, set }) { + Element = element; + Set = set; + this.IsIn = isIn; + } + + protected override Expression AsExpressionHelper(Dictionary definitions) { + return new BinaryExpr( + Token.NoToken, + IsIn ? BinaryExpr.Opcode.In : BinaryExpr.Opcode.NotIn, + definitions[Element], + definitions[Set]); + } +} + +public class ArrayLengthConstraint : Constraint { + + public PartialValue Array; + public List indices; + + public ArrayLengthConstraint(PartialValue array, List indices) + : base(new List { array }) { + Array = array; + this.indices = indices; + } + + protected override Expression AsExpressionHelper(Dictionary definitions) { + var length0 = new MemberSelectExpr(Token.NoToken, definitions[Array], indices.Count == 1 ? "Length" : "Length0"); + length0.Type = Type.Int; + var constraint = new BinaryExpr(Token.NoToken, BinaryExpr.Opcode.Gt, length0, indices.First()); + constraint.Type = Type.Bool; + for (int i = 1; i < indices.Count; i++) { + var length = new MemberSelectExpr(Token.NoToken, definitions[Array], $"Length{i}"); + length.Type = Type.Int; + var newConstraint = new BinaryExpr(Token.NoToken, BinaryExpr.Opcode.Gt, length, indices[i]); + newConstraint.Type = Type.Bool; + constraint = new BinaryExpr(Token.NoToken, BinaryExpr.Opcode.And, constraint, newConstraint); + constraint.Type = Type.Bool; + } + return constraint; + } +} + +public class NeqConstraint : Constraint { + private readonly PartialValue value; + private readonly PartialValue neq; + public NeqConstraint(PartialValue value, PartialValue neq) : base(new List { value, neq }) { + this.value = value; + this.neq = neq; + } + + protected override Expression AsExpressionHelper(Dictionary definitions) { + return new BinaryExpr( + Token.NoToken, + BinaryExpr.Opcode.Neq, + definitions[value], + definitions[neq]); + } +} + + +public class DatatypeConstructorCheckConstraint : Constraint { + private readonly PartialValue obj; + public readonly string ConstructorName; + + public DatatypeConstructorCheckConstraint(PartialValue obj, string constructorName) + : base(new List { obj }) { + this.obj = obj; + ConstructorName = constructorName; + } + + protected override Expression AsExpressionHelper(Dictionary definitions) { + return new MemberSelectExpr(Token.NoToken, definitions[obj], ConstructorName + "?"); + } +} + +public class CardinalityGtThanConstraint : Constraint { + private readonly PartialValue collection; + private readonly PartialValue bound; + + public CardinalityGtThanConstraint(PartialValue collection, PartialValue bound) + : base(new List { collection, bound }) { + this.collection = collection; + this.bound = bound; + } + + protected override Expression AsExpressionHelper(Dictionary definitions) { + var cardinalityExpr = new UnaryOpExpr(Token.NoToken, UnaryOpExpr.Opcode.Cardinality, definitions[collection]) { + Type = Type.Int + }; + return new BinaryExpr(Token.NoToken, BinaryExpr.Opcode.Gt, cardinalityExpr, definitions[bound]); + } +} + +public class CardinalityGtThanLiteralConstraint : Constraint { + private readonly PartialValue collection; + private readonly LiteralExpr bound; + + public CardinalityGtThanLiteralConstraint(PartialValue collection, LiteralExpr bound) + : base(new List { collection }) { + this.collection = collection; + this.bound = bound; + } + + protected override Expression AsExpressionHelper(Dictionary definitions) { + var cardinalityExpr = new UnaryOpExpr(Token.NoToken, UnaryOpExpr.Opcode.Cardinality, definitions[collection]) { + Type = Type.Int + }; + return new BinaryExpr(Token.NoToken, BinaryExpr.Opcode.Gt, cardinalityExpr, bound); + } +} + +public class TypeTestConstraint : Constraint { + public readonly Type Type; + private readonly PartialValue value; + public TypeTestConstraint(PartialValue value, Type type) : base(new List { value }) { + Type = type; + this.value = value; + } + + protected override Expression AsExpressionHelper(Dictionary definitions) { + return new TypeTestExpr(Token.NoToken, definitions[value], Type); + } +} + +public class NotNullConstraint : Constraint { + private readonly PartialValue value; + + public NotNullConstraint(PartialValue value) : base(new List { value }) { + this.value = value; + } + + protected override Expression AsExpressionHelper(Dictionary definitions) { + var nullValue = new LiteralExpr(Token.NoToken) { + Type = new InferredTypeProxy() + }; + return new BinaryExpr(Token.NoToken, BinaryExpr.Opcode.Neq, definitions[value], nullValue); + } +} \ No newline at end of file diff --git a/Source/DafnyCore/CounterExampleGeneration/DafnyModel.cs b/Source/DafnyCore/CounterExampleGeneration/DafnyModel.cs index 9333dafe9e..1ab7cfebe7 100644 --- a/Source/DafnyCore/CounterExampleGeneration/DafnyModel.cs +++ b/Source/DafnyCore/CounterExampleGeneration/DafnyModel.cs @@ -1,5 +1,6 @@ -// Copyright by the contributors to the Dafny Project +// Copyright by the contributors to the Dafny Project // SPDX-License-Identifier: MIT +#nullable enable using System; using System.Collections.Generic; @@ -8,6 +9,7 @@ using System.Numerics; using System.Text; using System.Text.RegularExpressions; +using Microsoft.BaseTypes; using Microsoft.Boogie; namespace Microsoft.Dafny { @@ -15,12 +17,13 @@ namespace Microsoft.Dafny { /// /// A wrapper around Boogie's Model class that allows extracting /// types and values of Elements representing Dafny variables. The three core - /// methods are: GetDafnyType, CanonicalName, and GetExpansion + /// methods are: GetDafnyType, DatatypeConstructorName, and GetExpansion /// public class DafnyModel { + public readonly List LoopGuards; private readonly DafnyOptions options; public readonly Model Model; - public readonly List States = new(); + public readonly List States = new(); public static readonly UserDefinedType UnknownType = new(new Token(), "?", null); private readonly ModelFuncWrapper fSetSelect, fSeqLength, fSeqIndex, fBox, @@ -28,24 +31,18 @@ public class DafnyModel { fNull, fSetUnion, fSetIntersection, fSetDifference, fSetUnionOne, fSetEmpty, fSeqEmpty, fSeqBuild, fSeqAppend, fSeqDrop, fSeqTake, fSeqUpdate, fSeqCreate, fU2Real, fU2Bool, fU2Int, - fMapDomain, fMapElements, fMapBuild, fIs, fIsBox, fUnbox; + fMapDomain, fMapElements, fMapValues, fMapBuild, fMapEmpty, fIs, fIsBox, fUnbox, fLs, fLz; private readonly Dictionary datatypeValues = new(); - - // maps a numeric type (int, real, bv4, etc.) to the set of integer - // values of that type that appear in the model. - private readonly Dictionary> reservedNumerals = new(); - // set of all UTF values for characters that appear in the model - private readonly HashSet reservedChars = new(); - private bool isTrueReserved; // True if "true" appears anywhere in the model - // maps an element representing a primitive to its string representation - private readonly Dictionary reservedValuesMap = new(); - // maps width to a unique object representing bitvector type of such width - private readonly Dictionary bitvectorTypes = new(); + private readonly List bitvectorFunctions = new(); // the model will begin assigning characters starting from this utf value - private const int FirstCharacterUtfValue = 65; // 'A' private static readonly Regex UnderscoreRemovalRegex = new("__"); + // This set is used by GetDafnyType to prevent infinite recursion + private HashSet exploredElements = new(); + + private readonly Dictionary concretizedValues = new(); + public DafnyModel(Model model, DafnyOptions options) { Model = model; this.options = options; @@ -70,7 +67,9 @@ public DafnyModel(Model model, DafnyOptions options) { fSetDifference = new ModelFuncWrapper(this, "Set#Difference", 2, 0); fMapDomain = new ModelFuncWrapper(this, "Map#Domain", 1, 0); fMapElements = new ModelFuncWrapper(this, "Map#Elements", 1, 0); + fMapValues = new ModelFuncWrapper(this, "Map#Values", 1, 0); fMapBuild = new ModelFuncWrapper(this, "Map#Build", 3, 0); + fMapEmpty = new ModelFuncWrapper(this, "Map#Empty", 0, 0); fIs = new ModelFuncWrapper(this, "$Is", 2, tyArgMultiplier); fIsBox = new ModelFuncWrapper(this, "$IsBox", 2, 0); fBox = new ModelFuncWrapper(this, "$Box", 1, tyArgMultiplier); @@ -86,15 +85,88 @@ public DafnyModel(Model model, DafnyOptions options) { fTag = new ModelFuncWrapper(this, "Tag", 1, 0); fBv = new ModelFuncWrapper(this, "TBitvector", 1, 0); fUnbox = new ModelFuncWrapper(this, "$Unbox", 2, 0); + fLs = new ModelFuncWrapper(this, "$LS", 1, 0); + fLz = new ModelFuncWrapper(this, "$LZ", 0, 0); InitDataTypes(); - RegisterReservedChars(); - RegisterReservedInts(); - RegisterReservedBools(); - RegisterReservedReals(); RegisterReservedBitVectors(); + LoopGuards = new List(); foreach (var s in model.States) { - var sn = new DafnyModelState(this, s); + var sn = new PartialState(this, s); States.Add(sn); + if (sn.IsLoopEntryState) { + LoopGuards.Add("counterexampleLoopGuard" + LoopGuards.Count); + } + sn.LoopGuards = LoopGuards.ToList(); + } + } + + public void AssignConcretePrimitiveValues() { + bool isTrueReserved = false; + foreach (var app in fU2Bool.Apps) { + isTrueReserved |= ((Model.Boolean)app.Result).Value; + } + foreach (var element in Model.Elements) { + var type = GetFormattedDafnyType(element); + if (type is BoolType && GetLiteralExpression(element, type) == null) { + if (isTrueReserved) { + concretizedValues[element] = new LiteralExpr(Token.NoToken, false); + } else { + concretizedValues[element] = new LiteralExpr(Token.NoToken, true); + } + continue; + } + if (GetLiteralExpression(element, type) != null) { + continue; + } + ModelFuncWrapper? otherValuesFunction = null; + switch (type) { + case BitvectorType bitvectorType: { + var funcName = "U_2_bv" + bitvectorType.Width; + if (Model.HasFunc(funcName)) { + otherValuesFunction = new ModelFuncWrapper(Model.GetFunc(funcName), 0); + } + break; + } + case CharType: + otherValuesFunction = fCharToInt; + break; + case RealType: + otherValuesFunction = fU2Real; + break; + case IntType: + otherValuesFunction = fU2Int; + break; + default: + continue; + } + var reservedValues = otherValuesFunction!.Apps + .Select(app => GetLiteralExpression(app.Result, type)) + .OfType() + .Select(literal => literal.ToString()).ToHashSet(); + reservedValues.UnionWith(concretizedValues.Values.Select(literal => literal.ToString())); + int numericalValue = -1; + LiteralExpr? literal = null; + bool literalIsReserved = true; + while (literalIsReserved) { + numericalValue++; + switch (type) { + case BitvectorType: + case IntType: { + literal = new LiteralExpr(Token.NoToken, BigInteger.Parse(numericalValue.ToString())); + break; + } + case CharType: + literal = new CharLiteralExpr(Token.NoToken, PrettyPrintChar(numericalValue)); + break; + case RealType: + literal = new LiteralExpr(Token.NoToken, BigDec.FromString(numericalValue.ToString())); + break; + } + if (!reservedValues.Contains(literal!.ToString())) { + literalIsReserved = false; + } + } + concretizedValues[element] = literal!; } } @@ -113,14 +185,17 @@ public static DafnyModel ExtractModel(DafnyOptions options, string mv) { public override string ToString() { var result = new StringBuilder(); + AssignConcretePrimitiveValues(); + result.AppendLine("WARNING: the following counterexample may be inconsistent or invalid. See dafny.org/dafny/DafnyRef/DafnyRef#sec-counterexamples"); + if (LoopGuards.Count > 0) { + result.AppendLine("Temporary variables to describe counterexamples: "); + foreach (var loopGuard in LoopGuards) { + result.AppendLine($"ghost var {loopGuard} : bool := false;"); + } + } foreach (var state in States.Where(state => state.StateContainsPosition())) { result.AppendLine(state.FullStateName + ":"); - var vars = state.ExpandedVariableSet(-1); - foreach (var variable in vars) { - result.AppendLine($"\t{variable.ShortName} : " + - $"{DafnyModelTypeUtils.GetInDafnyFormat(variable.Type)} = " + - $"{variable.Value}"); - } + result.AppendLine(state.AsAssumption().ToString()); } return result.ToString(); } @@ -145,71 +220,33 @@ private void InitDataTypes() { } } - /// Register all char values specified by the model - private void RegisterReservedChars() { - foreach (var app in fCharToInt.Apps) { - if (int.TryParse(((Model.Integer)app.Result).Numeral, - out var UTFCode) && UTFCode is <= char.MaxValue and >= 0) { - reservedChars.Add(UTFCode); - } - } - } - /// /// Return the character representation of a UTF code understood by Dafny /// This is either the character itself, if it is a parsable ASCII, /// Escaped character, for the cases specified in Dafny manual, /// Or escaped UTF code otherwise /// - private string PrettyPrintChar(int UTFCode) { - switch (UTFCode) { + private static string PrettyPrintChar(int utfCode) { + switch (utfCode) { case 0: - return "'\\0'"; + return "\\0"; case 9: - return "'\\t'"; + return "\\t"; case 10: - return "'\\n'"; + return "\\n"; case 13: - return "'\\r'"; + return "\\r"; case 34: - return "'\\\"'"; + return "\\\""; case 39: - return "'\\\''"; + return "\\\'"; case 92: - return "'\\\\'"; + return "\\\\"; default: - if ((UTFCode >= 32) && (UTFCode <= 126)) { - return $"'{Convert.ToChar(UTFCode)}'"; + if (utfCode is >= 32 and <= 126) { + return $"{Convert.ToChar(utfCode)}"; } - return $"'\\u{UTFCode:X4}'"; - } - } - - /// Registered all int values specified by the model - private void RegisterReservedInts() { - reservedNumerals[Type.Int] = new(); - foreach (var app in fU2Int.Apps) { - if (app.Result is Model.Integer integer && int.TryParse(integer.Numeral, out int value)) { - reservedNumerals[Type.Int].Add(value); - } - } - } - - /// Registered all bool values specified by the model - private void RegisterReservedBools() { - foreach (var app in fU2Bool.Apps) { - isTrueReserved |= ((Model.Boolean)app.Result).Value; - } - } - - /// Registered all real values specified by the model - private void RegisterReservedReals() { - reservedNumerals[Type.Real] = new(); - foreach (var app in fU2Real.Apps) { - var valueAsString = app.Result.ToString()?.Split(".")[0] ?? ""; - if ((app.Result is Model.Real) && int.TryParse(valueAsString, out int value)) { - reservedNumerals[Type.Real].Add(value); - } + return $"\\U{{{utfCode:X4}}}"; } } @@ -220,46 +257,39 @@ private void RegisterReservedBitVectors() { if (!bvFuncName.IsMatch(func.Name)) { continue; } - - int width = int.Parse(func.Name[6..]); - if (!bitvectorTypes.ContainsKey(width)) { - bitvectorTypes[width] = new BitvectorType(options, width); - } - - var type = bitvectorTypes[width]; - - if (!reservedNumerals.ContainsKey(type)) { - reservedNumerals[type] = new(); - } - - foreach (var app in func.Apps) { - if (int.TryParse((app.Result as Model.BitVector).Numeral, - out var value)) { - reservedNumerals[type].Add(value); - } - } + bitvectorFunctions.Add(func); } } - /// - /// Return True iff the variable name is referring to a variable that has - /// a direct analog in Dafny source (i.e. not $Heap, $_Frame, $nw, etc.) - /// - public static bool IsUserVariableName(string name) => - !name.Contains("$") && name.Count(c => c == '#') <= 1; - - public bool ElementIsNull(Model.Element element) => element == fNull.GetConstant(); - /// /// Return the name of a 0-arity type function that maps to the element if such /// a function exists and is unique. Return null otherwise. /// - private static string GetTrueTypeName(Model.Element element) { - return element?.Names.FirstOrDefault(funcTuple => funcTuple.Func.Arity == 0)?.Func.Name; + private static string? GetTrueTypeName(Model.Element element) { + return element.Names.FirstOrDefault(funcTuple => funcTuple.Func.Arity == 0)?.Func.Name; } /// Get the Dafny type of an element - internal Type GetDafnyType(Model.Element element) { + internal Type GetFormattedDafnyType(Model.Element element) { + exploredElements = new HashSet(); + return DafnyModelTypeUtils.GetInDafnyFormat(GetDafnyType(element)); + } + + internal void AddTypeConstraints(PartialValue partialValue) { + foreach (var typeElement in GetIsResults(partialValue.Element)) { + var reconstructedType = DafnyModelTypeUtils.GetInDafnyFormat(ReconstructType(typeElement)); + if (reconstructedType.ToString() != partialValue.Type.ToString()) { + var _ = new TypeTestConstraint(partialValue, reconstructedType); + } + } + } + + /// Get the Dafny type of an element + private Type GetDafnyType(Model.Element element) { + if (exploredElements.Contains(element)) { + return UnknownType; + } + exploredElements.Add(element); switch (element.Kind) { case Model.ElementKind.Boolean: return Type.Bool; @@ -270,7 +300,7 @@ internal Type GetDafnyType(Model.Element element) { case Model.ElementKind.BitVector: return new BitvectorType(options, ((Model.BitVector)element).Size); case Model.ElementKind.Uninterpreted: - return GetDafnyType(element as Model.Uninterpreted); + return GetDafnyType((element as Model.Uninterpreted)!); case Model.ElementKind.DataValue: if (((Model.DatatypeValue)element).ConstructorName is "-" or "/") { return GetDafnyType( @@ -295,10 +325,351 @@ internal Type GetDafnyType(Model.Element element) { return result; } + private Expression? GetLiteralExpression(Model.Element element, Type type) { + var result = GetLiteralExpressionHelper(element, type); + if (concretizedValues.ContainsKey(element) && result == null) { + result = concretizedValues[element]; + } + if (result != null) { + result.Type = type; + } + return result; + } + + /// + /// If the provided represents a literal in Dafny, return that literal. + /// Otherwise, return null. + /// + private Expression? GetLiteralExpressionHelper(Model.Element element, Type type) { + if (Equals(element, fNull.GetConstant())) { + return new LiteralExpr(Token.NoToken); + } + + if (element is not Model.Real && element is Model.Number number) { + return new LiteralExpr(Token.NoToken, BigInteger.Parse(number.Numeral)); + } + + if (element is Model.Real real) { + return new LiteralExpr(Token.NoToken, BigDec.FromString(real.ToString())); + } + + if (element is Model.Boolean boolean) { + return new LiteralExpr(Token.NoToken, boolean.Value); + } + + if (element.Kind == Model.ElementKind.DataValue) { + var datatypeValue = (Model.DatatypeValue)element; + switch (datatypeValue.ConstructorName) { + case "-": + return new NegationExpression(Token.NoToken, + GetLiteralExpression(datatypeValue.Arguments.First(), type)); + case "/": + return new BinaryExpr(Token.NoToken, BinaryExpr.Opcode.Div, + GetLiteralExpression(datatypeValue.Arguments[0], type), + GetLiteralExpression(datatypeValue.Arguments[1], type)); + } + } + + var unboxedValue = fU2Int.OptEval(element); + unboxedValue ??= fU2Bool.OptEval(element); + unboxedValue ??= fU2Real.OptEval(element); + if (unboxedValue != null) { + return GetLiteralExpression(unboxedValue, type); + } + + if (fCharToInt.OptEval(element) is Model.Integer literalCharValue) { + if (int.TryParse(literalCharValue.Numeral, + out var utfCode) && utfCode is <= char.MaxValue and >= 0) { + return new CharLiteralExpr(Token.NoToken, PrettyPrintChar(utfCode)); + } + } + + foreach (var bitvectorFunction in bitvectorFunctions) { + if (bitvectorFunction.OptEval(element) is Model.Number literalBitVectorValur) { + return new LiteralExpr(Token.NoToken, + BigInteger.Parse(literalBitVectorValur.Numeral)); + } + } + + return null; + } + + public void GetExpansion(PartialState state, PartialValue value) { + var literalExpr = GetLiteralExpression(value.Element, value.Type); + if (literalExpr != null) { + var _ = new LiteralExprConstraint(value, literalExpr); + return; + } + + if (value.Nullable) { + var _ = new NotNullConstraint(value); + } + + if (value.Type is BitvectorType || value.Type is CharType || value.Type is RealType || value.Type is BoolType || value.Type is IntType) { + foreach (var element in Model.Elements.Where(element => !Equals(element, value.Element))) { + var elementType = GetFormattedDafnyType(element); + if (elementType.ToString() == value.Type.ToString()) { + var partialValue = PartialValue.Get(element, state); + var _ = new NeqConstraint(value, partialValue); + } + } + return; + } + + var valueIsDatatype = datatypeValues.TryGetValue(value.Element, out var fnTuple); + + var layerValue = fLz.GetConstant(); + while (layerValue != null && fLs.AppWithArg(0, layerValue) != null && !Equals(fLs.AppWithArg(0, layerValue)!.Result, fLz.GetConstant())) { + layerValue = fLs.AppWithArg(0, layerValue)!.Result; + } + var functionApplications = GetFunctionConstants(value.Element, layerValue); + foreach (var functionApplication in functionApplications) { + var result = PartialValue.Get(functionApplication.Result, state); + var args = functionApplication.Args.Select(arg => PartialValue.Get(arg, state)).ToList(); + args = Equals(functionApplication.Args[0], layerValue) ? args.Skip(2).ToList() : args.Skip(1).ToList(); + var _ = new FunctionCallConstraint(result, value, args, functionApplication.Func.Name.Split(".").Last(), !valueIsDatatype); + } + + if (valueIsDatatype) { + var __ = new DatatypeConstructorCheckConstraint(value, fnTuple!.Func.Name.Split(".").Last()); + // Elt is a datatype value + var destructors = GetDestructorFunctions(value.Element).OrderBy(f => f.Name).ToList(); + if (destructors.Count > fnTuple.Args.Length) { + // Try to filter out predicate functions + // (that follow a format very similar to that of destructor names) + destructors = destructors.Where(destructor => + fnTuple.Args.Any(arg => Equals(destructor.OptEval(value.Element), arg))) + .ToList(); + } + + var elements = new List(); + if (destructors.Count == fnTuple.Args.Length) { + // we know all destructor names + foreach (var func in destructors) { + if (func.OptEval(value.Element) is not { } modelElement) { + continue; + } + var element = PartialValue.Get(UnboxNotNull(modelElement), state); + elements.Add(element); + var elementName = UnderscoreRemovalRegex.Replace(func.Name.Split(".").Last(), "_"); + var _ = new MemberSelectExprDatatypeConstraint(element, value, elementName); + } + } else { + // we don't know destructor names, so we use indices instead + for (int i = 0; i < fnTuple.Args.Length; i++) { + var element = PartialValue.Get(UnboxNotNull(fnTuple.Args[i]), state); + elements.Add(element); + } + } + var ___ = new DatatypeValueConstraint(value, value.Type.ToString(), fnTuple.Func.Name.Split(".").Last(), elements); + return; + } + + switch (value.Type) { + case SeqType: { + if (fSeqEmpty.AppWithResult(value.Element) != null) { + var _ = new LiteralExprConstraint(value, new SeqDisplayExpr(Token.NoToken, new List())); + return; + } + var lenghtTuple = fSeqLength.AppWithArg(0, value.Element); + BigNum seqLength = BigNum.MINUS_ONE; + if (lenghtTuple != null) { + var lengthValue = PartialValue.Get(lenghtTuple.Result, state); + var lengthValueLiteral = GetLiteralExpression(lengthValue.Element, lengthValue.Type); + if (lengthValueLiteral != null) { + BigNum.TryParse(lengthValueLiteral.ToString(), out seqLength); + } + var _ = new CardinalityConstraint(lengthValue, value); + } + + // Sequences can be constructed with the build operator: + List elements = new(); + + Model.Element substring = value.Element; + while (fSeqBuild.AppWithResult(substring) != null) { + elements.Insert(0, PartialValue.Get(UnboxNotNull(fSeqBuild.AppWithResult(substring)!.Args[1]), state)); + substring = fSeqBuild.AppWithResult(substring)!.Args[0]; + } + + for (int i = 0; i < elements.Count; i++) { + var index = new LiteralExpr(Token.NoToken, i) { + Type = Type.Int + }; + var _ = new SeqSelectExprWithLiteralConstraint(elements[i], value, index); + } + + if (elements.Count == 0) { + foreach (var funcTuple in fSeqIndex.AppsWithArg(0, value.Element)) { + var elementId = PartialValue.Get(funcTuple.Args[1], state); + var elementIdTry = GetLiteralExpression(funcTuple.Args[1], Type.Int); + if (elementIdTry != null && elementIdTry.ToString().Contains("-")) { + continue; + } + if (elementIdTry != null && BigNum.TryParse(elementIdTry.ToString(), out var elementIdTryBigNum)) { + if (!seqLength.Equals(BigNum.MINUS_ONE) && !(elementIdTryBigNum - seqLength).IsNegative) { + continue; // element out of bounds for sequence + } + } + var element = PartialValue.Get(UnboxNotNull(funcTuple.Result), state); + var _ = new SeqSelectExprConstraint(element, value, elementId); + } + } else { + var _ = new SeqDisplayConstraint(value, elements); + } + + return; + } + case SetType: { + if (fMapDomain.AppsWithResult(value.Element).Any()) { + foreach (var map in fMapDomain.AppsWithResult(value.Element)) { + var mapValue = PartialValue.Get(map.Args[0], state); + var _ = new MemberSelectExprDatatypeConstraint(value, mapValue, "Keys"); + } + return; + } + if (fMapValues.AppsWithResult(value.Element).Any()) { + foreach (var map in fMapValues.AppsWithResult(value.Element)) { + var mapValue = PartialValue.Get(map.Args[0], state); + var _ = new MemberSelectExprDatatypeConstraint(value, mapValue, "Values"); + } + } + if (fSetEmpty.AppWithResult(value.Element) != null) { + var _ = new LiteralExprConstraint(value, new SetDisplayExpr(Token.NoToken, true, new List())); + return; + } + + foreach (var tpl in fSetSelect.AppsWithArg(0, value.Element)) { + var setElement = PartialValue.Get(UnboxNotNull(tpl.Args[1]), state); + var containment = tpl.Result; + if (containment is Model.Boolean boolean) { + var _ = new ContainmentConstraint(setElement, value, boolean.Value); + } + } + + return; + } + case MapType: { + var mapKeysAdded = new HashSet(); // prevents mapping a key to multiple values + var mapsElementsVisited = new HashSet(); // prevents infinite recursion + var current = value.Element; + var mapBuilds = fMapBuild.AppsWithResult(current).ToList(); + var result = new List(); + while (mapBuilds.Count != 0) { + foreach (var mapBuild in mapBuilds.Where(m => Equals(m.Args[0], current))) { + result.AddRange(AddMappingHelper( + state, + value, + Unbox(mapBuild.Args[1]), + Unbox(mapBuild.Args[2]), + mapKeysAdded)); + } + + mapsElementsVisited.Add(current); + var nextMapBuild = mapBuilds.FirstOrDefault(m => !mapsElementsVisited.Contains(m.Args[0])); + if (nextMapBuild == null) { + return; + } + + current = nextMapBuild.Args[0]; + mapBuilds = fMapBuild.AppsWithResult(nextMapBuild.Args[0]) + .Where(m => !mapsElementsVisited.Contains(m.Args[0])).ToList(); + result.AddRange(AddMappingHelper( + state, + value, + Unbox(nextMapBuild.Args[1]), + Unbox(nextMapBuild.Args[2]), + mapKeysAdded)); + } + + var mapDomain = fMapDomain.OptEval(current); + var mapElements = fMapElements.OptEval(current); + if (mapDomain != null && mapElements != null) { + foreach (var app in fSetSelect.AppsWithArg(0, mapDomain)) { + var valueElement = fSetSelect.OptEval(mapElements, app.Args[1]); + if (valueElement != null) { + valueElement = Unbox(valueElement); + } + result.AddRange(AddMappingHelper( + state, + value, + Unbox(app.Args[1]), + valueElement, + mapKeysAdded, !((Model.Boolean)app.Result).Value)); + } + } + + + if (!result.Any() && fMapEmpty.AppWithResult(value.Element) != null) { + var _ = new LiteralExprConstraint(value, new MapDisplayExpr(Token.NoToken, true, new List())); + } + + return; + + } + default: { + + var heap = state.State.TryGet("$Heap"); + // Elt is an array or an object: + if (heap == null) { + return; + } + + var constantFields = GetDestructorFunctions(value.Element).OrderBy(f => f.Name).ToList(); + var fields = fSetSelect.AppsWithArgs(0, heap, 1, value.Element).ToList(); + + foreach (var fieldFunc in constantFields) { + if (fieldFunc.OptEval(value.Element) is not { } modelElement) { + continue; + } + var field = PartialValue.Get(UnboxNotNull(modelElement), state); + var fieldName = UnderscoreRemovalRegex.Replace(fieldFunc.Name.Split(".").Last(), "_"); + if (fieldName.Contains("#")) { + continue; + } + var _ = new MemberSelectExprClassConstraint(field, value, fieldName); + } + + if (!fields.Any()) { + return; + } + + foreach (var tpl in fSetSelect.AppsWithArg(0, fields.ToList()[0].Result)) { + foreach (var fieldName in GetFieldNames(tpl.Args[1])) { + if (fieldName != "alloc") { + var field = PartialValue.Get(UnboxNotNull(tpl.Result), state); + // make sure the field in quetion is not an array index + if (fieldName.Contains("#")) { + continue; + } + if (fieldName.StartsWith('[') && fieldName.EndsWith(']')) { + var indexStrings = fieldName.TrimStart('[').TrimEnd(']').Split(","); + var indices = new List(); + foreach (var indexString in indexStrings) { + if (BigInteger.TryParse(indexString, out var index)) { + var indexLiteral = new LiteralExpr(Token.NoToken, index); + indexLiteral.Type = Type.Int; + indices.Add(indexLiteral); + } + } + if (indices.Count != indexStrings.Length) { + continue; + } + var _ = new ArraySelectionConstraint(field, value, indices); + } else { + var _ = new MemberSelectExprClassConstraint(field, value, fieldName); + } + } + } + } + return; + } + } + } + /// /// Get the Dafny type of the value indicated by /// This is in contrast to ReconstructType, which returns the type indicated by the element itself. - /// This method tries to extract the base type (so seq instead of string) + /// This method tries to extract the base type (so sequence of characters instead of string) /// private Type GetDafnyType(Model.Uninterpreted element) { var finalResult = UnknownType; @@ -312,43 +683,30 @@ private Type GetDafnyType(Model.Uninterpreted element) { } } var seqOperation = fSeqAppend.AppWithResult(element); - seqOperation ??= fSeqDrop.AppWithResult(element); - seqOperation ??= fSeqTake.AppWithResult(element); - seqOperation ??= fSeqUpdate.AppWithResult(element); - if (seqOperation != null) { - return GetDafnyType(seqOperation.Args[0]); - } + if (seqOperation != null && !exploredElements.Contains(seqOperation.Args[0])) { return GetDafnyType(seqOperation.Args[0]); } + seqOperation = fSeqDrop.AppWithResult(element); + if (seqOperation != null && !exploredElements.Contains(seqOperation.Args[0])) { return GetDafnyType(seqOperation.Args[0]); } + seqOperation = fSeqTake.AppWithResult(element); + if (seqOperation != null && !exploredElements.Contains(seqOperation.Args[0])) { return GetDafnyType(seqOperation.Args[0]); } + seqOperation = fSeqUpdate.AppWithResult(element); + if (seqOperation != null && !exploredElements.Contains(seqOperation.Args[0])) { return GetDafnyType(seqOperation.Args[0]); } seqOperation = fSeqBuild.AppWithResult(element); - if (seqOperation != null) { - return new SeqType(GetDafnyType(Unbox(seqOperation.Args[1]))); - } + if (seqOperation != null && !exploredElements.Contains(UnboxNotNull(seqOperation.Args[1]))) { return new SeqType(GetDafnyType(UnboxNotNull(seqOperation.Args[1]))); } seqOperation = fSeqCreate.AppWithResult(element); - seqOperation ??= fSeqEmpty.AppWithResult(element); - if (seqOperation != null) { - return new SeqType(ReconstructType(seqOperation.Args.First())); - } + if (seqOperation != null && !exploredElements.Contains(UnboxNotNull(seqOperation.Args.First()))) { return new SeqType(ReconstructType(seqOperation.Args.First())); } + if (fSeqEmpty.AppWithResult(element) != null) { return new SeqType(null); } var setOperation = fSetUnion.AppWithResult(element); - setOperation ??= fSetIntersection.AppWithResult(element); - setOperation ??= fSetDifference.AppWithResult(element); - if (setOperation != null) { - return GetDafnyType(setOperation.Args[0]); - } + if (setOperation != null && !exploredElements.Contains(setOperation.Args[0])) { return GetDafnyType(setOperation.Args[0]); } + setOperation = fSetIntersection.AppWithResult(element); + if (setOperation != null && !exploredElements.Contains(setOperation.Args[0])) { return GetDafnyType(setOperation.Args[0]); } + setOperation = fSetDifference.AppWithResult(element); + if (setOperation != null && !exploredElements.Contains(setOperation.Args[0])) { return GetDafnyType(setOperation.Args[0]); } setOperation = fSetUnionOne.AppWithResult(element); - if (setOperation != null) { - return new SetType(true, GetDafnyType(Unbox(setOperation.Args[1]))); - } - setOperation = fSetEmpty.AppWithResult(element); - if (setOperation != null) { - var setElement = fSetSelect.AppWithArg(0, element); - if (setElement != null) { - return new SetType(true, GetDafnyType(setElement.Args[1])); - } - // not possible to infer the type argument in this case if type encoding is Arguments - return new SetType(true, UnknownType); - } + if (setOperation != null && !exploredElements.Contains(setOperation.Args[1])) { return new SetType(true, GetDafnyType(UnboxNotNull(setOperation.Args[1]))); } + if (fSetEmpty.AppWithResult(element) != null) { return new SetType(true, null); } var mapOperation = fMapBuild.AppWithResult(element); if (mapOperation != null) { - return new MapType(true, GetDafnyType(Unbox(mapOperation.Args[1])), GetDafnyType(Unbox(mapOperation.Args[2]))); + return new MapType(true, GetDafnyType(UnboxNotNull(mapOperation.Args[1])), GetDafnyType(UnboxNotNull(mapOperation.Args[2]))); } var unboxedTypes = fIsBox.AppsWithArg(0, element) .Where(tuple => ((Model.Boolean)tuple.Result).Value) @@ -363,18 +721,25 @@ private Type GetDafnyType(Model.Uninterpreted element) { return finalResult; } var dtypeElement = fDtype.OptEval(element); - return dtypeElement != null ? ReconstructType(dtypeElement) : finalResult; + if (dtypeElement != null) { + ReconstructType(dtypeElement); + } + if (datatypeValues.TryGetValue(element, out var fnTuple)) { + var fullyQualifiedPath = fnTuple.Func.Name[1..].Split("."); + return new UserDefinedType(Token.NoToken, string.Join(".", fullyQualifiedPath.Take(fullyQualifiedPath.Count() - 1)), null); + } + return finalResult; } /// /// Reconstruct Dafny type from an element that represents a type in Z3 /// - private Type ReconstructType(Model.Element typeElement) { + private Type ReconstructType(Model.Element? typeElement) { if (typeElement == null) { return UnknownType; } var fullName = GetTrueTypeName(typeElement); - if (fullName != null && fullName.Length > 7 && fullName[..7].Equals("Tclass.")) { + if (fullName is { Length: > 7 } && fullName[..7].Equals("Tclass.")) { return new UserDefinedType(new Token(), fullName[7..], null); } switch (fullName) { @@ -387,8 +752,8 @@ private Type ReconstructType(Model.Element typeElement) { case "TChar": return Type.Char; } - if (fBv.AppWithResult(typeElement) != null) { - return new BitvectorType(options, ((Model.Integer)fBv.AppWithResult(typeElement).Args[0]).AsInt()); + if (fBv.AppWithResult(typeElement) is { } tupleWrapper) { + return new BitvectorType(options, ((Model.Integer)tupleWrapper.Args[0]).AsInt()); } Type fallBackType = UnknownType; // to be returned in the event all else fails @@ -432,369 +797,106 @@ private Type ReconstructType(Model.Element typeElement) { } } - /// - /// Extract the string representation of the element. - /// Return "" if !IsPrimitive(elt, state) unless elt is a datatype, - /// in which case return the corresponding constructor name. - /// - public string CanonicalName(Model.Element elt, Type type) { - if (elt == null || (type is UserDefinedType userDefinedType && userDefinedType.Name == UnknownType.Name)) { - return "?"; - } - if (elt == fNull.GetConstant()) { - return "null"; - } - if (elt is Model.Integer or Model.Boolean or Model.Real) { - return elt.ToString(); - } - if (elt is Model.BitVector vector) { - return vector.Numeral; - } - if (elt.Kind == Model.ElementKind.DataValue) { - if (((Model.DatatypeValue)elt).ConstructorName == "-") { - return "-" + CanonicalName(((Model.DatatypeValue)elt).Arguments.First(), type); - } - if (((Model.DatatypeValue)elt).ConstructorName == "/") { - return CanonicalName(((Model.DatatypeValue)elt).Arguments.First(), type) + - "/" + CanonicalName(((Model.DatatypeValue)elt).Arguments[1], type); - } - } - if (datatypeValues.TryGetValue(elt, out var fnTuple)) { - return fnTuple.Func.Name.Split(".").Last(); - } - switch (type) { - case BitvectorType bitvectorType: { - int width = bitvectorType.Width; - var funcName = "U_2_bv" + width; - if (!bitvectorTypes.ContainsKey(width)) { - bitvectorTypes[width] = new BitvectorType(options, width); - reservedNumerals[bitvectorTypes[width]] = new HashSet(); - } - if (!Model.HasFunc(funcName)) { - return GetUnreservedNumericValue(elt, bitvectorTypes[width]); - } - if (Model.GetFunc(funcName).OptEval(elt) != null) { - return (Model.GetFunc(funcName).OptEval(elt) as Model.Number)?.Numeral; - } - return GetUnreservedNumericValue(elt, bitvectorTypes[width]); - } - case CharType: { - if (fCharToInt.OptEval(elt) != null) { - if (int.TryParse(((Model.Integer)fCharToInt.OptEval(elt)).Numeral, - out var UTFCode) && UTFCode is <= char.MaxValue and >= 0) { - return PrettyPrintChar(UTFCode); - } - } - return GetUnreservedCharValue(elt); - } - case RealType when fU2Real.OptEval(elt) != null: - return CanonicalName(fU2Real.OptEval(elt), type); - case RealType: - return GetUnreservedNumericValue(elt, Type.Real); - case BoolType when fU2Bool.OptEval(elt) != null: - return CanonicalName(fU2Bool.OptEval(elt), type); - case BoolType: - return GetUnreservedBoolValue(elt); - case IntType when fU2Int.OptEval(elt) != null: - return CanonicalName(fU2Int.OptEval(elt), type); - case IntType: - return GetUnreservedNumericValue(elt, Type.Int); - default: - return ""; - } - } - - /// - /// Find a char value that is different from any other value - /// of that type in the entire model. Reserve that value for given element - /// - private string GetUnreservedCharValue(Model.Element element) { - if (reservedValuesMap.TryGetValue(element, out var reservedValue)) { - return reservedValue; - } - int i = FirstCharacterUtfValue; - while (reservedChars.Contains(i)) { - i++; - } - reservedValuesMap[element] = PrettyPrintChar(i); - reservedChars.Add(i); - return reservedValuesMap[element]; - } - - /// - /// Find a bool value that is different from any other value - /// of that type in the entire model (if possible). - /// Reserve that value for given element - /// - private string GetUnreservedBoolValue(Model.Element element) { - if (reservedValuesMap.TryGetValue(element, out var reservedValue)) { - return reservedValue; - } - if (!isTrueReserved) { - isTrueReserved = true; - reservedValuesMap[element] = true.ToString().ToLower(); - } else { - reservedValuesMap[element] = false.ToString().ToLower(); - } - return reservedValuesMap[element]; - } - - /// - /// Find a value of the given numericType that is different from - /// any other value of that type in the entire model. - /// Reserve that value for given element - /// - public string GetUnreservedNumericValue(Model.Element element, Type numericType) { - if (reservedValuesMap.TryGetValue(element, out var reservedValue)) { - return reservedValue; - } - int i = 0; - while (reservedNumerals[numericType].Contains(i)) { - i++; - } - if (numericType == Type.Real) { - reservedValuesMap[element] = i + ".0"; - } else { - reservedValuesMap[element] = i.ToString(); - } - reservedNumerals[numericType].Add(i); - return reservedValuesMap[element]; - } - /// /// Perform operations necessary to add a mapping to a map variable, - /// return newly created DafnyModelVariable objects + /// return newly created PartialValue objects /// - private IEnumerable AddMappingHelper(DafnyModelState state, MapVariable mapVariable, Model.Element keyElement, Model.Element valueElement, HashSet keySet) { - if (mapVariable == null) { + private IEnumerable AddMappingHelper(PartialState state, PartialValue? mapVariable, Model.Element? keyElement, Model.Element? valueElement, HashSet keySet, bool keyNotPresent = false) { + if (mapVariable == null || keyElement == null || keySet.Contains(keyElement)) { yield break; } - var pairId = mapVariable.Children.Count.ToString(); - var key = DafnyModelVariableFactory.Get(state, keyElement, pairId, mapVariable); - if (valueElement != null) { - var value = DafnyModelVariableFactory.Get(state, valueElement, pairId, mapVariable); - mapVariable.AddMapping(key, value); + var key = PartialValue.Get(keyElement, state); + var opcode = keyNotPresent ? BinaryExpr.Opcode.NotIn : BinaryExpr.Opcode.In; + var _ = new ContainmentConstraint(key, mapVariable, opcode == BinaryExpr.Opcode.In); + // Note that it is possible for valueElement to not be null while the element is not present in the set! + if (valueElement != null && !keyNotPresent) { + var value = PartialValue.Get(valueElement, state); + var __ = new MapSelectExprConstraint(value, mapVariable, key); yield return value; - } else { - mapVariable.AddMapping(key, null); } keySet.Add(keyElement); yield return key; } /// - /// Return a set of variables associated with an element. These could be - /// values of fields for objects, values at certain positions for - /// sequences, etc. + /// Return all functions application relevant to an element. These can be: + /// 1) destructor values of a datatype + /// 2) constant fields of an object + /// 3) function applications /// - public IEnumerable GetExpansion(DafnyModelState state, DafnyModelVariable var) { - HashSet result = new(); - if (var.Element.Kind != Model.ElementKind.Uninterpreted) { - return result; // primitive types can't have fields + private List GetDestructorFunctions(Model.Element element) { + var possibleTypeIdentifiers = GetIsResults(element); + if (fDtype.OptEval(element) != null) { + possibleTypeIdentifiers.Add(fDtype.OptEval(element)!); } - - if (datatypeValues.TryGetValue(var.Element, out var fnTuple)) { - // Elt is a datatype value - var destructors = GetDestructorFunctions(var.Element).OrderBy(f => f.Name).ToList(); - if (destructors.Count > fnTuple.Args.Length) { - // Try to filter out predicate functions - // (that follow a format very similar to that of destructor names) - destructors = destructors.Where(destructor => - fnTuple.Args.Any(arg => destructor.OptEval(var.Element) == arg)) - .ToList(); - } - if (destructors.Count == fnTuple.Args.Length) { - // we know all destructor names - foreach (var func in destructors) { - result.Add(DafnyModelVariableFactory.Get(state, Unbox(func.OptEval(var.Element)), - UnderscoreRemovalRegex.Replace(func.Name.Split(".").Last(), "_"), var)); - } - } else { - // we don't know destructor names, so we use indices instead - for (int i = 0; i < fnTuple.Args.Length; i++) { - result.Add(DafnyModelVariableFactory.Get(state, Unbox(fnTuple.Args[i]), - "[" + i + "]", var)); - } + var possiblyNullableTypes = possibleTypeIdentifiers + .Select(ReconstructType).OfType() + .Where(type => type.Name != UnknownType.Name); + var types = possiblyNullableTypes.Select(DafnyModelTypeUtils.GetNonNullable).OfType().ToList(); + List result = new(); + var builtInDatatypeDestructor = new Regex("^.*[^_](__)*_q$"); + var canCallFunctions = new HashSet(); + foreach (var app in element.References) { + if (app.Func.Arity != 1 || !Equals(app.Args[0], element) || + !types.Any(type => app.Func.Name.StartsWith(type.Name + ".")) || + builtInDatatypeDestructor.IsMatch(app.Func.Name.Split(".").Last()) || + app.Func.Name.Split(".").Last().StartsWith("_")) { + continue; } - return result; - } - switch (var.Type) { - case SeqType: { - var seqLen = fSeqLength.OptEval(var.Element); - if (seqLen != null) { - var length = DafnyModelVariableFactory.Get(state, seqLen, "Length", var); - result.Add(length); - (var as SeqVariable)?.SetLength(length); - } - - // Sequences can be constructed with the build operator: - List elements = new(); - - var substring = var.Element; - while (fSeqBuild.AppWithResult(substring) != null) { - elements.Insert(0, Unbox(fSeqBuild.AppWithResult(substring).Args[1])); - substring = fSeqBuild.AppWithResult(substring).Args[0]; - } - for (int i = 0; i < elements.Count; i++) { - var e = DafnyModelVariableFactory.Get(state, Unbox(elements[i]), "[" + i + "]", var); - result.Add(e); - (var as SeqVariable)?.AddAtIndex(e, i.ToString()); - } - if (elements.Count > 0) { - return result; - } - - // Otherwise, sequences can be reconstructed index by index, ensuring indices are in ascending order. - // NB: per https://github.com/dafny-lang/dafny/issues/3048 , not all indices may be parsed as a BigInteger, - // so ensure we do not try to sort those numerically. - var intIndices = new List<(Model.Element, BigInteger)>(); - var otherIndices = new List<(Model.Element, String)>(); - foreach (var tpl in fSeqIndex.AppsWithArg(0, var.Element)) { - var asString = tpl.Args[1].ToString(); - if (BigInteger.TryParse(asString, out var bi)) { - intIndices.Add((Unbox(tpl.Result), bi)); - } else { - otherIndices.Add((Unbox(tpl.Result), asString)); - } - } - - var sortedIndices = intIndices - .OrderBy(p => p.Item2) - .Select(p => (p.Item1, p.Item2.ToString())) - .Concat(otherIndices); - - foreach (var (res, idx) in sortedIndices) { - var e = DafnyModelVariableFactory.Get(state, res, "[" + idx + "]", var); - result.Add(e); - (var as SeqVariable)?.AddAtIndex(e, idx); - } - - return result; - } - case SetType: { - foreach (var tpl in fSetSelect.AppsWithArg(0, var.Element)) { - var setElement = tpl.Args[1]; - var containment = tpl.Result; - if (containment.Kind != Model.ElementKind.Boolean) { - continue; - } - - result.Add(DafnyModelVariableFactory.Get(state, Unbox(setElement), - ((Model.Boolean)containment).ToString(), var)); - } - return result; - } - case MapType: { - var mapKeysAdded = new HashSet(); // prevents mapping a key to multiple values - var mapsElementsVisited = new HashSet(); // prevents infinite recursion - var current = var.Element; - var mapBuilds = fMapBuild.AppsWithResult(var.Element).ToList(); - while (mapBuilds.Count != 0) { - foreach (var mapBuild in mapBuilds.Where(m => m.Args[0] == current && !mapKeysAdded.Contains(m.Args[1]))) { - result.UnionWith(AddMappingHelper( - state, - var as MapVariable, - Unbox(mapBuild.Args[1]), - Unbox(mapBuild.Args[2]), - mapKeysAdded)); - } - mapsElementsVisited.Add(current); - var nextMapBuild = mapBuilds.FirstOrDefault(m => !mapsElementsVisited.Contains(m.Args[0])); - if (nextMapBuild == null) { - break; - } - current = nextMapBuild.Args[0]; - mapBuilds = fMapBuild.AppsWithResult(nextMapBuild.Args[0]).Where(m => !mapsElementsVisited.Contains(m.Args[0])).ToList(); - if (mapKeysAdded.Contains(nextMapBuild.Args[1])) { - continue; - } - result.UnionWith(AddMappingHelper( - state, - var as MapVariable, - Unbox(nextMapBuild.Args[1]), - Unbox(nextMapBuild.Args[2]), - mapKeysAdded)); - } - var mapDomain = fMapDomain.OptEval(current); - var mapElements = fMapElements.OptEval(current); - if (mapDomain == null || mapElements == null) { - return result; - } - foreach (var app in fSetSelect.AppsWithArg(0, mapDomain)) { - if (!((Model.Boolean)app.Result).Value) { - continue; - } - result.UnionWith(AddMappingHelper( - state, - var as MapVariable, - Unbox(app.Args[1]), - Unbox(fSetSelect.OptEval(mapElements, app.Args[1])), - mapKeysAdded)); - } - return result; - } - } - - // Elt is an array or an object: - var heap = state.State.TryGet("$Heap"); - if (heap == null) { - return result; - } - var constantFields = GetDestructorFunctions(var.Element).OrderBy(f => f.Name).ToList(); - foreach (var field in constantFields) { - result.Add(DafnyModelVariableFactory.Get(state, Unbox(field.OptEval(var.Element)), - UnderscoreRemovalRegex.Replace(field.Name.Split(".").Last(), "_"), var)); - } - var fields = fSetSelect.AppsWithArgs(0, heap, 1, var.Element); - if (fields == null || !fields.Any()) { - return result; - } - foreach (var tpl in fSetSelect.AppsWithArg(0, fields.ToList()[0].Result)) { - foreach (var fieldName in GetFieldNames(tpl.Args[1])) { - if (fieldName != "alloc") { - result.Add(DafnyModelVariableFactory.Get(state, Unbox(tpl.Result), fieldName, var)); - } + if (app.Func.Name.EndsWith("#canCall")) { + canCallFunctions.Add(app.Func.Name); + } else { + result.Add(app.Func); } } - return result; + return result.Where(func => canCallFunctions.All(canCall => !canCall.StartsWith(func.Name))).ToList(); } /// - /// Return all functions mapping an object to a destructor value. + /// Return all function applications relevant to an element. /// - private List GetDestructorFunctions(Model.Element element) { + private List GetFunctionConstants(Model.Element element, Model.Element? heap) { var possibleTypeIdentifiers = GetIsResults(element); if (fDtype.OptEval(element) != null) { - possibleTypeIdentifiers.Add(fDtype.OptEval(element)); + possibleTypeIdentifiers.Add(fDtype.OptEval(element)!); } var possiblyNullableTypes = possibleTypeIdentifiers - .Select(isResult => ReconstructType(isResult) as UserDefinedType) - .Where(type => type != null && type.Name != UnknownType.Name); - var types = possiblyNullableTypes.Select(type => DafnyModelTypeUtils.GetNonNullable(type) as UserDefinedType); - List result = new(); - var builtInDatatypeDestructor = new Regex("^.*[^_](__)*_q$"); + .Select(ReconstructType).OfType() + .Where(type => type.Name != UnknownType.Name); + var types = possiblyNullableTypes.Select(DafnyModelTypeUtils.GetNonNullable).OfType().ToList(); + List applications = new(); + List wellFormed = new(); foreach (var app in element.References) { - if (app.Func.Arity != 1 || app.Args[0] != element || + if (app.Args.Length == 0 || (!Equals(app.Args[0], element) && (heap == null || !Equals(app.Args[0], heap) || app.Args.Length == 1 || !Equals(app.Args[1], element))) || !types.Any(type => app.Func.Name.StartsWith(type.Name + ".")) || - builtInDatatypeDestructor.IsMatch(app.Func.Name.Split(".").Last())) { + app.Func.Name.Split(".").Last().StartsWith("_")) { continue; } - result.Add(app.Func); + + if (app.Func.Name.EndsWith("#canCall")) { + if (app.Result is Model.Boolean { Value: true }) { + wellFormed.Add(app); + } + } else { + applications.Add(app); + } } - return result; - } - private const string PleaseEnableModelCompressFalse = - "Please enable /proverOpt:O:model_compress=false (for z3 version < 4.8.7)" + - " or /proverOpt:O:model.compact=false (for z3 version >= 4.8.7)," + - " otherwise you'll get unexpected values."; + return applications.Where(application => + wellFormed.Any(wellFormedTuple => wellFormedTuple.Func.Name == application.Func.Name + "#canCall" && + ((wellFormedTuple.Args.Length == application.Args.Length && + wellFormedTuple.Args.SequenceEqual(application.Args)) || + (wellFormedTuple.Args.Length == application.Args.Length - 1 && + wellFormedTuple.Args.SequenceEqual(application.Args[1..])))) + ).ToList(); + } /// /// Return the name of the field represented by the given element. /// Special care is required if the element represents an array index /// - private List GetFieldNames(Model.Element elt) { + private List GetFieldNames(Model.Element? elt) { if (elt == null) { return new List(); } @@ -807,20 +909,18 @@ private List GetFieldNames(Model.Element elt) { .ToList(); } // Reaching this code means elt is an index into an array - var indices = new Model.Element[(int)dims]; + var indices = new Model.Element?[(int)dims]; for (int i = (int)dims; 0 <= --i;) { - ModelFuncWrapper.ModelFuncTupleWrapper dimTuple; + ModelFuncWrapper.ModelFuncTupleWrapper? dimTuple; if (i == 0) { dimTuple = fIndexField.AppWithResult(elt); if (dimTuple == null) { - options.OutputWriter.WriteLine(PleaseEnableModelCompressFalse); continue; } indices[i] = dimTuple.Args[0]; } else { dimTuple = fMultiIndexField.AppWithResult(elt); if (dimTuple == null) { - options.OutputWriter.WriteLine(PleaseEnableModelCompressFalse); continue; } indices[i] = dimTuple.Args[1]; @@ -834,16 +934,18 @@ private List GetFieldNames(Model.Element elt) { } /// Unboxes an element, if possible - private Model.Element Unbox(Model.Element elt) { - if (elt == null) { - return null; - } + private Model.Element? Unbox(Model.Element? elt) { + return elt == null ? null : UnboxNotNull(elt); + } + + /// Unboxes an element, if possible + private Model.Element UnboxNotNull(Model.Element elt) { var unboxed = fBox.AppWithResult(elt); if (unboxed != null) { - return Unbox(unboxed.Args[0]); + return UnboxNotNull(unboxed.Args[0]); } unboxed = fUnbox.AppWithArg(1, elt); - return unboxed != null ? Unbox(unboxed.Result) : elt; + return unboxed != null ? UnboxNotNull(unboxed.Result) : elt; } } } diff --git a/Source/DafnyCore/CounterExampleGeneration/DafnyModelState.cs b/Source/DafnyCore/CounterExampleGeneration/DafnyModelState.cs deleted file mode 100644 index 6d2a6cb3fd..0000000000 --- a/Source/DafnyCore/CounterExampleGeneration/DafnyModelState.cs +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright by the contributors to the Dafny Project -// SPDX-License-Identifier: MIT - - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using Microsoft.Boogie; - -namespace Microsoft.Dafny { - - /// - /// Represents a program state in a DafnyModel - /// - public class DafnyModelState { - - internal readonly DafnyModel Model; - internal readonly Model.CapturedState State; - internal int VarIndex; // used to assign unique indices to variables - private readonly List vars; - private readonly List boundVars; - // varMap prevents the creation of multiple variables for the same element. - private readonly Dictionary varMap; - // varNameCount keeps track of the number of variables with the same name. - // Name collision can happen in the presence of an old expression, - // for instance, in which case the it is important to distinguish between - // two variables that have the same name using an index provided by Boogie - private readonly Dictionary varNameCount; - - private const string InitialStateName = ""; - private static readonly Regex StatePositionRegex = new( - @".*\((?\d+),(?\d+)\)", - RegexOptions.IgnoreCase | RegexOptions.Singleline - ); - - internal DafnyModelState(DafnyModel model, Model.CapturedState state) { - Model = model; - State = state; - VarIndex = 0; - vars = new(); - varMap = new(); - varNameCount = new(); - boundVars = new(BoundVars()); - SetupVars(); - } - - /// - /// Start with the union of vars and boundVars and expand the set by adding - /// variables that represent fields of any object in the original set or - /// elements of any sequence in the original set, etc. This is done - /// recursively in breadth-first order and only up to a certain maximum - /// depth. - /// - /// The maximum depth up to which to expand the - /// variable set. - /// Set of variables - public List ExpandedVariableSet(int maxDepth) { - List expandedSet = new(); - // The following is the queue for elements to be added to the set. The 2nd - // element of a tuple is the depth of the variable w.r.t. the original set - List> varsToAdd = new(); - vars.ForEach(variable => varsToAdd.Add(new(variable, 0))); - boundVars.ForEach(variable => varsToAdd.Add(new(variable, 0))); - while (varsToAdd.Count != 0) { - var (next, depth) = varsToAdd[0]; - varsToAdd.RemoveAt(0); - if (expandedSet.Contains(next)) { - continue; - } - if (depth == maxDepth) { - break; - } - expandedSet.Add(next); - // fields of primitive types are skipped: - foreach (var v in next.GetExpansion(). - Where(variable => !expandedSet.Contains(variable) && !variable.IsPrimitive)) { - varsToAdd.Add(new(v, depth + 1)); - } - } - return expandedSet; - } - - internal bool ExistsVar(Model.Element element) { - return varMap.ContainsKey(element); - } - - internal void AddVar(Model.Element element, DafnyModelVariable var) { - if (!ExistsVar(element)) { - varMap[element] = var; - } - } - - internal DafnyModelVariable GetVar(Model.Element element) { - return varMap[element]; - } - - internal void AddVarName(string name) { - varNameCount[name] = varNameCount.GetValueOrDefault(name, 0) + 1; - } - - internal bool VarNameIsShared(string name) { - return varNameCount.GetValueOrDefault(name, 0) > 1; - } - - public string FullStateName => State.Name; - - private string ShortenedStateName => ShortenName(State.Name, 20); - - public bool IsInitialState => FullStateName.Equals(InitialStateName); - - public bool StateContainsPosition() { - return StatePositionRegex.Match(ShortenedStateName).Success; - } - - public int GetLineId() { - var match = StatePositionRegex.Match(ShortenedStateName); - if (!match.Success) { - throw new ArgumentException( - $"state does not contain position: {ShortenedStateName}"); - } - return int.Parse(match.Groups["line"].Value); - } - - public int GetCharId() { - var match = StatePositionRegex.Match(ShortenedStateName); - if (!match.Success) { - throw new ArgumentException( - $"state does not contain position: {ShortenedStateName}"); - } - return int.Parse(match.Groups["character"].Value); - } - - /// - /// Initialize the vars list, which stores all variables relevant to - /// the counterexample except for the bound variables - /// - private void SetupVars() { - var names = Enumerable.Empty(); - if (Model.States.Count > 0) { - var prev = Model.States.Last(); - names = prev.vars.ConvertAll(variable => variable.Name); - } - names = names.Concat(State.Variables).Distinct(); - foreach (var v in names) { - if (!DafnyModel.IsUserVariableName(v)) { - continue; - } - var val = State.TryGet(v); - if (val == null) { - continue; // This variable has no value in the model, so ignore it. - } - - var vn = DafnyModelVariableFactory.Get(this, val, v, duplicate: true); - vars.Add(vn); - } - } - - /// - /// Return list of bound variables - /// - private IEnumerable BoundVars() { - foreach (var f in Model.Model.Functions) { - if (f.Arity != 0) { - continue; - } - int n = f.Name.IndexOf('!'); - if (n == -1) { - continue; - } - var name = f.Name.Substring(0, n); - if (!name.Contains('#') || name.Contains("$")) { - continue; - } - - yield return DafnyModelVariableFactory.Get(this, f.GetConstant(), name, - null, true); - } - } - - private static string ShortenName(string name, int fnLimit) { - var loc = TryParseSourceLocation(name); - if (loc != null) { - var fn = loc.Filename; - int idx = fn.LastIndexOfAny(new[] { '\\', '/' }); - if (idx > 0) { - fn = fn.Substring(idx + 1); - } - if (fn.Length > fnLimit) { - fn = fn.Substring(0, fnLimit) + ".."; - } - var addInfo = loc.AddInfo; - if (addInfo != "") { - addInfo = ":" + addInfo; - } - return $"{fn}({loc.Line},{loc.Column}){addInfo}"; - } - return name; - } - - /// - /// Parse a string (typically the name of the captured state in Boogie) to - /// extract a SourceLocation from it. An example of a string to be parsed: - /// @"c:\users\foo\bar.c(12,10) : random string" - /// The ": random string" part is optional. - /// - private static SourceLocation TryParseSourceLocation(string name) { - int par = name.LastIndexOf('('); - if (par <= 0) { - return null; - } - var res = new SourceLocation() { Filename = name.Substring(0, par) }; - var words = name.Substring(par + 1) - .Split(',', ')', ':') - .Where(x => x != "") - .ToArray(); - if (words.Length < 2) { - return null; - } - if (!int.TryParse(words[0], out res.Line) || - !int.TryParse(words[1], out res.Column)) { - return null; - } - int colon = name.IndexOf(':', par); - res.AddInfo = colon > 0 ? name.Substring(colon + 1).Trim() : ""; - return res; - } - - private class SourceLocation { - public string Filename; - public string AddInfo; - public int Line; - public int Column; - } - } -} \ No newline at end of file diff --git a/Source/DafnyCore/CounterExampleGeneration/DafnyModelTypeUtils.cs b/Source/DafnyCore/CounterExampleGeneration/DafnyModelTypeUtils.cs index 7e17ad24ef..b6ff5486a6 100644 --- a/Source/DafnyCore/CounterExampleGeneration/DafnyModelTypeUtils.cs +++ b/Source/DafnyCore/CounterExampleGeneration/DafnyModelTypeUtils.cs @@ -1,5 +1,6 @@ // Copyright by the contributors to the Dafny Project // SPDX-License-Identifier: MIT +#nullable enable using System; using System.Linq; @@ -28,7 +29,9 @@ public static Type GetNonNullable(Type type) { } public static Type ReplaceTypeVariables(Type type, Type with) { - return ReplaceType(type, t => t.Name.Contains('$'), _ => with); + var result = ReplaceType(type, t => t.Name.Contains('$'), _ => with); + FillInTypeArgs(result, with); + return result; } /// @@ -61,6 +64,8 @@ public static Type GetInDafnyFormat(Type type) { // The code below converts every "__" to "_": newName = UnderscoreRemovalRegex.Replace(newName, "_"); newName = ConvertTupleName(newName); + newName = string.Join(".", newName.Split(".") + .Where(part => part != "_module" && part != "_default" && part != "_System")); return new UserDefinedType(new Token(), newName, type.TypeArgs.ConvertAll(t => TransformType(t, GetInDafnyFormat))); } @@ -82,15 +87,15 @@ private static Type TransformType(Type type, Func transfo switch (type) { case BasicType: return type; - case MapType mapType: + case MapType mapType when mapType.HasTypeArg(): return new MapType(mapType.Finite, TransformType(mapType.Domain, transform), TransformType(mapType.Range, transform)); - case SeqType seqType: + case SeqType seqType when seqType.HasTypeArg(): return new SeqType(TransformType(seqType.Arg, transform)); - case SetType setType: + case SetType setType when setType.HasTypeArg(): return new SetType(setType.Finite, TransformType(setType.Arg, transform)); - case MultiSetType multiSetType: + case MultiSetType multiSetType when multiSetType.HasTypeArg(): return new MultiSetType(TransformType(multiSetType, transform)); case UserDefinedType userDefinedType: return transform(userDefinedType); @@ -101,5 +106,33 @@ private static Type TransformType(Type type, Func transfo } return type; } + + /// + /// Whenever a collection type does not have an argument, fill it in with the provided arg type + /// + private static void FillInTypeArgs(Type type, Type arg) { + switch (type) { + case BasicType: + return; + case MapType mapType when !mapType.HasTypeArg(): + mapType.SetTypeArgs(arg, arg); + return; + case SeqType seqType when !seqType.HasTypeArg(): + seqType.SetTypeArg(arg); + return; + case SetType setType when !setType.HasTypeArg(): + setType.SetTypeArg(arg); + return; + case MultiSetType multiSetType when !multiSetType.HasTypeArg(): + multiSetType.SetTypeArg(arg); + return; + case UserDefinedType userDefinedType: + userDefinedType.TypeArgs.ForEach(typ => FillInTypeArgs(typ, arg)); + return; + case InferredTypeProxy inferredTypeProxy: + FillInTypeArgs(inferredTypeProxy.T, arg); + return; + } + } } } \ No newline at end of file diff --git a/Source/DafnyCore/CounterExampleGeneration/DafnyModelVariable.cs b/Source/DafnyCore/CounterExampleGeneration/DafnyModelVariable.cs deleted file mode 100644 index 855b59e5ab..0000000000 --- a/Source/DafnyCore/CounterExampleGeneration/DafnyModelVariable.cs +++ /dev/null @@ -1,299 +0,0 @@ -// Copyright by the contributors to the Dafny Project -// SPDX-License-Identifier: MIT - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using Microsoft.Boogie; - -namespace Microsoft.Dafny { - - /// - /// A static class for generating instance of DafnyModelvariable and its - /// subclasses. The factory chooses which subclass of DafnyModelVariable to - /// employ depending on the DafnyModelType` of the `Element` for which the - /// variable is generated. - /// - public static class DafnyModelVariableFactory { - - /// - /// Create a new variable to be associated with the given model element in - /// a given counterexample state or return such a variable if one already - /// exists. - /// - /// - /// - /// the name to be assigned to the variable OR, - /// if parent != null, the name of the field associated with it. In the later - /// case, Name is set to some unique id. - /// if not null, this variable represents the field of - /// some parent object - /// forces the creation of a new variable even if - /// one already exists - /// - public static DafnyModelVariable Get(DafnyModelState state, - Model.Element element, string name, DafnyModelVariable parent = null, - bool duplicate = false) { - if (state.ExistsVar(element)) { - parent?.AddChild(name, state.GetVar(element)); - if (!duplicate) { - return state.GetVar(element); - } - return new DuplicateVariable(state, state.GetVar(element), name, parent); - } - - return state.Model.GetDafnyType(element) switch { - SeqType _ => new SeqVariable(state, element, name, parent), - MapType _ => new MapVariable(state, element, name, parent), - _ => new DafnyModelVariable(state, element, name, parent) - }; - } - } - - /// - /// Represents a variable at a particular state. Note that a variable in Dafny - /// source can be represented by multiple DafnyModelVariables, one for each - /// DafnyModelState in DafnyModel. - /// - public class DafnyModelVariable { - - public readonly string Name; // name given to the variable at creation - public readonly Microsoft.Dafny.Type Type; // Dafny type of the variable - public readonly Model.Element Element; - // Maps a field name, sequence index, or some other identifier to - // a list of DafnyModelVariables that represent the corresponding value - private readonly Dictionary> children; - private readonly DafnyModelState state; // the associated captured state - public virtual Dictionary> Children => children; - - internal DafnyModelVariable(DafnyModelState state, Model.Element element, - string name, DafnyModelVariable parent) { - this.state = state; - Element = element; - Type = state.Model.GetDafnyType(element); - children = new Dictionary>(); - state.AddVar(element, this); - if (parent == null) { - Name = name; - } else { - Name = "@" + state.VarIndex++; - parent.AddChild(name, this); - } - state.AddVarName(ShortName); - } - - public virtual IEnumerable GetExpansion() { - return state.Model.GetExpansion(state, this); - } - - public string CanonicalName() { - return state.Model.CanonicalName(Element, Type); - } - - public virtual string Value { - get { - var result = state.Model.CanonicalName(Element, Type); - if (children.Count == 0) { - if (result != "") { - return result; - } - return Type is SetType ? "{}" : "()"; - } - - List<(string ChildName, string ChildValue)> childList = new(); - foreach (var childName in children.Keys) { - foreach (var child in children[childName]) { - if (child.IsPrimitive) { - childList.Add(new ValueTuple(childName, child.Value)); - } else { - childList.Add(new ValueTuple(childName, child.ShortName)); - } - } - } - string childValues; - if (Type is SetType) { - childValues = string.Join(", ", - childList.ConvertAll(tpl => tpl.Item2 + " := " + tpl.Item1)); - return result + "{" + childValues + "}"; - } - childValues = string.Join(", ", - childList.ConvertAll(tpl => tpl.Item1 + " := " + tpl.Item2)); - return result + "(" + childValues + ")"; - } - } - public bool IsPrimitive => Type is BasicType || state.Model.ElementIsNull(Element); - - public string ShortName { - get { - var shortName = Regex.Replace(Name, @"#.*$", ""); - return state.VarNameIsShared(shortName) ? Name : shortName; - } - } - - internal void AddChild(string name, DafnyModelVariable child) { - name = Regex.Replace(name, "^_h", "#"); - if (!children.ContainsKey(name)) { - children[name] = new(); - } - children[name].Add(child); - } - - public override int GetHashCode() { - return Element.Id.GetHashCode(); - } - - public override bool Equals(object obj) { - if (obj is not DafnyModelVariable other) { - return false; - } - - return other.Element == Element && - other.state == state && - other.Name == Name; - } - } - - /// - /// a variable that has a different name but represents the same Element in - /// the same DafnyModelState as some other variable. - /// - public class DuplicateVariable : DafnyModelVariable { - - public readonly DafnyModelVariable Original; - - internal DuplicateVariable(DafnyModelState state, - DafnyModelVariable original, string newName, DafnyModelVariable parent) - : base(state, original.Element, newName, parent) { - Original = original; - } - - public override string Value => Original.ShortName; - - public override Dictionary> Children => Original.Children; - - public override IEnumerable GetExpansion() { - return Original.GetExpansion(); - } - } - - /// - /// A variable that represents a sequence. - /// - public class SeqVariable : DafnyModelVariable { - - private DafnyModelVariable seqLength; - // Dafny integers are unbounded, hence using strings for seq indices: - private readonly Dictionary seqElements; - - internal SeqVariable(DafnyModelState state, Model.Element element, - string name, DafnyModelVariable parent) - : base(state, element, name, parent) { - seqLength = null; - seqElements = new Dictionary(); - } - - public override string Value { - get { - var length = GetLength(); - if (length == -1 || seqElements.Count != length) { - return base.Value; - } - List result = new(); - for (int i = 0; i < length; i++) { - var id = i.ToString(); - if (!seqElements.ContainsKey(id)) { - return base.Value; - } - result.Add(seqElements[id].IsPrimitive ? - seqElements[id].Value : - seqElements[id].ShortName); - } - return "[" + string.Join(", ", result) + "]"; - } - } - - public int GetLength() { - if (int.TryParse((seqLength?.Element as Model.Integer)?.Numeral, - out var value)) { - return value; - } - // Since the length is not explicitly specified, the index of the last known element should suffice - int lastId = 0; - foreach (var id in seqElements.Keys) { - if (int.TryParse(id, out var idAsInt)) { - if (lastId < idAsInt + 1) { - lastId = idAsInt + 1; - } - } else { - return -1; // Failed to parse the index, so just say that the length is unknown - } - } - return lastId; - } - - public DafnyModelVariable this[int index] => seqElements.GetValueOrDefault(index.ToString(), null); - - public void SetLength(DafnyModelVariable seqLength) { - this.seqLength = seqLength; - } - - public void AddAtIndex(DafnyModelVariable e, string index) { - if (index == null) { - return; - } - seqElements[index] = e; - } - } - - /// - /// A variable that represents a map. - /// - public class MapVariable : DafnyModelVariable { - - public readonly Dictionary - Mappings = new(); - - internal MapVariable(DafnyModelState state, Model.Element element, - string name, DafnyModelVariable parent) - : base(state, element, name, parent) { } - - public override string Value { - get { - if (Mappings.Count == 0) { - return "map[]"; - } - // maps a key-value pair to how many times it appears in the map - // a key value pair can appear many times in a map due to "?:int" etc. - Dictionary mapStrings = new(); - foreach (var key in Mappings.Keys) { - var keyString = key.IsPrimitive ? key.Value : key.Name; - var valueString = "?"; - if (Mappings[key] != null) { - valueString = Mappings[key].IsPrimitive - ? Mappings[key].Value - : Mappings[key].Name; - } - var mapString = keyString + " := " + valueString; - mapStrings[mapString] = - mapStrings.GetValueOrDefault(mapString, 0) + 1; - } - - return "map[" + string.Join(", ", mapStrings.Keys.ToList() - .ConvertAll(keyValuePair => - mapStrings[keyValuePair] == 1 - ? keyValuePair - : keyValuePair + " [+" + (mapStrings[keyValuePair] - 1) + - "]")) + - "]"; - } - } - - public void AddMapping(DafnyModelVariable from, DafnyModelVariable to) { - if (Mappings.ContainsKey(from)) { - return; - } - Mappings[from] = to; - } - } -} \ No newline at end of file diff --git a/Source/DafnyCore/CounterExampleGeneration/ModelFuncWrapper.cs b/Source/DafnyCore/CounterExampleGeneration/ModelFuncWrapper.cs index 96334b6bc8..5970115bc6 100644 --- a/Source/DafnyCore/CounterExampleGeneration/ModelFuncWrapper.cs +++ b/Source/DafnyCore/CounterExampleGeneration/ModelFuncWrapper.cs @@ -1,5 +1,6 @@ // Copyright by the contributors to the Dafny Project // SPDX-License-Identifier: MIT +#nullable enable using System; using System.Collections.Generic; @@ -28,25 +29,30 @@ public ModelFuncWrapper(DafnyModel model, string name, int arity, int argsToSkip func = model.Model.MkFunc(name, arity + this.argsToSkip); } - private ModelFuncTupleWrapper ConvertFuncTuple(Model.FuncTuple tuple) { - return tuple == null ? null : new ModelFuncTupleWrapper(func, tuple.Result, tuple.Args[argsToSkip..]); + public ModelFuncWrapper(Model.Func func, int argsToSkip) { + this.func = func; + this.argsToSkip = argsToSkip; + } + + private ModelFuncTupleWrapper? ConvertFuncTuple(Model.FuncTuple? tuple) { + return tuple == null ? null : new ModelFuncTupleWrapper(tuple.Result, tuple.Args[argsToSkip..]); } - public ModelFuncTupleWrapper AppWithResult(Model.Element element) { + public ModelFuncTupleWrapper? AppWithResult(Model.Element element) { return ConvertFuncTuple(func.AppWithResult(element)); } public IEnumerable AppsWithResult(Model.Element element) { - return func.AppsWithResult(element).Select(ConvertFuncTuple); + return func.AppsWithResult(element).Select(ConvertFuncTuple).OfType(); } - public IEnumerable Apps => func.Apps.Select(ConvertFuncTuple); + public IEnumerable Apps => func.Apps.Select(ConvertFuncTuple).OfType(); - public Model.Element GetConstant() { + public Model.Element? GetConstant() { return func.GetConstant(); } - public Model.Element OptEval(Model.Element element) { + public Model.Element? OptEval(Model.Element? element) { if (element == null) { return null; } @@ -54,11 +60,11 @@ public Model.Element OptEval(Model.Element element) { return app?.Result; } - public ModelFuncTupleWrapper AppWithArg(int index, Model.Element element) { + public ModelFuncTupleWrapper? AppWithArg(int index, Model.Element element) { return ConvertFuncTuple(func.AppWithArg(argsToSkip + index, element)); } - public Model.Element OptEval(Model.Element first, Model.Element second) { + public Model.Element? OptEval(Model.Element? first, Model.Element? second) { if (first == null || second == null) { return null; } @@ -67,11 +73,11 @@ public Model.Element OptEval(Model.Element first, Model.Element second) { } public IEnumerable AppsWithArg(int i, Model.Element element) { - return func.AppsWithArg(i + argsToSkip, element).Select(ConvertFuncTuple); + return func.AppsWithArg(i + argsToSkip, element).Select(ConvertFuncTuple).OfType(); } public IEnumerable AppsWithArgs(int i0, Model.Element element0, int i1, Model.Element element1) { - return func.AppsWithArgs(i0 + argsToSkip, element0, i1 + argsToSkip, element1).Select(ConvertFuncTuple); + return func.AppsWithArgs(i0 + argsToSkip, element0, i1 + argsToSkip, element1).Select(ConvertFuncTuple).OfType(); } /// @@ -107,13 +113,11 @@ public class ModelFuncTupleWrapper { static readonly Model.Element[] EmptyArgs = Array.Empty(); - public readonly Model.Func Func; - public Model.Element Result; + public readonly Model.Element Result; public readonly Model.Element[] Args; - public ModelFuncTupleWrapper(Model.Func func, Model.Element res, Model.Element[] args) { + public ModelFuncTupleWrapper(Model.Element res, Model.Element[] args) { Args = args ?? EmptyArgs; - Func = func; Result = res; } } diff --git a/Source/DafnyCore/CounterExampleGeneration/PartialState.cs b/Source/DafnyCore/CounterExampleGeneration/PartialState.cs new file mode 100644 index 0000000000..0704d02578 --- /dev/null +++ b/Source/DafnyCore/CounterExampleGeneration/PartialState.cs @@ -0,0 +1,324 @@ +// Copyright by the contributors to the Dafny Project +// SPDX-License-Identifier: MIT +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using Microsoft.Boogie; + +namespace Microsoft.Dafny; + +public class PartialState { + + public bool IsLoopEntryState => FullStateName.Contains(CaptureStateExtensions.AfterLoopIterationsStateMarker); + // ghost variables introduced by the counterexample whose values must be true for the counterexample to hold: + public List LoopGuards = new(); + public readonly Dictionary> KnownVariableNames = new(); + private readonly List initialPartialValues; + internal readonly DafnyModel Model; + internal readonly Model.CapturedState State; + private const string InitialStateName = ""; + private static readonly Regex StatePositionRegex = new( + @".*\((?\d+),(?\d+)\)", + RegexOptions.IgnoreCase | RegexOptions.Singleline + ); + internal readonly Dictionary AllPartialValues = new(); + + private const string BoundVarPrefix = "boundVar"; + + internal PartialState(DafnyModel model, Model.CapturedState state) { + Model = model; + State = state; + initialPartialValues = new List(); + SetupBoundVars(); + SetupVars(); + } + + /// + /// Start with the union of vars and boundVars and expand the set by adding + /// all partial values that are necessary to fully constrain the counterexample. + /// + /// Set of partial values + public HashSet ExpandedVariableSet() { + HashSet expandedSet = new(); + // The following is the queue for elements to be added to the set. The 2nd + // element of a tuple is the depth of the variable w.r.t. the original set + List> varsToAdd = new(); + initialPartialValues.ForEach(variable => varsToAdd.Add(new(variable, 0))); + while (varsToAdd.Count != 0) { + var (next, depth) = varsToAdd[0]; + varsToAdd.RemoveAt(0); + if (expandedSet.Contains(next)) { + continue; + } + expandedSet.Add(next); + // fields of primitive types are skipped: + foreach (var v in next.GetRelatedValues(). + Where(variable => !expandedSet.Contains(variable))) { + varsToAdd.Add(new(v, depth + 1)); + } + } + return expandedSet; + } + + /// + /// Return a conjunction of expression that is represented by a balanced AST. This is intended to prevent + /// stackoverflow errors that occur if multiple conjuncts are joined in a linked list fashion. + /// + /// + private Expression GetCompactConjunction(List conjuncts) { + if (!conjuncts.Any()) { + return new LiteralExpr(Token.NoToken, true); + } + + if (conjuncts.Count() == 1) { + return conjuncts.First(); + } + + var middle = conjuncts.Count() / 2; + var left = GetCompactConjunction(conjuncts.Take(middle).ToList()); + var right = GetCompactConjunction(conjuncts.Skip(middle).ToList()); + return new BinaryExpr(Token.NoToken, BinaryExpr.Opcode.And, left, right); + } + + /// + /// Convert this counterexample state into an assumption that could be inserted in Dafny source code + /// + public Statement AsAssumption() { + var allVariableNames = new Dictionary(); + var variables = ExpandedVariableSet().ToArray(); + var constraintSet = new HashSet(); + + // Collect all constraints into one list: + foreach (var variable in variables) { + foreach (var constraint in variable.Constraints) { + constraintSet.Add(constraint); + } + } + + // Ignore TypeTest constraints because they make the counterexample too verbose + var constraints = constraintSet.Where(constraint => constraint is not TypeTestConstraint).ToList(); + constraints = Constraint.ResolveAndOrder(allVariableNames, constraints, true, true); + + // Create a bound variable for every partial value that we cannot otherwise refer to using variables in scope + var boundVars = new List(); + for (int i = 0; i < variables.Length; i++) { + if (!allVariableNames.ContainsKey(variables[i])) { + boundVars.Add(new BoundVar(Token.NoToken, BoundVarPrefix + boundVars.Count, variables[i].Type)); + allVariableNames[variables[i]] = new IdentifierExpr(Token.NoToken, boundVars.Last()); + } + } + + // Translate all constraints to Dafny expressions, removing any duplicates: + var constraintsAsExpressions = new List(); + var constraintsAsStrings = new HashSet(); + foreach (var constraint in constraints) { + var constraintAsExpression = constraint.AsExpression(allVariableNames); + if (constraintAsExpression == null) { + continue; + } + var constraintAsString = constraintAsExpression.ToString(); + if (constraintsAsStrings.Contains(constraintAsString)) { + continue; + } + + constraintsAsStrings.Add(constraintAsString); + constraintsAsExpressions.Add(constraintAsExpression); + } + + // Convert the constraints into one conjunction + Expression expression = GetCompactConjunction(constraintsAsExpressions); + + if (constraintsAsExpressions.Count > 0 && boundVars.Count > 0) { + expression = new ExistsExpr(Token.NoToken, RangeToken.NoToken, boundVars, null, expression, null); + } + + if ((LoopGuards.Count != 0 && !IsLoopEntryState) || LoopGuards.Count > 1) { + Expression loopGuard = new IdentifierExpr(Token.NoToken, LoopGuards[0]); + for (int i = 1; i < LoopGuards.Count; i++) { + if (i == LoopGuards.Count - 1 && IsLoopEntryState) { + continue; + } + loopGuard = new BinaryExpr(Token.NoToken, BinaryExpr.Opcode.And, loopGuard, + new IdentifierExpr(Token.NoToken, LoopGuards[i])); + } + expression = new BinaryExpr(Token.NoToken, BinaryExpr.Opcode.Imp, loopGuard, expression); + } + + if (!IsLoopEntryState) { + return new AssumeStmt(RangeToken.NoToken, expression, null); + } + return new UpdateStmt(RangeToken.NoToken, new List() { new IdentifierExpr(Token.NoToken, LoopGuards.Last()) }, + new List() { new ExprRhs(expression) }); + } + + /// + /// Initialize the vars list, which stores all variables relevant to + /// the counterexample except for the bound variables + /// + private void SetupVars() { + var names = Enumerable.Empty(); + foreach (var partialState in Model.States) { + names = names.Concat(partialState.State.Variables); + } + names = names.Concat(State.Variables).Distinct().ToList(); + var notDefinitelyAssigned = new HashSet(); + foreach (var name in names.Where(name => name.StartsWith("defass#"))) { + var val = State.TryGet(name); + if (val == null) { + continue; + } + if (val is Model.Boolean { Value: false }) { + notDefinitelyAssigned.Add(name[7..]); + } + } + foreach (var v in names) { + if (!IsUserVariableName(v) || notDefinitelyAssigned.Contains(v)) { + continue; + } + var val = State.TryGet(v); + if (val == null) { + continue; // This variable has no value in the model, so ignore it. + } + + var value = PartialValue.Get(val, this); + initialPartialValues.Add(value); + var _ = new IdentifierExprConstraint(value, v.Split("#").First()); + if (!KnownVariableNames.ContainsKey(value)) { + KnownVariableNames[value] = new List(); + } + KnownVariableNames[value].Add(v.Split("#").First()); + } + } + + /// + /// Return True iff the variable name is referring to a variable that has + /// a direct analog in Dafny source (i.e. not $Heap, $_Frame, $nw, etc.) + /// + private static bool IsUserVariableName(string name) => + !name.Contains("$") && name.Count(c => c == '#') <= 1; + + /// + /// Instantiate BoundVariables + /// + private void SetupBoundVars() { + foreach (var f in Model.Model.Functions) { + if (f.Arity != 0) { + continue; + } + int n = f.Name.IndexOf('!'); + if (n == -1) { + continue; + } + var name = f.Name[..n]; + if (!name.Contains('#') || name.Contains('$')) { + continue; + } + + var value = PartialValue.Get(f.GetConstant(), this); + initialPartialValues.Add(value); + var _ = new IdentifierExprConstraint(value, name); + if (!KnownVariableNames.ContainsKey(value)) { + KnownVariableNames[value] = new(); + } + KnownVariableNames[value].Add(name); + } + } + + public string FullStateName => State.Name; + + private string ShortenedStateName => ShortenName(State.Name, 20); + + public bool IsInitialState => FullStateName.Equals(InitialStateName); + + public bool StateContainsPosition() { + return StatePositionRegex.Match(ShortenedStateName).Success; + } + + public int GetLineId() { + var match = StatePositionRegex.Match(ShortenedStateName); + if (!match.Success) { + throw new ArgumentException( + $"state does not contain position: {ShortenedStateName}"); + } + return int.Parse(match.Groups["line"].Value); + } + + public int GetCharId() { + var match = StatePositionRegex.Match(ShortenedStateName); + if (!match.Success) { + throw new ArgumentException( + $"state does not contain position: {ShortenedStateName}"); + } + return int.Parse(match.Groups["character"].Value); + } + + private static string ShortenName(string name, int fnLimit) { + var loc = TryParseSourceLocation(name); + if (loc != null) { + var fn = loc.Filename; + int idx = fn.LastIndexOfAny(new[] { '\\', '/' }); + if (idx > 0) { + fn = fn[(idx + 1)..]; + } + if (fn.Length > fnLimit) { + fn = fn[..fnLimit] + ".."; + } + var addInfo = loc.AddInfo; + if (addInfo != "") { + addInfo = ":" + addInfo; + } + return $"{fn}({loc.Line},{loc.Column}){addInfo}"; + } + return name; + } + + /// + /// Parse a string (typically the name of the captured state in Boogie) to + /// extract a SourceLocation from it. An example of a string to be parsed: + /// @"c:\users\foo\bar.c(12,10) : random string" + /// The ": random string" part is optional. + /// + private static SourceLocation? TryParseSourceLocation(string name) { + int par = name.LastIndexOf('('); + if (par <= 0) { + return null; + } + // var res = new SourceLocation { Filename = name[..par] }; + var words = name[(par + 1)..] + .Split(',', ')', ':') + .Where(x => x != "") + .ToArray(); + if (words.Length < 2) { + return null; + } + if (!int.TryParse(words[0], out var line) || + !int.TryParse(words[1], out var column)) { + return null; + } + int colon = name.IndexOf(':', par); + var res = new SourceLocation( + name[..par], + colon > 0 ? name[(colon + 1)..].Trim() : "", + line, + column); + return res; + } + + private class SourceLocation { + public readonly string Filename; + public readonly string AddInfo; + public readonly int Line; + public readonly int Column; + + public SourceLocation(string filename, string addInfo, int line, int column) { + Filename = filename; + AddInfo = addInfo; + Line = line; + Column = column; + } + } + +} \ No newline at end of file diff --git a/Source/DafnyCore/CounterExampleGeneration/PartialValue.cs b/Source/DafnyCore/CounterExampleGeneration/PartialValue.cs new file mode 100644 index 0000000000..c8125fbd65 --- /dev/null +++ b/Source/DafnyCore/CounterExampleGeneration/PartialValue.cs @@ -0,0 +1,180 @@ +// Copyright by the contributors to the Dafny Project +// SPDX-License-Identifier: MIT +#nullable enable + +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Microsoft.Boogie; + +namespace Microsoft.Dafny; + +/// +/// Each PartialValue corresponds to an Element in the counterexample model returned by Boogie and represents a +/// Dafny value about which we might have limited information (e.g. a sequence of which we only know one element) +/// +public class PartialValue { + + public readonly Model.Element Element; // the element in the counterexample model associated with the value + public readonly List Constraints; // constraints describing this value + public readonly PartialState state; // corresponding state in the counterexample model + private readonly Type type; // Dafny type associated with the value + private bool haveExpanded; + + /// + /// This factory method ensures we don't create duplicate partial value objects for the same element and state in the + /// counterexample model + /// + public static PartialValue Get(Model.Element element, PartialState state) { + if (state.AllPartialValues.TryGetValue(element, out var value)) { + return value; + } + + return new PartialValue(element, state); + } + + private PartialValue(Model.Element element, PartialState state) { + Element = element; + this.state = state; + Constraints = new(); + haveExpanded = false; + state.AllPartialValues[element] = this; + type = state.Model.GetFormattedDafnyType(element); + var _ = new TypeTestConstraint(this, type); + state.Model.AddTypeConstraints(this); + } + + /// + /// Return all partial values that appear in any of the constraints describing this element + /// + public IEnumerable GetRelatedValues() { + var relatedValues = new HashSet() { this }; + if (!haveExpanded) { + state.Model.GetExpansion(state, this); + haveExpanded = true; + } + + foreach (var constraint in Constraints) { + foreach (var value in constraint.ReferencedValues) { + if (!relatedValues.Contains(value)) { + relatedValues.Add(value); + yield return value; + } + } + + if (constraint is DefinitionConstraint definitionConstraint && + !relatedValues.Contains(definitionConstraint.DefinedValue)) { + relatedValues.Add(definitionConstraint.DefinedValue); + yield return definitionConstraint.DefinedValue; + } + } + } + + public bool Nullable => Constraints.OfType() + .Any(test => test.Type is UserDefinedType userDefinedType && userDefinedType.Name.EndsWith("?")); + + public Type Type => type; + + public string PrimitiveLiteral { + get { + return Constraints.OfType() + .Select(literal => literal.LiteralExpr.ToString()).FirstOrDefault() ?? ""; + } + } + + public Dictionary Fields() { + var fields = new Dictionary(); + foreach (var memberSelectExpr in Constraints.OfType() + .Where(constraint => Equals(constraint.Obj, this))) { + fields[memberSelectExpr.MemberName] = memberSelectExpr.DefinedValue; + } + + return fields; + } + + public IEnumerable UnnamedDestructors() { + var datatypeValue = Constraints.OfType() + .FirstOrDefault(constraint => Equals(constraint.DefinedValue, this)); + if (datatypeValue != null) { + foreach (var destructor in datatypeValue.UnnamedDestructors) { + yield return destructor; + } + } + } + + public IEnumerable SetElements() { + return Constraints.OfType() + .Where(containment => Equals(containment.Set, this)) + .Select(containment => containment.Element); + } + + public string DatatypeConstructorName() { + return Constraints.OfType() + .Select(constructorCheck => constructorCheck.ConstructorName).FirstOrDefault() ?? ""; + } + + public IEnumerable<(PartialValue Key, PartialValue Value)> Mappings() { + foreach (var mapping in Constraints.OfType().Where(constraint => Equals(constraint.Map, this))) { + yield return new(mapping.Key, mapping.DefinedValue); + } + } + + public int? Cardinality() { + if (Constraints.OfType().Any(constraint => + (constraint.LiteralExpr is DisplayExpression displayExpression && !displayExpression.SubExpressions.Any()) || + (constraint.LiteralExpr is MapDisplayExpr mapDisplayExpr && !mapDisplayExpr.Elements.Any()))) { + return 0; + } + + var cardinality = Constraints.OfType() + .FirstOrDefault(constraint => Equals(constraint.Collection, this))?.DefinedValue; + if (cardinality == null) { + return -1; + } + + var cardinalityLiteral = + cardinality.Constraints.OfType().FirstOrDefault()?.LiteralExpr as LiteralExpr; + if (cardinalityLiteral == null) { + return -1; + } + + if (cardinalityLiteral.Value is not BigInteger bigInteger || + !bigInteger.LessThanOrEquals(new BigInteger(int.MaxValue))) { + return -1; + } + + return (int)bigInteger; + } + + + public PartialValue? this[int i] { + get { + foreach (var seqSelectConstraint in Constraints.OfType() + .Where(constraint => Equals(constraint.Seq, this))) { + if (seqSelectConstraint.Index.ToString() == i.ToString()) { + return seqSelectConstraint.DefinedValue; + } + } + + foreach (var seqSelectConstraint in Constraints.OfType() + .Where(constraint => Equals(constraint.Seq, this))) { + var indexLiteral = seqSelectConstraint.Index.Constraints.OfType().FirstOrDefault() + ?.LiteralExpr; + if (indexLiteral != null && indexLiteral.ToString() == i.ToString()) { + return seqSelectConstraint.DefinedValue; + } + } + + return null; + } + } + + public override bool Equals(object? obj) { + return obj is PartialValue other && other.Element == Element && other.state == state; + } + + public override int GetHashCode() { + return Element.GetHashCode(); + } + +} \ No newline at end of file diff --git a/Source/DafnyCore/CounterExampleGeneration/README.md b/Source/DafnyCore/CounterExampleGeneration/README.md deleted file mode 100644 index 42d4b3d2b8..0000000000 --- a/Source/DafnyCore/CounterExampleGeneration/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# Counterexample Generation - -The following is a class-by-class description of the files in this directory intended to help with maintaining and improving the counterexample generation feature of Dafny: - -- [DafnyModel](DafnyModel.cs) - a wrapper around Boogie's `Model` class that defines Dafny specific functions and provides functionality for extracting types and values of `Elements` representing Dafny variables. The three core methods are: - - `GetDafnyType`, which returns a `DafnyModelType` instance for an arbitrary `Element` in the underlying model - - `CanonicalName`, which returns the value of any Element representing a variable of the basic type in Dafny - - `GetExpansion`, which computes all the "children" of a particular variable, that is fields for objects, destructor values for datatypes, elements for sequences, etc. -- [DafnyModelState](DafnyModelState.cs) - Represents a state in a `DafnyModel` and captures the values of all variables at a particular point in the code. -- [DafnyModelVariable](DafnyModelVariable.cs) - Represents a variable at a particular state. Note that a variable in Dafny source can be represented by multiple `DafnyModelVariables`, one for each `DafnyModelState` in `DafnyModel`. The subclasses of `DafnyModelVariable` are: - - `DuplicateVariable` - a variable that has a different name but represents the same `Element` in the same `DafnyModelState` as some other variable. - - `MapVariable` - a variable that represents a map. Allows adding mappings to the map and displaying the map using Dafny syntax. - - `SeqVariable` - a variable that represents a sequence. Allows displaying the sequence using Dafny syntax. -- [DafnyModelVariableFactory](DafnyModelVariable.cs) - A static class for generating instance of `DafnyModelvariable` and its subclasses. The factory chooses which subclass of `DafnyModelVariable` to employ depending on the `Microsoft.Dafny.Type` of the `Element` for which the variable is generated. -- [DafnyModelType](DafnyModelTypeUtils.cs) - Contains a set of utils for manipulating type objects (e.g. reconstructing the original Dafny type name from its Boogie translation: `Mo_dule_.Module2_.Cla__ss` from `Mo__dule___mModule2__.Cla____ss`). diff --git a/Source/DafnyCore/Verifier/BoogieGenerator.TrStatement.cs b/Source/DafnyCore/Verifier/BoogieGenerator.TrStatement.cs index c27b90f21b..23025289ac 100644 --- a/Source/DafnyCore/Verifier/BoogieGenerator.TrStatement.cs +++ b/Source/DafnyCore/Verifier/BoogieGenerator.TrStatement.cs @@ -1510,7 +1510,7 @@ void TrLoop(LoopStmt s, Expression Guard, BodyTranslator/*?*/ bodyTr, } var loopBodyBuilder = new BoogieStmtListBuilder(this, options); - loopBodyBuilder.AddCaptureState(s.Tok, true, "after some loop iterations"); + loopBodyBuilder.AddCaptureState(s.Tok, true, CaptureStateExtensions.AfterLoopIterationsStateMarker); // As the first thing inside the loop, generate: if (!w) { CheckWellformed(inv); assume false; } invDefinednessBuilder.Add(TrAssumeCmd(s.Tok, Bpl.Expr.False)); diff --git a/Source/DafnyCore/Verifier/CaptureStateExtensions.cs b/Source/DafnyCore/Verifier/CaptureStateExtensions.cs index b406b4aa7a..acb4a5a3f7 100644 --- a/Source/DafnyCore/Verifier/CaptureStateExtensions.cs +++ b/Source/DafnyCore/Verifier/CaptureStateExtensions.cs @@ -3,9 +3,11 @@ using Bpl = Microsoft.Boogie; namespace Microsoft.Dafny { - static class CaptureStateExtensions { + public static class CaptureStateExtensions { - public static void AddCaptureState(this BoogieStmtListBuilder builder, Statement statement) { + public const string AfterLoopIterationsStateMarker = "after some loop iterations"; + + internal static void AddCaptureState(this BoogieStmtListBuilder builder, Statement statement) { if (builder.Options.ExpectingModel || builder.Options.TestGenOptions.Mode != TestGenerationOptions.Modes.None) { builder.Add(CaptureState(builder.Options, statement)); } @@ -17,7 +19,7 @@ private static Bpl.Cmd CaptureState(DafnyOptions options, Statement stmt) { return CaptureState(options, stmt.RangeToken.EndToken, true, null); } - public static void AddCaptureState(this BoogieStmtListBuilder builder, IToken tok, bool isEndToken, string /*?*/ additionalInfo) { + internal static void AddCaptureState(this BoogieStmtListBuilder builder, IToken tok, bool isEndToken, string /*?*/ additionalInfo) { if (builder.Options.ExpectingModel || builder.Options.TestGenOptions.Mode != TestGenerationOptions.Modes.None) { builder.Add(CaptureState(builder.Options, tok, isEndToken, additionalInfo)); } diff --git a/Source/DafnyLanguageServer.Test/Various/CounterExampleTest.cs b/Source/DafnyLanguageServer.Test/Various/CounterExampleTest.cs index 660d1a95ea..d57d6dc0f1 100644 --- a/Source/DafnyLanguageServer.Test/Various/CounterExampleTest.cs +++ b/Source/DafnyLanguageServer.Test/Various/CounterExampleTest.cs @@ -5,21 +5,16 @@ using Microsoft.Dafny.LanguageServer.IntegrationTest.Extensions; using OmniSharp.Extensions.LanguageServer.Protocol; using System.Linq; +using System.Numerics; using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.Boogie; using Microsoft.Dafny.LanguageServer.IntegrationTest.Util; using Microsoft.Dafny.LanguageServer.Workspace; -using Microsoft.Extensions.Logging; using Xunit; using Xunit.Abstractions; namespace Microsoft.Dafny.LanguageServer.IntegrationTest.Various { - static class StringAssert { - public static void Matches(string value, Regex regex) { - Assert.True(regex.Matches(value).Any()); - } - } public class CounterExampleTest : ClientBasedLanguageServerTest { @@ -50,7 +45,7 @@ public async Task CounterexamplesStillWorksIfNothingHasBeenVerified(Action options.Set(ProjectManager.Verification, VerifyOnMode.Never)); var source = @" method Abs(x: int) returns (y: int) - ensures y > 0 + ensures y >= 0 { } ".TrimStart(); @@ -61,7 +56,7 @@ ensures y > 0 OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); Assert.Equal((2, 6), counterExamples[0].Position); - Assert.True(counterExamples[0].Variables.ContainsKey("y:int")); + Assert.Matches("-[0-9]+ == y", counterExamples[0].Assumption); } [Theory] @@ -70,7 +65,7 @@ public async Task FileWithBodyLessMethodReturnsSingleCounterExampleForPostcondit await SetUpOptions(optionSettings); var source = @" method Abs(x: int) returns (y: int) - ensures y > 0 + ensures y >= 0 { } ".TrimStart(); @@ -81,7 +76,7 @@ ensures y > 0 OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); Assert.Equal((2, 6), counterExamples[0].Position); - Assert.True(counterExamples[0].Variables.ContainsKey("y:int")); + Assert.Matches("-[0-9]+ == y", counterExamples[0].Assumption); } [Theory] @@ -104,9 +99,7 @@ method Abs(x: int) returns (y: int) Assert.Equal((2, 6), counterExamples[0].Position); Assert.Equal((3, 18), counterExamples[1].Position); Assert.Equal((4, 14), counterExamples[2].Position); - Assert.True(counterExamples[2].Variables.ContainsKey("x:int")); - Assert.True(counterExamples[2].Variables.ContainsKey("y:int")); - Assert.True(counterExamples[2].Variables.ContainsKey("z:int")); + Assert.Matches("-[0-9]+ == [xyz]", counterExamples[2].Assumption); } [Theory] @@ -136,7 +129,7 @@ public async Task GetCounterExampleWithMultipleMethodsWithErrorsReturnsCounterEx await SetUpOptions(optionSettings); var source = @" method Abs(x: int) returns (y: int) - ensures y > 0 + ensures y >= 0 { } @@ -154,10 +147,15 @@ method Negate(a: int) returns (b: int) .OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Equal(2, counterExamples.Length); Assert.Equal((2, 6), counterExamples[0].Position); - Assert.True(counterExamples[0].Variables.ContainsKey("y:int")); + Assert.Matches(new Regex("-[0-9]+ == y"), counterExamples[0].Assumption); Assert.Equal((7, 6), counterExamples[1].Position); - Assert.True(counterExamples[1].Variables.ContainsKey("a:int")); - Assert.True(counterExamples[1].Variables.ContainsKey("b:int")); + var aRegex = new Regex("(-?[0-9]+) == a"); + var bRegex = new Regex("(-?[0-9]+) == b"); + Assert.Matches(aRegex, counterExamples[1].Assumption); + Assert.Matches(bRegex, counterExamples[1].Assumption); + Assert.NotEqual( + aRegex.Match(counterExamples[1].Assumption).Groups[1].Value, + bRegex.Match(counterExamples[1].Assumption).Groups[1].Value); } [Theory] @@ -174,9 +172,24 @@ method a(r:real) { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.Equal(1, counterExamples[0].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("r:real")); - Assert.Equal("1.0", counterExamples[0].Variables["r:real"]); + Assert.Contains("1.0 == r", counterExamples[0].Assumption); + } + + [Theory] + [MemberData(nameof(OptionSettings))] + public async Task SpecificInteger(Action optionSettings) { + await SetUpOptions(optionSettings); + var source = @" + method a(i:int) { + assert i != 25; + } + ".TrimStart(); + var documentItem = CreateTestDocument(source, "integer.dfy"); + await client.OpenDocumentAndWaitAsync(documentItem, CancellationToken); + var counterExamples = (await RequestCounterExamples(documentItem.Uri)). + OrderBy(counterexample => counterexample.Position).ToArray(); + Assert.Single(counterExamples); + Assert.Contains("25 == i", counterExamples[0].Assumption); } [Theory] @@ -193,9 +206,7 @@ method a(r:real) { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.Equal(1, counterExamples[0].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("r:real")); - StringAssert.Matches(counterExamples[0].Variables["r:real"], new Regex("[0-9]+\\.[0-9]+/[0-9]+\\.[0-9]+")); + Assert.Matches("[0-9]+\\.[0-9]+ / [0-9]+\\.[0-9]+ == r;", counterExamples[0].Assumption); } [Theory] @@ -215,9 +226,7 @@ method a(v:Value) { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.Equal(1, counterExamples[0].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("v:_module.Value")); - Assert.Equal("(v := 0.0)", counterExamples[0].Variables["v:_module.Value"]); + Assert.Contains("0.0 == v.v", counterExamples[0].Assumption); } [Theory] @@ -237,9 +246,7 @@ method a(v:Value) { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.Equal(1, counterExamples[0].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("v:_module.Value")); - Assert.Equal("(with_underscore_ := 42)", counterExamples[0].Variables["v:_module.Value"]); + Assert.Contains("42 == v.with_underscore_", counterExamples[0].Assumption); } [Theory] @@ -259,9 +266,7 @@ method a(v:Value) { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.Equal(1, counterExamples[0].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("v:_module.Value")); - StringAssert.Matches(counterExamples[0].Variables["v:_module.Value"], new Regex("\\(v := [0-9]+\\.[0-9]+/[0-9]+\\.[0-9]+\\)")); + Assert.Matches($"\\(?[0-9]+\\.[0-9]+ / [0-9]+\\.[0-9]+\\)? == v.v", counterExamples[0].Assumption); } [Theory] @@ -281,12 +286,10 @@ method IsSelfReferring(n:Node) { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.Equal(1, counterExamples[0].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("n:_module.Node")); - Assert.Equal("(next := n)", counterExamples[0].Variables["n:_module.Node"]); + Assert.Matches("n == n.next", counterExamples[0].Assumption); } - [Theory] + [Theory(Skip = "This test should be re-enabled once we can assert inequality between objects")] [MemberData(nameof(OptionSettings))] public async Task ObjectWithANonNullField(Action optionSettings) { await SetUpOptions(optionSettings); @@ -303,9 +306,8 @@ method IsSelfRecursive(n:Node) { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.Equal(2, counterExamples[0].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("n:_module.Node")); - StringAssert.Matches(counterExamples[0].Variables["n:_module.Node"], new Regex("\\(next := @[0-9]+\\)")); + Assert.Contains("n != null", counterExamples[0].Assumption); + Assert.Contains("n.next != n", counterExamples[0].Assumption); } [Theory] @@ -325,9 +327,7 @@ method IsSelfRecursive(n:Node) { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.Equal(1, counterExamples[0].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("n:_module.Node")); - Assert.Equal("(next := null)", counterExamples[0].Variables["n:_module.Node"]); + Assert.Contains("null == n.next", counterExamples[0].Assumption); } [Theory] @@ -353,15 +353,8 @@ modifies this await client.OpenDocumentAndWaitAsync(documentItem, CancellationToken); var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); - Assert.Equal(2, counterExamples.Length); - Assert.Equal(2, counterExamples[0].Variables.Count); - Assert.Equal(2, counterExamples[1].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("amount:int")); - Assert.True(counterExamples[1].Variables.ContainsKey("amount:int")); - Assert.True(counterExamples[0].Variables.ContainsKey("this:_module.BankAccountUnsafe")); - Assert.True(counterExamples[1].Variables.ContainsKey("this:_module.BankAccountUnsafe")); - StringAssert.Matches(counterExamples[0].Variables["this:_module.BankAccountUnsafe"], new Regex("\\(balance := [0-9]+\\)")); - StringAssert.Matches(counterExamples[1].Variables["this:_module.BankAccountUnsafe"], new Regex("\\(balance := \\-[0-9]+\\)")); + Assert.Matches("[0-9]+ == this.balance", counterExamples[0].Assumption); + Assert.Matches("-[0-9]+ == this.balance", counterExamples[1].Assumption); } [Theory] @@ -378,9 +371,29 @@ method a(c:char) { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.Equal(1, counterExamples[0].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("c:char")); - Assert.Equal("'0'", counterExamples[0].Variables["c:char"]); + Assert.Contains("'0' == c", counterExamples[0].Assumption); + } + + [Theory] + [MemberData(nameof(OptionSettings))] + public async Task TwoCharacters(Action optionSettings) { + await SetUpOptions(optionSettings); + var source = @" + method a(c1:char, c2:char) { + assert c1 == c2; + } + ".TrimStart(); + var documentItem = CreateTestDocument(source, "TwoCharacters.dfy"); + await client.OpenDocumentAndWaitAsync(documentItem, CancellationToken); + var counterExamples = (await RequestCounterExamples(documentItem.Uri)). + OrderBy(counterexample => counterexample.Position).ToArray(); + Assert.Single(counterExamples); + var charRegex = "(\'[^']+\')"; + Assert.Matches(charRegex + " == c1", counterExamples[0].Assumption); + Assert.Matches(charRegex + " == c2", counterExamples[0].Assumption); + var c1 = Regex.Match(counterExamples[0].Assumption, charRegex + " == c1").Groups[1]; + var c2 = Regex.Match(counterExamples[0].Assumption, charRegex + " == c2").Groups[1]; + Assert.NotEqual(c1, c2); } [Theory] @@ -397,10 +410,7 @@ method a(c:char) { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.Equal(1, counterExamples[0].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("c:char")); - StringAssert.Matches(counterExamples[0].Variables["c:char"], new Regex("('.'|\\?#[0-9]+)")); - Assert.NotEqual("'0'", counterExamples[0].Variables["c:char"]); + Assert.Matches("'.+' == c", counterExamples[0].Assumption); } [Theory] @@ -418,15 +428,31 @@ method a(b:B) { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.Equal(1, counterExamples[0].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("b:_module.B")); - // Unnamed destructors are implicitly assigned names starting with "#" during resolution: - Assert.Equal("A(#0 := 5)", counterExamples[0].Variables["b:_module.B"]); + Assert.Contains("B.A(5) == b", counterExamples[0].Assumption); } [Theory] [MemberData(nameof(OptionSettings))] - public async Task DatatypeWithDestructorThanIsADataValue(Action optionSettings) { + public async Task DatatypeWithUnnamedDestructor2(Action optionSettings) { + await SetUpOptions(optionSettings); + var source = @" + datatype A = A(i:int) + datatype B = B(A) + method a(b:B) { + assert b != B(A(5)); + } + ".TrimStart(); + var documentItem = CreateTestDocument(source, "DatatypeWithUnnamedDestructor.dfy"); + await client.OpenDocumentAndWaitAsync(documentItem, CancellationToken); + var counterExamples = (await RequestCounterExamples(documentItem.Uri)). + OrderBy(counterexample => counterexample.Position).ToArray(); + Assert.Single(counterExamples); + Assert.Contains("B.B(A.A(5)) == b", counterExamples[0].Assumption); + } + + [Theory] + [MemberData(nameof(OptionSettings))] + public async Task DatatypeWithDestructorThatIsADataValue(Action optionSettings) { await SetUpOptions(optionSettings); var source = @" datatype A = B(x:real) @@ -439,9 +465,8 @@ method destructorNameTest(a:A) { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.Equal(1, counterExamples[0].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("a:_module.A")); - StringAssert.Matches(counterExamples[0].Variables["a:_module.A"], new Regex("B\\(x := -[0-9]+\\.[0-9]+/[0-9]+\\.[0-9]+\\)")); + var realRegex = "-\\(?[0-9]+\\.[0-9]+ / [0-9]+\\.[0-9]+\\)"; + Assert.Matches($"A.B\\({realRegex}\\) == a", counterExamples[0].Assumption); } [Theory] @@ -460,11 +485,8 @@ requires h0.Right? && h1.Left? { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.Equal(2, counterExamples[0].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("h0:_module.Hand")); - Assert.True(counterExamples[0].Variables.ContainsKey("h1:_module.Hand")); - StringAssert.Matches(counterExamples[0].Variables["h0:_module.Hand"], new Regex("Right\\([a|b] := -?[0-9]+, [b|a] := -?[0-9]+\\)")); - StringAssert.Matches(counterExamples[0].Variables["h1:_module.Hand"], new Regex("Left\\([x|y] := -?[0-9]+, [x|y] := -?[0-9]+\\)")); + Assert.Matches("Hand\\.Right\\([0-9]+, [0-9]+\\) == h0", counterExamples[0].Assumption); + Assert.Matches("Hand\\.Left\\([0-9]+, [0-9]+\\) == h1", counterExamples[0].Assumption); } [Theory] @@ -482,9 +504,7 @@ method T_datatype0_1(h:Hand) { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.Equal(1, counterExamples[0].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("h:_module.Hand")); - StringAssert.Matches(counterExamples[0].Variables["h:_module.Hand"], new Regex("Left\\([a|b] := 3, [a|b] := 3\\)")); + Assert.Contains("Hand.Left(3, 3) == h", counterExamples[0].Assumption); } [Theory] @@ -502,9 +522,7 @@ method m (a:A) requires !a.B_?{ var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.Equal(1, counterExamples[0].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("a:_module.A")); - StringAssert.Matches(counterExamples[0].Variables["a:_module.A"], new Regex("C\\((B_q|C_q|D_q) := false, (B_q|C_q|D_q) := false, (B_q|C_q|D_q) := false\\)")); + Assert.Contains("A.C(false, false, false) == a", counterExamples[0].Assumption); } @@ -523,36 +541,12 @@ method m(a:A) requires a == One(false) || a == One(true) { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.Equal(1, counterExamples[0].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("a:_module.A")); - Assert.Equal("One(b := false)", counterExamples[0].Variables["a:_module.A"]); + Assert.Contains("A.One(false) == a", counterExamples[0].Assumption); } [Theory] [MemberData(nameof(OptionSettings))] - public async Task ArbitraryBool(Action optionSettings) { - await SetUpOptions(optionSettings); - var source = @" - datatype List = Nil | Cons(head: T, tail: List) - method listHasSingleElement(list:List) - requires list != Nil - { - assert list.tail != Nil; - } - ".TrimStart(); - var documentItem = CreateTestDocument(source, "ArbitraryBool.dfy"); - await client.OpenDocumentAndWaitAsync(documentItem, CancellationToken); - var counterExamples = (await RequestCounterExamples(documentItem.Uri)). - OrderBy(counterexample => counterexample.Position).ToArray(); - Assert.Single(counterExamples); - Assert.Equal(2, counterExamples[0].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("list:_module.List")); - StringAssert.Matches(counterExamples[0].Variables["list:_module.List"], new Regex("Cons\\(head := (true|false), tail := @[0-9]+\\)")); - } - - [Theory] - [MemberData(nameof(OptionSettings))] - public async Task ArbitraryInt(Action optionSettings) { + public async Task DestructorDoesNotMatter(Action optionSettings) { await SetUpOptions(optionSettings); var source = @" datatype List = Nil | Cons(head: T, tail: List) @@ -567,31 +561,7 @@ method listHasSingleElement(list:List) var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.Equal(2, counterExamples[0].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("list:_module.List")); - StringAssert.Matches(counterExamples[0].Variables["list:_module.List"], new Regex("Cons\\(head := -?[0-9]+, tail := @[0-9]+\\)")); - } - - [Theory] - [MemberData(nameof(OptionSettings))] - public async Task ArbitraryReal(Action optionSettings) { - await SetUpOptions(optionSettings); - var source = @" - datatype List = Nil | Cons(head: T, tail: List) - method listHasSingleElement(list:List) - requires list != Nil - { - assert list.tail != Nil; - } - ".TrimStart(); - var documentItem = CreateTestDocument(source, "ArbitraryReal.dfy"); - await client.OpenDocumentAndWaitAsync(documentItem, CancellationToken); - var counterExamples = (await RequestCounterExamples(documentItem.Uri)). - OrderBy(counterexample => counterexample.Position).ToArray(); - Assert.Single(counterExamples); - Assert.Equal(2, counterExamples[0].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("list:_module.List")); - StringAssert.Matches(counterExamples[0].Variables["list:_module.List"], new Regex("Cons\\(head := -?[0-9]+\\.[0-9], tail := @[0-9]+\\)")); + Assert.Matches("List\\.Cons\\([0-9]+, List\\.Nil\\) == list", counterExamples[0].Assumption); } [Theory] @@ -608,9 +578,9 @@ method a(arr:array) requires arr.Length == 2 { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.Equal(1, counterExamples[0].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("arr:_System.array"), string.Join(", ", counterExamples[0].Variables)); - Assert.Equal("(Length := 2, [0] := 4, [1] := 5)", counterExamples[0].Variables["arr:_System.array"]); + Assert.Contains("2 == arr.Length", counterExamples[0].Assumption); + Assert.Contains("4 == arr[0]", counterExamples[0].Assumption); + Assert.Contains("5 == arr[1]", counterExamples[0].Assumption); } [Theory] @@ -627,9 +597,8 @@ method a(s:seq) requires |s| == 1 { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.Equal(1, counterExamples[0].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("s:seq")); - Assert.Equal("[4]", counterExamples[0].Variables["s:seq"]); + Assert.Contains("1 == |s|", counterExamples[0].Assumption); + Assert.Contains("4 == s[0]", counterExamples[0].Assumption); } [Theory] @@ -646,9 +615,8 @@ method a(s:seq) requires |s| == 2 { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.Equal(1, counterExamples[0].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("s:seq")); - Assert.Equal("(Length := 2, [1] := 2)", counterExamples[0].Variables["s:seq"]); + Assert.Contains("2 == |s|", counterExamples[0].Assumption); + Assert.Contains("2 == s[1]", counterExamples[0].Assumption); } [Theory] @@ -665,9 +633,7 @@ method a(bv:bv7) { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.Equal(1, counterExamples[0].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("bv:bv7")); - Assert.Equal("2", counterExamples[0].Variables["bv:bv7"]); + Assert.Contains("2 == bv", counterExamples[0].Assumption); } [Theory] @@ -684,9 +650,7 @@ method a(b:bv2) { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.Equal(1, counterExamples[0].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("b:bv2")); - StringAssert.Matches(counterExamples[0].Variables["b:bv2"], new Regex("[023]")); + Assert.Matches("[023] == b", counterExamples[0].Assumption); } [Theory] @@ -703,11 +667,8 @@ method m(a:bv1, b:bv1) { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.Equal(2, counterExamples[0].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("a:bv1")); - Assert.True(counterExamples[0].Variables.ContainsKey("b:bv1")); - StringAssert.Matches(counterExamples[0].Variables["a:bv1"], new Regex("(1|b)")); - StringAssert.Matches(counterExamples[0].Variables["b:bv1"], new Regex("(1|a)")); + Assert.Contains("1 == a", counterExamples[0].Assumption); + Assert.Contains("1 == b", counterExamples[0].Assumption); } [Theory] @@ -727,9 +688,7 @@ method a(v:Value) { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.Equal(1, counterExamples[0].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("v:_module.Value")); - Assert.Equal("(b := 2)", counterExamples[0].Variables["v:_module.Value"]); + Assert.Matches("2 == v.b", counterExamples[0].Assumption); } [Theory] @@ -746,8 +705,8 @@ method a(s:set>>>) requires |s| <= 1{ var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.True(counterExamples[0].Variables.ContainsKey("s:set>>>")); - StringAssert.Matches(counterExamples[0].Variables["s:set>>>"], new Regex("\\{@[0-9]+ := true\\}")); + Assert.Contains("exists boundVar0: seq>>", counterExamples[0].Assumption); + Assert.Contains("boundVar0 in s", counterExamples[0].Assumption); } [Theory] @@ -764,9 +723,10 @@ method m(a:array3) requires a.Length0 == 4 requires a.Length1 == 5 requires var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.Equal(1, counterExamples[0].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("a:_System.array3"), string.Join(", ", counterExamples[0].Variables)); - Assert.Equal("(Length0 := 4, Length1 := 5, Length2 := 6, [2,3,1] := 7)", counterExamples[0].Variables["a:_System.array3"]); + Assert.Matches("(a.Length0 == 4|4 == a.Length0)", counterExamples[0].Assumption); + Assert.Matches("(a.Length1 == 5|5 == a.Length1)", counterExamples[0].Assumption); + Assert.Matches("(a.Length2 == 6|6 == a.Length2)", counterExamples[0].Assumption); + Assert.Contains("7 == a[2, 3, 1]", counterExamples[0].Assumption); } [Theory] @@ -783,10 +743,7 @@ method test(x:array, y:array) { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.Equal(2, counterExamples[0].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("x:_System.array")); - Assert.True(counterExamples[0].Variables.ContainsKey("y:_System.array")); - Assert.True(counterExamples[0].Variables["y:_System.array"] == "x" || counterExamples[0].Variables["x:_System.array"] == "y"); + Assert.Matches("(x == y|y == x)", counterExamples[0].Assumption); } [Theory] @@ -806,27 +763,16 @@ method a(s1:set, s2:set) { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Equal(4, counterExamples.Length); - Assert.Equal(5, counterExamples[2].Variables.Count); - Assert.True(counterExamples[3].Variables.ContainsKey("s1:set")); - Assert.True(counterExamples[3].Variables.ContainsKey("s2:set")); - Assert.True(counterExamples[3].Variables.ContainsKey("sUnion:set")); - Assert.True(counterExamples[3].Variables.ContainsKey("sInter:set")); - Assert.True(counterExamples[3].Variables.ContainsKey("sDiff:set")); - var s1 = counterExamples[3].Variables["s1:set"][1..^1].Split(", "); - var s2 = counterExamples[3].Variables["s2:set"][1..^1].Split(", "); - var sUnion = counterExamples[3].Variables["sUnion:set"][1..^1].Split(", "); - var sInter = counterExamples[3].Variables["sInter:set"][1..^1].Split(", "); - var sDiff = counterExamples[3].Variables["sDiff:set"][1..^1].Split(", "); - Assert.Contains("'a' := true", s1); - Assert.Contains("'a' := false", s2); - Assert.Contains("'a' := true", sDiff); - Assert.Contains("'a' := true", sUnion); - Assert.Contains("'a' := false", sInter); - Assert.Contains("'b' := true", s1); - Assert.Contains("'b' := true", s2); - Assert.Contains("'b' := false", sDiff); - Assert.Contains("'b' := true", sUnion); - Assert.Contains("'b' := true", sInter); + Assert.Contains("'a' in s1", counterExamples[3].Assumption); + Assert.Contains("'a' !in s2", counterExamples[3].Assumption); + Assert.Contains("'a' in sDiff", counterExamples[3].Assumption); + Assert.Contains("'a' in sUnion", counterExamples[3].Assumption); + Assert.Contains("'a' !in sInter", counterExamples[3].Assumption); + Assert.Contains("'b' in s1", counterExamples[3].Assumption); + Assert.Contains("'b' in s2", counterExamples[3].Assumption); + Assert.Contains("'b' !in sDiff", counterExamples[3].Assumption); + Assert.Contains("'b' in sUnion", counterExamples[3].Assumption); + Assert.Contains("'b' in sInter", counterExamples[3].Assumption); } [Theory] @@ -843,9 +789,7 @@ method test() { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Equal(2, counterExamples.Length); - Assert.Equal(1, counterExamples[1].Variables.Count); - Assert.True(counterExamples[1].Variables.ContainsKey("s:set")); - StringAssert.Matches(counterExamples[1].Variables["s:set"], new Regex("\\{.*6 := true.*\\}")); + Assert.Contains("6 in s", counterExamples[1].Assumption); } [Theory] @@ -861,9 +805,7 @@ public async Task StringBuilding(Action optionSettings) { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.Equal(1, counterExamples[0].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("s:seq")); - Assert.Equal("['a', 'b', 'c']", counterExamples[0].Variables["s:seq"]); + Assert.Contains("['a', 'b', 'c'] == s", counterExamples[0].Assumption); } [Theory] @@ -879,13 +821,10 @@ public async Task SequenceEdit(Action optionSettings) { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Equal(2, counterExamples.Length); - Assert.Equal(3, counterExamples[1].Variables.Count); - Assert.True(counterExamples[1].Variables.ContainsKey("s1:seq")); - Assert.True(counterExamples[1].Variables.ContainsKey("s2:seq")); - Assert.True(counterExamples[1].Variables.ContainsKey("c:char")); - Assert.Equal("['a', 'b', 'c']", counterExamples[1].Variables["s1:seq"]); - Assert.Equal("['a', 'd', 'c']", counterExamples[1].Variables["s2:seq"]); - Assert.Equal("'d'", counterExamples[1].Variables["c:char"]); + Assert.Contains("3 == |s2|", counterExamples[1].Assumption); + Assert.Contains("'a' == s2[0]", counterExamples[1].Assumption); + Assert.Contains("'d' == s2[1]", counterExamples[1].Assumption); + Assert.Contains("'c' == s2[2]", counterExamples[1].Assumption); } [Theory] @@ -902,9 +841,7 @@ method test() { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Equal(2, counterExamples.Length); - Assert.Equal(1, counterExamples[1].Variables.Count); - Assert.True(counterExamples[1].Variables.ContainsKey("s:seq")); - StringAssert.Matches(counterExamples[1].Variables["s:seq"], new Regex("\\[6\\]")); + Assert.Contains("[6] == s", counterExamples[1].Assumption); } [Theory] @@ -913,7 +850,7 @@ public async Task SequenceConcat(Action optionSettings) { await SetUpOptions(optionSettings); var source = @" method a(s1:string, s2:string) requires |s1| == 1 && |s2| == 1 { - var sCat:string := s2 + s1; + var sCat:string := s1 + s2; assert sCat[0] != 'a' || sCat[1] != 'b'; }".TrimStart(); var documentItem = CreateTestDocument(source, "SequenceConcat.dfy"); @@ -921,16 +858,15 @@ method a(s1:string, s2:string) requires |s1| == 1 && |s2| == 1 { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Equal(2, counterExamples.Length); - Assert.Equal(3, counterExamples[1].Variables.Count); - Assert.True(counterExamples[1].Variables.ContainsKey("s1:seq")); - Assert.True(counterExamples[1].Variables.ContainsKey("s2:seq")); - Assert.True(counterExamples[1].Variables.ContainsKey("sCat:seq")); - Assert.Equal("['b']", counterExamples[1].Variables["s1:seq"]); - Assert.Equal("['a']", counterExamples[1].Variables["s2:seq"]); - Assert.Equal("['a', 'b']", counterExamples[1].Variables["sCat:seq"]); + Assert.Contains("1 == |s1|", counterExamples[1].Assumption); + Assert.Contains("1 == |s2|", counterExamples[1].Assumption); + Assert.Contains("'a' == sCat[0]", counterExamples[1].Assumption); + Assert.Contains("'a' == s1[0]", counterExamples[1].Assumption); + Assert.Contains("'b' == sCat[1]", counterExamples[1].Assumption); + Assert.Contains("'b' == s2[0]", counterExamples[1].Assumption); } - [Theory] + [Theory(Skip = "This test should be re-enabled once counterexamples support lambda expressions")] [MemberData(nameof(OptionSettings))] public async Task SequenceGenerate(Action optionSettings) { await SetUpOptions(optionSettings); @@ -943,11 +879,7 @@ method a(multiplier:int) { await client.OpenDocumentAndWaitAsync(documentItem, CancellationToken); var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); - Assert.Equal(2, counterExamples.Length); - Assert.True(counterExamples[1].Variables.ContainsKey("multiplier:int")); - Assert.True(counterExamples[1].Variables.ContainsKey("s:seq")); - StringAssert.Matches(counterExamples[1].Variables["s:seq"], new Regex("\\(Length := 3, .*\\[2\\] := 6.*\\)")); - Assert.Equal("3", counterExamples[1].Variables["multiplier:int"]); + Assert.Fail("This test needs to be updated in a way that deals with the variables introduced as part of the lambda expression."); } [Theory] @@ -955,8 +887,8 @@ method a(multiplier:int) { public async Task SequenceSub(Action optionSettings) { await SetUpOptions(optionSettings); var source = @" - method a(s:seq) requires |s| == 5 { - var sSub:seq := s[2..4]; + method a(s:seq) requires |s| == 7 { + var sSub:seq := s[2..6]; assert sSub[0] != 'a' || sSub[1] != 'b'; }".TrimStart(); var documentItem = CreateTestDocument(source, "SequenceSub.dfy"); @@ -964,11 +896,12 @@ method a(s:seq) requires |s| == 5 { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Equal(2, counterExamples.Length); - Assert.Equal(2, counterExamples[1].Variables.Count); - Assert.True(counterExamples[1].Variables.ContainsKey("sSub:seq")); - Assert.True(counterExamples[1].Variables.ContainsKey("s:seq")); - Assert.Equal("['a', 'b']", counterExamples[1].Variables["sSub:seq"]); - StringAssert.Matches(counterExamples[0].Variables["s:seq"], new Regex("\\(Length := 5,.*\\[2\\] := 'a', \\[3\\] := 'b'.*")); + Assert.Contains("4 == |sSub|", counterExamples[1].Assumption); + Assert.Contains("7 == |s|", counterExamples[1].Assumption); + Assert.Contains("'a' == s[2]", counterExamples[1].Assumption); + Assert.Contains("'b' == s[3]", counterExamples[1].Assumption); + Assert.Contains("'a' == sSub[0]", counterExamples[1].Assumption); + Assert.Contains("'b' == sSub[1]", counterExamples[1].Assumption); } [Theory] @@ -976,20 +909,16 @@ method a(s:seq) requires |s| == 5 { public async Task SequenceDrop(Action optionSettings) { await SetUpOptions(optionSettings); var source = @" - method a(s:seq) requires |s| == 5 { + method a(s:seq) requires s == ['a', 'b', 'c', 'd', 'e'] { var sSub:seq := s[2..]; - assert sSub[0] != 'a' || sSub[1] != 'b' || sSub[2] != 'c'; + assert false; }".TrimStart(); var documentItem = CreateTestDocument(source, "SequenceDrop.dfy"); await client.OpenDocumentAndWaitAsync(documentItem, CancellationToken); var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Equal(2, counterExamples.Length); - Assert.Equal(2, counterExamples[1].Variables.Count); - Assert.True(counterExamples[1].Variables.ContainsKey("sSub:seq")); - Assert.True(counterExamples[1].Variables.ContainsKey("s:seq")); - Assert.Equal("['a', 'b', 'c']", counterExamples[1].Variables["sSub:seq"]); - StringAssert.Matches(counterExamples[0].Variables["s:seq"], new Regex("\\(Length := 5,.*\\[2\\] := 'a', \\[3\\] := 'b', \\[4\\] := 'c'.*")); + Assert.Contains("['c', 'd', 'e'] == sSub", counterExamples[1].Assumption); } [Theory] @@ -1006,11 +935,9 @@ method a(s:seq) requires |s| == 5 { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Equal(2, counterExamples.Length); - Assert.Equal(2, counterExamples[1].Variables.Count); - Assert.True(counterExamples[1].Variables.ContainsKey("sSub:seq")); - Assert.True(counterExamples[1].Variables.ContainsKey("s:seq")); - Assert.Equal("['a', 'b', 'c']", counterExamples[1].Variables["sSub:seq"]); - StringAssert.Matches(counterExamples[0].Variables["s:seq"], new Regex("\\(Length := 5,.*\\[0\\] := 'a', \\[1\\] := 'b', \\[2\\] := 'c'.*")); + Assert.Contains("'a' == s[0]", counterExamples[0].Assumption); + Assert.Contains("'b' == s[1]", counterExamples[0].Assumption); + Assert.Contains("'c' == s[2]", counterExamples[0].Assumption); } [Theory] @@ -1043,9 +970,8 @@ method test() { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Equal(2, counterExamples.Length); - Assert.Equal(1, counterExamples[1].Variables.Count); - Assert.True(counterExamples[1].Variables.ContainsKey("m:map")); - StringAssert.Matches(counterExamples[1].Variables["m:map"], new Regex("map\\[.*3 := false.*")); + Assert.Contains("3 in m", counterExamples[1].Assumption); + Assert.Contains("false == m[3]", counterExamples[1].Assumption); } [Theory] @@ -1055,29 +981,56 @@ public async Task MapsEmpty(Action optionSettings) { var source = @" method test() { var m : map := map[]; - assert 3 in m; + assert false; }".TrimStart(); var documentItem = CreateTestDocument(source, "MapsEmpty.dfy"); await client.OpenDocumentAndWaitAsync(documentItem, CancellationToken); var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Equal(2, counterExamples.Length); - Assert.Equal(1, counterExamples[1].Variables.Count); - Assert.True(counterExamples[1].Variables.ContainsKey("m:map")); - Assert.Equal("map[]", counterExamples[1].Variables["m:map"]); + Assert.Matches("(map\\[\\] == m|0 == \\|m\\|)", counterExamples[1].Assumption); } - [Theory] + [Theory(Skip = "This test should be re-enabled once we support traits")] [MemberData(nameof(OptionSettings))] public async Task TraitType(Action optionSettings) { await SetUpOptions(optionSettings); var source = @" module M { - trait C { - predicate Valid() {false} + trait T { + function Value():int reads this + } + class C extends T { + var value:int + function Value():int + reads this + { value } + } + method test(t1:T, t2:T) { + assert t1.Value() == t2.Value(); + } + }".TrimStart(); + var documentItem = CreateTestDocument(source, "TraitType.dfy"); + await client.OpenDocumentAndWaitAsync(documentItem, CancellationToken); + var counterExamples = (await RequestCounterExamples(documentItem.Uri)). + OrderBy(counterexample => counterexample.Position).ToArray(); + Assert.Single(counterExamples); + Assert.Fail("This test needs to be updated once we support traits"); + } + + [Theory] + [MemberData(nameof(OptionSettings))] + public async Task ClassTypeWithPredicate(Action optionSettings) { + await SetUpOptions(optionSettings); + var source = @" + module M { + class C { + predicate Valid() + function Identity(i:int):int + function Sum(a:int, b:int):int } method test(c:C) { - assert c.Valid(); + assert !c.Valid() || c.Identity(1) != 1 || c.Sum(1, 2) != 3; } }".TrimStart(); var documentItem = CreateTestDocument(source, "TraitType.dfy"); @@ -1085,8 +1038,9 @@ method test(c:C) { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.Single(counterExamples[0].Variables); - Assert.True(counterExamples[0].Variables.ContainsKey("c:M.C")); + Assert.Contains("1 == c.Identity(1)", counterExamples[0].Assumption); + Assert.Contains("3 == c.Sum(1, 2)", counterExamples[0].Assumption); + Assert.Contains("true == c.Valid()", counterExamples[0].Assumption); } [Theory] @@ -1105,7 +1059,7 @@ method test() { await client.OpenDocumentAndWaitAsync(documentItem, CancellationToken); var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); - Assert.True(counterExamples[^1].Variables.ContainsKey("c:(int, bool) ~> real")); + Assert.Equal(4, counterExamples.Count()); } [Theory] @@ -1122,8 +1076,9 @@ method test() { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Equal(2, counterExamples.Length); - Assert.Equal(2, counterExamples[1].Variables.Count); - Assert.True(counterExamples[1].Variables.ContainsKey("s:set>")); + Assert.Matches("boundVar[0-9]+: map", counterExamples[1].Assumption); + Assert.Matches("boundVar[0-9]+ in s", counterExamples[1].Assumption); + Assert.Matches("5 == boundVar[0-9]+\\[3\\]", counterExamples[1].Assumption); } [Theory] @@ -1133,8 +1088,7 @@ public async Task DatatypeTypeAsTypeArgument(Action optionSettings var source = @" module M { datatype C = A | B - method test() { - var s : set := {A}; + method test(s:set) { assert |s| == 0; } }".TrimStart(); @@ -1142,10 +1096,9 @@ method test() { await client.OpenDocumentAndWaitAsync(documentItem, CancellationToken); var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); - Assert.Equal(2, counterExamples.Length); - Assert.Equal(2, counterExamples[1].Variables.Count); - Assert.True(counterExamples[1].Variables.ContainsKey("s:set")); - Assert.Contains(counterExamples[1].Variables.Keys, key => key.EndsWith("M.C")); + Assert.Single(counterExamples); + Assert.Contains("exists boundVar0: M.C ::", counterExamples[0].Assumption); + Assert.Contains("boundVar0 in s", counterExamples[0].Assumption); } [Theory] @@ -1162,10 +1115,9 @@ method test() { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Equal(2, counterExamples.Length); - Assert.Equal(1, counterExamples[1].Variables.Count); - // Cannot infer the type when Arguments polymorphic encoding is used - Assert.True(counterExamples[1].Variables.ContainsKey("s:set")); - Assert.Equal("{}", counterExamples[1].Variables["s:set"]); + // the assertion below ensures Dafny does not emit a type constraint if it cannot figure out the variable's type + Assert.DoesNotContain("?", counterExamples[1].Assumption); + Assert.Matches("({} == s|0 == \\|s\\|)", counterExamples[1].Assumption); } [Theory] @@ -1173,27 +1125,21 @@ method test() { public async Task MapsUpdate(Action optionSettings) { await SetUpOptions(optionSettings); var source = @" - method test(value:int) { + method test(value:int) requires value != 3 { var m := map[3 := -1]; - var b := m[3] == -1; m := m[3 := value]; - assert b && m[3] <= 0; + assert m[3] <= 0; }".TrimStart(); var documentItem = CreateTestDocument(source, "MapsUpdate.dfy"); await client.OpenDocumentAndWaitAsync(documentItem, CancellationToken); var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); - Assert.Equal(4, counterExamples.Length); - Assert.Equal(2, counterExamples[1].Variables.Count); - Assert.True(counterExamples[1].Variables.ContainsKey("m:map")); - StringAssert.Matches(counterExamples[1].Variables["m:map"], new Regex("map\\[.*3 := -1.*")); - Assert.Equal(3, counterExamples[3].Variables.Count); - Assert.True(counterExamples[3].Variables.ContainsKey("m:map")); - Assert.True(counterExamples[3].Variables.ContainsKey("value:int")); - Assert.True(counterExamples[3].Variables.ContainsKey("b:bool")); - Assert.Equal("true", counterExamples[3].Variables["b:bool"]); - StringAssert.Matches(counterExamples[3].Variables["value:int"], new Regex("[1-9][0-9]*")); - StringAssert.Matches(counterExamples[3].Variables["m:map"], new Regex("map\\[.*3 := [1-9].*")); + Assert.Equal(3, counterExamples.Length); + Assert.Contains("-1 == m[3]", counterExamples[1].Assumption); + Assert.Matches("[1-9][0-9]* == m\\[3\\]", counterExamples[2].Assumption); + Assert.Matches("[1-9][0-9]* == value", counterExamples[2].Assumption); + Assert.DoesNotContain("m[3] == -1", counterExamples[2].Assumption); + Assert.DoesNotContain("-1 == m[3]", counterExamples[2].Assumption); } [Theory] @@ -1201,38 +1147,30 @@ method test(value:int) { public async Task MapsUpdateStoredInANewVariable(Action optionSettings) { await SetUpOptions(optionSettings); var source = @" - method T_map1(m:map, key:int, val:int) - requires key in m.Keys + method T_map1(m:map) { - var m' := m[key := val - 1]; - m' := m'[key := val]; - assert m'.Values == m.Values - {m[key]} + {val}; + var m' := m[4 := 5]; + m' := m'[4 := 6]; + assert m'.Values == m.Values - {m[4]} + {6}; }".TrimStart(); var documentItem = CreateTestDocument(source, "MapsUpdateStoredInANewVariable.dfy"); await client.OpenDocumentAndWaitAsync(documentItem, CancellationToken); var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); - Assert.Equal(3, counterExamples.Length); - Assert.Equal(4, counterExamples[2].Variables.Count); - Assert.True(counterExamples[2].Variables.ContainsKey("m:map")); - Assert.True(counterExamples[2].Variables.ContainsKey("m':map")); - Assert.True(counterExamples[2].Variables.ContainsKey("val:int")); - Assert.True(counterExamples[2].Variables.ContainsKey("key:int")); - var key = counterExamples[2].Variables["key:int"]; - var val = counterExamples[2].Variables["val:int"]; - StringAssert.Matches(counterExamples[2].Variables["m':map"], new Regex("map\\[.*" + key + " := " + val + ".*")); + Assert.Contains("6 == m'[4]", counterExamples.Last().Assumption); + Assert.DoesNotContain("5 == m'[4]", counterExamples.Last().Assumption); } [Theory] [MemberData(nameof(OptionSettings))] - public async Task MapsBuildRecursive(Action optionSettings) { + public async Task MapsBuildRecursiveSameValue(Action optionSettings) { await SetUpOptions(optionSettings); var source = @" method T_map2() { - var m := map[5 := 39]; - m := m[5 := 38]; - m := m[5 := 37]; + var m := map[5 := 9]; + m := m[5 := 99]; + m := m[5 := 999]; m := m[5 := 36]; assert 5 !in m; }".TrimStart(); @@ -1241,9 +1179,34 @@ method T_map2() var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Equal(5, counterExamples.Length); - Assert.Equal(1, counterExamples[4].Variables.Count); - Assert.True(counterExamples[4].Variables.ContainsKey("m:map")); - StringAssert.Matches(counterExamples[4].Variables["m:map"], new Regex("map\\[.*5 := 36.*")); + Assert.Contains("5 in m", counterExamples[4].Assumption); + Assert.Contains("36 == m[5]", counterExamples[4].Assumption); + Assert.DoesNotContain("9", counterExamples[4].Assumption); + } + + [Theory] + [MemberData(nameof(OptionSettings))] + public async Task MapsBuildRecursive(Action optionSettings) { + await SetUpOptions(optionSettings); + var source = @" + method T_map2() + { + var m := map[1 := 9]; + m := m[2 := 99]; + m := m[3 := 999]; + assert false; + }".TrimStart(); + var documentItem = CreateTestDocument(source, "MapsBuildRecursive.dfy"); + await client.OpenDocumentAndWaitAsync(documentItem, CancellationToken); + var counterExamples = (await RequestCounterExamples(documentItem.Uri)). + OrderBy(counterexample => counterexample.Position).ToArray(); + Assert.Equal(4, counterExamples.Length); + Assert.Contains("1 in m", counterExamples[3].Assumption); + Assert.Contains("2 in m", counterExamples[3].Assumption); + Assert.Contains("3 in m", counterExamples[3].Assumption); + Assert.Contains("9 == m[1]", counterExamples[3].Assumption); + Assert.Contains("99 == m[2]", counterExamples[3].Assumption); + Assert.Contains("999 == m[3]", counterExamples[3].Assumption); } [Theory] @@ -1262,17 +1225,8 @@ method T_map0(m:map, key:int, val:int) var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Equal(2, counterExamples.Length); - Assert.Equal(4, counterExamples[1].Variables.Count); - Assert.True(counterExamples[1].Variables.ContainsKey("m:map")); - Assert.True(counterExamples[1].Variables.ContainsKey("m':map")); - Assert.True(counterExamples[1].Variables.ContainsKey("val:int")); - Assert.True(counterExamples[1].Variables.ContainsKey("key:int")); - var key = counterExamples[1].Variables["key:int"]; - var val = counterExamples[1].Variables["val:int"]; - var mapRegex = new Regex("map\\[.*" + key + " := " + val + ".*"); - Assert.True(mapRegex.IsMatch(counterExamples[1].Variables["m':map"]) || - mapRegex.IsMatch(counterExamples[1].Variables["m:map"])); - + // The precise counterexample that Dafny returns in this case is ambiguous, so there are no further assertions. + // Keeping this test case because it used to trigger an infinite loop. } [Theory] @@ -1289,11 +1243,8 @@ method test(m:map) { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Equal(2, counterExamples.Length); - Assert.Equal(2, counterExamples[1].Variables.Count); - Assert.True(counterExamples[1].Variables.ContainsKey("m:map")); - Assert.True(counterExamples[1].Variables.ContainsKey("keys:set")); - StringAssert.Matches(counterExamples[1].Variables["m:map"], new Regex("map\\[.*25 := .*")); - StringAssert.Matches(counterExamples[1].Variables["keys:set"], new Regex("\\{.*25 := true.*")); + Assert.Contains("25 in m", counterExamples[1].Assumption); + Assert.Matches("(keys == m.Keys|m.Keys == keys)", counterExamples[1].Assumption); } [Theory] @@ -1310,11 +1261,9 @@ method test(m:map) { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Equal(2, counterExamples.Length); - Assert.Equal(2, counterExamples[1].Variables.Count); - Assert.True(counterExamples[1].Variables.ContainsKey("m:map")); - Assert.True(counterExamples[1].Variables.ContainsKey("values:set")); - StringAssert.Matches(counterExamples[1].Variables["m:map"], new Regex("map\\[.* := 'c'.*")); - StringAssert.Matches(counterExamples[1].Variables["values:set"], new Regex("\\{.*'c' := true.*")); + Assert.Equal(2, counterExamples.Length); + Assert.Matches("('c' in values|'c' in m.Values)", counterExamples[1].Assumption); + Assert.Matches("(values == m.Values|m.Values == values)", counterExamples[1].Assumption); } [Theory] @@ -1333,9 +1282,7 @@ method test(m:map) { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.Equal(1, counterExamples[0].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("m:map")); - StringAssert.Matches(counterExamples[0].Variables["m:map"], new Regex("map\\[.*[0-9]+ := [0-9]+.*")); + Assert.Matches("[0-9]+ in m", counterExamples[0].Assumption); } [Theory] @@ -1358,9 +1305,7 @@ method test() { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.Equal(1, counterExamples[0].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("this:Mo_dule_.Module2_.Cla__ss")); - Assert.Equal("(i := 5)", counterExamples[0].Variables["this:Mo_dule_.Module2_.Cla__ss"]); + Assert.Matches("5 == this.i", counterExamples[0].Assumption); } [Theory] @@ -1380,10 +1325,12 @@ function plus(a: nat64, b: nat64): nat64 { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.True(counterExamples[0].Variables.ContainsKey("a:int")); - Assert.True(counterExamples[0].Variables.ContainsKey("b:int")); - var a = long.Parse(counterExamples[0].Variables["a:int"]); - var b = long.Parse(counterExamples[0].Variables["b:int"]); + var aRegex = new Regex("([0-9]+) == a"); + var bRegex = new Regex("([0-9]+) == b"); + Assert.Matches(aRegex, counterExamples[0].Assumption); + Assert.Matches(bRegex, counterExamples[0].Assumption); + var a = long.Parse(aRegex.Match(counterExamples[0].Assumption).Groups[1].Value); + var b = long.Parse(bRegex.Match(counterExamples[0].Assumption).Groups[1].Value); Assert.True(a + b < a || a + b < b); } @@ -1396,20 +1343,20 @@ module M { datatype D = C(i:int) { predicate p() {true} } - - method test(d: D) { + } + method test(d: M.D) { if (d.p()) { - assert d.i != 123; + assert d.i != 123; } - } }".TrimStart(); var documentItem = CreateTestDocument(source, "DatatypeWithPredicate.dfy"); await client.OpenDocumentAndWaitAsync(documentItem, CancellationToken); var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.True(counterExamples[0].Variables.ContainsKey("d:M.D")); - Assert.Equal("C(i := 123)", counterExamples[0].Variables["d:M.D"]); + Assert.Contains("M.D.C(123) == d", counterExamples.First().Assumption); + Assert.Contains("M.D.C(123).p.requires()", counterExamples.First().Assumption); + Assert.Contains("true == M.D.C(123).p()", counterExamples.First().Assumption); } /** Makes sure the counterexample lists the base type of a variable */ @@ -1427,8 +1374,6 @@ public async Task SubsetType(Action optionSettings) { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.True(counterExamples[0].Variables.ContainsKey("s:seq")); - Assert.Equal("['a', 'w', 's']", counterExamples[0].Variables["s:seq"]); } /// @@ -1455,10 +1400,9 @@ method test(c: C?) { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.True(counterExamples[0].Variables.ContainsKey("c:M.C?")); - Assert.True(counterExamples[0].Variables["c:M.C?"] is - "(c1 := '\\u1023', c2 := '\\u1023')" or - "(c2 := '\\u1023', c1 := '\\u1023')"); + Assert.Contains("c != null", counterExamples[0].Assumption); + Assert.Contains("'\\U{1023}' == c.c1", counterExamples[0].Assumption); + Assert.Contains("'\\U{1023}' == c.c2", counterExamples[0].Assumption); } /// @@ -1475,11 +1419,10 @@ public async Task NonIntegerSeqIndices(Action optionSettings) { var documentItem = CreateTestDocument(source, "NonIntegerSeqIndices.dfy"); await client.OpenDocumentAndWaitAsync(documentItem, CancellationToken); - /* First, ensure we can correctly extract counterexamples without crashing... */ var nonIntegralIndexedSeqs = (await RequestCounterExamples(documentItem.Uri)) - .SelectMany(cet => cet.Variables.ToList()) - /* Then, extract the number of non-integral indexed sequences from the repro case... */ + .Select(cet => cet.Assumption) + // Then, extract the number of non-integral indexed sequences from the repro case... .Count(IsNegativeIndexedSeq); // With more recent Z3 versions (at least 4.11+, but maybe going back farther), this situation doesn't seem @@ -1493,24 +1436,12 @@ public async Task NonIntegerSeqIndices(Action optionSettings) { } /* Helper for the NonIntegerSeqIndices test. */ - private static bool IsNegativeIndexedSeq(KeyValuePair kvp) { + private static bool IsNegativeIndexedSeq(string assumption) { var r = new Regex(@"\[\(- \d+\)\]"); - return kvp.Key.Contains("seq<_module.uint8>") && r.IsMatch(kvp.Value); - } - - [Fact] - public void TestIsNegativeIndexedSeq() { - Assert.False( - IsNegativeIndexedSeq(new KeyValuePair("uint8", "42"))); - Assert.False( - IsNegativeIndexedSeq(new KeyValuePair("seq<_module.uint8>", "(Length := 42, [0] := 42)"))); - Assert.True( - IsNegativeIndexedSeq(new KeyValuePair("seq<_module.uint8>", "(Length := 9899, [(- 1)] := 42)"))); - Assert.True( - IsNegativeIndexedSeq(new KeyValuePair("seq>", "(Length := 1123, [(- 12345)] := @12)"))); + return r.IsMatch(assumption); } - [Theory] + [Theory(Skip = "This test should be re-enabled once it is OK to use fully-qualified names everywhere")] [MemberData(nameof(OptionSettings))] public async Task TypePolymorphism(Action optionSettings) { await SetUpOptions(optionSettings); @@ -1525,10 +1456,7 @@ class C { var counterExamples = (await RequestCounterExamples(documentItem.Uri)). OrderBy(counterexample => counterexample.Position).ToArray(); Assert.Single(counterExamples); - Assert.Equal(3, counterExamples[0].Variables.Count); - Assert.True(counterExamples[0].Variables.ContainsKey("a:M.C.Equal$T")); - Assert.True(counterExamples[0].Variables.ContainsKey("b:M.C.Equal$T")); - Assert.True(counterExamples[0].Variables.ContainsKey("this:M.C")); + Assert.Fail("This test needs to be updated so that types in the counterexample can be properly resolved."); } /// diff --git a/Source/DafnyLanguageServer/Handlers/Custom/CounterExampleItem.cs b/Source/DafnyLanguageServer/Handlers/Custom/CounterExampleItem.cs index 0ccca4b0bf..d1aadd9f3b 100644 --- a/Source/DafnyLanguageServer/Handlers/Custom/CounterExampleItem.cs +++ b/Source/DafnyLanguageServer/Handlers/Custom/CounterExampleItem.cs @@ -2,5 +2,14 @@ using System.Collections.Generic; namespace Microsoft.Dafny.LanguageServer.Handlers.Custom { - public record CounterExampleItem(Position Position, IDictionary Variables); + public class CounterExampleItem { + public Position Position { get; } + + public string Assumption { get; } + + public CounterExampleItem(Position position, string assumption) { + Position = position; + Assumption = assumption; + } + } } diff --git a/Source/DafnyLanguageServer/Handlers/Custom/DafnyCounterExampleHandler.cs b/Source/DafnyLanguageServer/Handlers/Custom/DafnyCounterExampleHandler.cs index 47e882942c..20d70713a8 100644 --- a/Source/DafnyLanguageServer/Handlers/Custom/DafnyCounterExampleHandler.cs +++ b/Source/DafnyLanguageServer/Handlers/Custom/DafnyCounterExampleHandler.cs @@ -95,7 +95,9 @@ private IEnumerable GetLanguageSpecificModels(IReadOnlyList GetCounterExamples(DafnyModel model) { @@ -104,15 +106,11 @@ private IEnumerable GetCounterExamples(DafnyModel model) { .Select(GetCounterExample); } - private CounterExampleItem GetCounterExample(DafnyModelState state) { - List vars = state.ExpandedVariableSet(counterExampleDepth); + private CounterExampleItem GetCounterExample(PartialState state) { return new( new Position(state.GetLineId() - 1, state.GetCharId()), - vars.WithCancellation(cancellationToken).ToDictionary( - variable => variable.ShortName + ":" + DafnyModelTypeUtils.GetInDafnyFormat(variable.Type), - variable => variable.Value - ) - ); + state.AsAssumption().ToString() + ); } } } diff --git a/Source/DafnyServer/CounterExampleProvider.cs b/Source/DafnyServer/CounterExampleProvider.cs index 6c997c1853..9857cf7d4f 100644 --- a/Source/DafnyServer/CounterExampleProvider.cs +++ b/Source/DafnyServer/CounterExampleProvider.cs @@ -12,7 +12,6 @@ namespace DafnyServer { public sealed class CounterExampleProvider { - private const int maximumCounterexampleDepth = 5; public readonly string ModelBvd; public CounterExampleProvider() { @@ -68,14 +67,14 @@ private CounterExample ConvertModels(List specificModels) { }; AddLineInformation(counterExampleState, state.FullStateName); - var vars = state.ExpandedVariableSet(maximumCounterexampleDepth); + var vars = state.ExpandedVariableSet(); foreach (var variableNode in vars) { counterExampleState.Variables.Add(new CounterExampleVariable { - Name = variableNode.ShortName, - Value = variableNode.Value, - // CanonicalName is same as Value now but keeping this for legacy - CanonicalName = variableNode.Value + Name = "", + Value = variableNode.ToString(), + // DatatypeConstructorName is same as Value now but keeping this for legacy + CanonicalName = variableNode.ToString() }); } var index = counterExample.States.FindIndex(c => c.Column == counterExampleState.Column && c.Line == counterExampleState.Line); diff --git a/Source/DafnyTestGeneration.Test/BasicTypes.cs b/Source/DafnyTestGeneration.Test/BasicTypes.cs index 19ff2fd62b..11d80d063e 100644 --- a/Source/DafnyTestGeneration.Test/BasicTypes.cs +++ b/Source/DafnyTestGeneration.Test/BasicTypes.cs @@ -214,7 +214,7 @@ module SimpleTest { Assert.True(methods.All(m => m.ValueCreation.Count == 0)); Assert.True(methods.Exists(m => m.ArgValues[0] == "'B'")); Assert.True(methods.Exists(m => - Regex.IsMatch(m.ArgValues[0], "'[^B]'"))); + Regex.IsMatch(m.ArgValues[0], "'[^B]+'"))); } } diff --git a/Source/DafnyTestGeneration.Test/Collections.cs b/Source/DafnyTestGeneration.Test/Collections.cs index 16e86dec82..740010a0b0 100644 --- a/Source/DafnyTestGeneration.Test/Collections.cs +++ b/Source/DafnyTestGeneration.Test/Collections.cs @@ -35,6 +35,7 @@ module SimpleTest { } else if (|tseq| > 0 && tseq[0].1.1 == 'R') { return 2; } + return 3; } } ".TrimStart(); diff --git a/Source/DafnyTestGeneration/TestMethod.cs b/Source/DafnyTestGeneration/TestMethod.cs index 3dce9938cb..65fedd9fb8 100644 --- a/Source/DafnyTestGeneration/TestMethod.cs +++ b/Source/DafnyTestGeneration/TestMethod.cs @@ -9,6 +9,7 @@ using Microsoft.Boogie; using Microsoft.Dafny; using MapType = Microsoft.Dafny.MapType; +using Token = Microsoft.Dafny.Token; using Type = Microsoft.Dafny.Type; namespace DafnyTestGeneration { @@ -20,7 +21,7 @@ public class TestMethod { // list of values to mock together with their types // maps a variable that is mocked to its unique id - private readonly Dictionary mockedVarId = new(); + private readonly Dictionary mockedVarId = new(); public readonly List<(string parentId, string fieldName, string childId)> Assignments = new(); public readonly List<(string id, Type type, string value)> ValueCreation = new(); // next id to assign to a variable with given name: @@ -44,18 +45,25 @@ public class TestMethod { private List getDefaultValueParams = new(); // similar to above but for objects private readonly HashSet getClassTypeInstanceParams = new(); - private Dictionary defaultValueForType = new(); private readonly Modifications cache; + private readonly Dictionary constraintContext; + public TestMethod(DafnyInfo dafnyInfo, string log, Modifications cache) { DafnyInfo = dafnyInfo; this.cache = cache; var typeNames = ExtractPrintedInfo(log, "Types | "); var argumentNames = ExtractPrintedInfo(log, "Impl | "); dafnyModel = DafnyModel.ExtractModel(dafnyInfo.Options, log); + dafnyModel.AssignConcretePrimitiveValues(); MethodName = argumentNames.First(); argumentNames.RemoveAt(0); NOfTypeArgs = dafnyInfo.GetTypeArgs(MethodName).Count; + constraintContext = new Dictionary(); + foreach (var partialValue in dafnyModel.States.First().KnownVariableNames.Keys) { + constraintContext[partialValue] = new Microsoft.Dafny.IdentifierExpr(Token.NoToken, dafnyModel.States.First().KnownVariableNames[partialValue].First()); + constraintContext[partialValue].Type = partialValue.Type; + } ArgValues = ExtractInputs(dafnyModel.States.First(), argumentNames, typeNames); } @@ -136,9 +144,17 @@ public static string EmitSynthesizeMethods(DafnyInfo dafnyInfo, Modifications ca /// type alone /// the types of the elements /// - private List ExtractInputs(DafnyModelState state, IReadOnlyList printOutput, IReadOnlyList types) { + private List ExtractInputs(PartialState state, IReadOnlyList printOutput, IReadOnlyList types) { var result = new List(); - var vars = state.ExpandedVariableSet(-1); + var vars = state.ExpandedVariableSet(); + var constraintSet = new List(); + foreach (var variable in vars) { + foreach (var constraint in variable.Constraints) { + constraintSet.Add(constraint); + } + } + var constraints = constraintSet.ToList(); + constraints = Constraint.ResolveAndOrder(constraintContext, constraints, false, false); var parameterIndex = DafnyInfo.IsStatic(MethodName) ? -1 : -2; for (var i = 0; i < printOutput.Count; i++) { if (types[i] == "Ty") { @@ -188,8 +204,8 @@ private List ExtractInputs(DafnyModelState state, IReadOnlyList } // Returns a new value of the defaultType type (set to int by default) - private string GetADefaultTypeValue(DafnyModelVariable variable) { - return dafnyModel.GetUnreservedNumericValue(variable.Element, Type.Int); + private string GetADefaultTypeValue(PartialValue variable) { + return "0"; } private string GetFunctionOfType(ArrowType type) { @@ -219,7 +235,7 @@ private Type GetBasicType(Type start, Func stopCondition) { /// Extract the value of a variable. This can have side-effects on /// assignments, reservedValues, reservedValuesMap, and objectsToMock. /// - private string ExtractVariable(DafnyModelVariable variable, Type/*?*/ asType) { + private string ExtractVariable(PartialValue variable, Type/*?*/ asType) { if (variable == null) { if (asType != null) { return GetDefaultValue(asType); @@ -238,10 +254,6 @@ private string ExtractVariable(DafnyModelVariable variable, Type/*?*/ asType) { return mockedVarId[variable]; } - if (variable is DuplicateVariable duplicateVariable) { - return ExtractVariable(duplicateVariable.Original, asType); - } - List elements = new(); var variableType = DafnyModelTypeUtils.GetInDafnyFormat( DafnyModelTypeUtils.ReplaceTypeVariables(variable.Type, defaultType)); @@ -259,18 +271,17 @@ private string ExtractVariable(DafnyModelVariable variable, Type/*?*/ asType) { case BoolType: case CharType: case BitvectorType: - return GetPrimitiveAsType(variable.Value, asType); + return GetPrimitiveAsType(variable.PrimitiveLiteral, asType); case SeqType seqType: var asBasicSeqType = GetBasicType(asType, type => type is SeqType) as SeqType; - var seqVar = variable as SeqVariable; - if (seqVar?.GetLength() == -1) { + if (variable?.Cardinality() == -1) { if (seqType.Arg is CharType) { return "\"\""; } return AddValue(asType ?? variableType, "[]"); } - for (var i = 0; i < seqVar?.GetLength(); i++) { - var element = seqVar?[i]; + for (var i = 0; i < variable?.Cardinality(); i++) { + var element = variable?[i]; if (element == null) { getDefaultValueParams = new(); elements.Add(GetDefaultValue(seqType.Arg, asBasicSeqType?.TypeArgs?.FirstOrDefault((Type/*?*/)null))); @@ -302,18 +313,14 @@ private string ExtractVariable(DafnyModelVariable variable, Type/*?*/ asType) { return AddValue(asType ?? variableType, $"[{string.Join(", ", elements)}]"); case SetType: var asBasicSetType = GetBasicType(asType, type => type is SetType) as SetType; - if (!variable.Children.ContainsKey("true")) { - return AddValue(asType ?? variableType, "{}"); - } - foreach (var element in variable.Children["true"]) { + foreach (var element in variable.SetElements()) { elements.Add(ExtractVariable(element, asBasicSetType?.TypeArgs?.FirstOrDefault((Type/*?*/)null))); } return AddValue(asType ?? variableType, $"{{{string.Join(", ", elements)}}}"); case MapType: var asBasicMapType = GetBasicType(asType, type => type is MapType) as MapType; - var mapVar = variable as MapVariable; List mappingStrings = new(); - foreach (var mapping in mapVar?.Mappings ?? new()) { + foreach (var mapping in variable?.Mappings()) { var asTypeTypeArgs = asBasicMapType?.TypeArgs?.Count == 2 ? asBasicMapType.TypeArgs : null; mappingStrings.Add($"{ExtractVariable(mapping.Key, asTypeTypeArgs?[0])} := {ExtractVariable(mapping.Value, asTypeTypeArgs?[1])}"); @@ -321,8 +328,8 @@ private string ExtractVariable(DafnyModelVariable variable, Type/*?*/ asType) { return AddValue(asType ?? variableType, $"map[{string.Join(", ", mappingStrings)}]"); case UserDefinedType tupleType when tupleType.Name.StartsWith("_tuple#"): return AddValue(asType ?? variableType, "(" + - string.Join(",", variable.Children.Values - .Select(v => ExtractVariable(v.First(), null))) + ")"); + string.Join(",", variable.UnnamedDestructors() + .Select(v => ExtractVariable(v, null))) + ")"); case ArrowType arrowType: var asBasicArrowType = GetBasicType(asType, type => type is ArrowType) as ArrowType; return GetFunctionOfType(asBasicArrowType ?? arrowType); @@ -335,7 +342,7 @@ private string ExtractVariable(DafnyModelVariable variable, Type/*?*/ asType) { case UserDefinedType arrType when new Regex("^_System.array[0-9]*\\?$").IsMatch(arrType.Name): errorMessages.Add($"// Failed because arrays are not yet supported (type {arrType} element {variable.Element})"); break; - case UserDefinedType _ when variable.CanonicalName() == "null": + case UserDefinedType _ when variable.PrimitiveLiteral != "": return "null"; case UserDefinedType userDefinedType: var basicType = GetBasicType(asType ?? userDefinedType, @@ -346,28 +353,28 @@ private string ExtractVariable(DafnyModelVariable variable, Type/*?*/ asType) { return GetClassTypeInstance(userDefinedType, asType, variable); } - if (variable.CanonicalName() == "") { + if (variable.DatatypeConstructorName() == "") { getDefaultValueParams = new(); return GetDefaultValue(userDefinedType, asType); } - var ctor = DafnyInfo.Datatypes[basicType.Name].Ctors.FirstOrDefault(ctor => ctor.Name == variable.CanonicalName(), null); + var ctor = DafnyInfo.Datatypes[basicType.Name].Ctors.FirstOrDefault(ctor => ctor.Name == variable.DatatypeConstructorName(), null); if (ctor == null) { - errorMessages.Add($"// Failed: Cannot find constructor {variable.CanonicalName()} for datatype {basicType}"); + errorMessages.Add($"// Failed: Cannot find constructor {variable.DatatypeConstructorName()} for datatype {basicType}"); return basicType.ToString(); } List fields = new(); for (int i = 0; i < ctor.Destructors.Count; i++) { var fieldName = ctor.Destructors[i].Name; - if (!variable.Children.ContainsKey(fieldName)) { + if (!variable.Fields().ContainsKey(fieldName)) { fieldName = $"[{i}]"; } - if (!variable.Children.ContainsKey(fieldName)) { + if (!variable.Fields().ContainsKey(fieldName)) { errorMessages.Add($"// Failed: Cannot find destructor " + $"{ctor.Destructors[i].Name} of constructor " + - $"{variable.CanonicalName()} for datatype " + + $"{variable.DatatypeConstructorName()} for datatype " + $"{basicType}. Available destructors are: " + - string.Join(",", variable.Children.Keys.ToList())); + string.Join(",", variable.Fields().Keys.ToList())); return basicType.ToString(); } @@ -375,18 +382,18 @@ private string ExtractVariable(DafnyModelVariable variable, Type/*?*/ asType) { Utils.UseFullName(ctor.Destructors[i].Type), ctor.EnclosingDatatype.TypeArgs.ConvertAll(arg => arg.Name), basicType.TypeArgs); if (ctor.Destructors[i].Name.StartsWith("#")) { - fields.Add(ExtractVariable(variable.Children[fieldName].First(), destructorType)); + fields.Add(ExtractVariable(variable.Fields()[fieldName], destructorType)); } else { fields.Add(ctor.Destructors[i].Name + ":=" + - ExtractVariable(variable.Children[fieldName].First(), destructorType)); + ExtractVariable(variable.Fields()[fieldName], destructorType)); } } var value = basicType.ToString(); if (fields.Count == 0) { - value += "." + variable.CanonicalName(); + value += "." + variable.DatatypeConstructorName(); } else { - value += "." + variable.CanonicalName() + "(" + + value += "." + variable.DatatypeConstructorName() + "(" + string.Join(",", fields) + ")"; } return AddValue(asType ?? userDefinedType, value); @@ -395,7 +402,7 @@ private string ExtractVariable(DafnyModelVariable variable, Type/*?*/ asType) { return "null"; } - private string GetClassTypeInstance(UserDefinedType type, Type/*?*/ asType, DafnyModelVariable/*?*/ variable) { + private string GetClassTypeInstance(UserDefinedType type, Type/*?*/ asType, PartialValue/*?*/ variable) { var asBasicType = GetBasicType(asType, _ => false); if ((asBasicType != null) && (asBasicType is not UserDefinedType)) { return GetDefaultValue(asType, asType); @@ -454,13 +461,12 @@ private string GetClassTypeInstance(UserDefinedType type, Type/*?*/ asType, Dafn return varId; } - private string GetFieldValue((string name, Type type, bool mutable, string/*?*/ defValue) field, DafnyModelVariable/*?*/ variable) { + private string GetFieldValue((string name, Type type, bool mutable, string/*?*/ defValue) field, PartialValue/*?*/ variable) { if (field.defValue != null) { return field.defValue; } - if (variable != null && variable.Children.ContainsKey(field.name) && - variable.Children[field.name].Count == 1) { - return ExtractVariable(variable.Children[field.name].First(), field.type); + if (variable != null && variable.Fields().ContainsKey(field.name)) { + return ExtractVariable(variable.Fields()[field.name], field.type); } var previouslyCreated = ValueCreation.FirstOrDefault(obj => diff --git a/Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-2026.dfy.expect b/Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-2026.dfy.expect index 6e0c90512a..cad8bc1509 100644 --- a/Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-2026.dfy.expect +++ b/Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-2026.dfy.expect @@ -1,32 +1,21 @@ git-issue-2026.dfy(18,18): Error: this invariant could not be proved to be maintained by the loop Related message: loop invariant violation Related counterexample: + WARNING: the following counterexample may be inconsistent or invalid. See dafny.org/dafny/DafnyRef/DafnyRef#sec-counterexamples + Temporary variables to describe counterexamples: + ghost var counterexampleLoopGuard0 : bool := false; git-issue-2026.dfy(12,0): initial state: - n : int = 2 - ret : ? = ? + assume 2 == n; git-issue-2026.dfy(13,24): - n : int = 2 - ret : _System.array?> = (Length := 2, [0] := @0) - @0 : seq = ['o', 'd', 'd'] + assume ret != null && ret.Length > 0 && 2 == n && 2 == ret.Length && ['o', 'd', 'd'] == ret[0]; git-issue-2026.dfy(15,14): - n : int = 2 - ret : _System.array?> = (Length := 2, [0] := @0) - i : int = 0 - @0 : seq = ['o', 'd', 'd'] + assume ret != null && ret.Length > 0 && 2 == n && 2 == ret.Length && ['o', 'd', 'd'] == ret[0] && 0 == i; git-issue-2026.dfy(16,4): after some loop iterations: - n : int = 2 - ret : _System.array?> = (Length := 2) - i : int = 0 + counterexampleLoopGuard0 := ret != null && 2 == n && 2 == ret.Length && 0 == i; git-issue-2026.dfy(22,27): - n : int = 2 - ret : _System.array?> = (Length := 2, [0] := @0) - i : int = 0 - @0 : seq = ['o', 'd', 'd'] + assume counterexampleLoopGuard0 ==> ret != null && ret.Length > 0 && 2 == n && 2 == ret.Length && ['o', 'd', 'd'] == ret[0] && 0 == i; git-issue-2026.dfy(26,18): - n : int = 2 - ret : _System.array?> = (Length := 2, [0] := @0) - i : int = 1 - @0 : seq = ['o', 'd', 'd'] + assume counterexampleLoopGuard0 ==> ret != null && ret.Length > 0 && 2 == n && 2 == ret.Length && ['o', 'd', 'd'] == ret[0] && 1 == i; Dafny program verifier finished with 0 verified, 1 error diff --git a/Source/IntegrationTests/TestFiles/LitTests/LitTest/server/counterexample.transcript b/Source/IntegrationTests/TestFiles/LitTests/LitTest/server/counterexample.transcript deleted file mode 100644 index d6fea106fd..0000000000 --- a/Source/IntegrationTests/TestFiles/LitTests/LitTest/server/counterexample.transcript +++ /dev/null @@ -1,6 +0,0 @@ -# RUN: %server "%s" > "%t" -# RUN: %diff "%s.expect" "%t" - -counterexample -eyJhcmdzIjpbXSwiZmlsZW5hbWUiOiJhYnMuZGZ5Iiwic291cmNlIjoibWV0aG9kIEFicyh4OiBpbnQpIFxyXG4gICAgcmV0dXJucyAoeTogaW50KVxyXG5lbnN1cmVzIHkgPj0gMCB7XHJcbiAgICByZXR1cm4geDtcclxufSIsInNvdXJjZUlzRmlsZSI6ZmFsc2V9 -[[DAFNY-CLIENT: EOM]] diff --git a/Source/IntegrationTests/TestFiles/LitTests/LitTest/server/counterexample.transcript.expect b/Source/IntegrationTests/TestFiles/LitTests/LitTest/server/counterexample.transcript.expect deleted file mode 100644 index 9f68c5dfd2..0000000000 --- a/Source/IntegrationTests/TestFiles/LitTests/LitTest/server/counterexample.transcript.expect +++ /dev/null @@ -1,9 +0,0 @@ -# Reading from counterexample.transcript - -Verifying Abs (correctness) ... - [1 proof obligation] error -abs.dfy(4,4): Error: a postcondition could not be proved on this return path -abs.dfy(3,10): Related location: this is the postcondition that could not be proved -COUNTEREXAMPLE_START {"States":[{"Column":0,"Line":0,"Name":"","Variables":[{"CanonicalName":"-1","Name":"x","RealName":null,"Value":"-1"}]},{"Column":15,"Line":3,"Name":"abs.dfy(3,15): initial state","Variables":[{"CanonicalName":"-1","Name":"x","RealName":null,"Value":"-1"}]},{"Column":12,"Line":4,"Name":"abs.dfy(4,12)","Variables":[{"CanonicalName":"-1","Name":"x","RealName":null,"Value":"-1"},{"CanonicalName":"-1","Name":"y","RealName":null,"Value":"-1"}]}]} COUNTEREXAMPLE_END -Verification completed successfully! -[SUCCESS] [[DAFNY-SERVER: EOM]] diff --git a/Source/IntegrationTests/TestFiles/LitTests/LitTest/server/counterexample_commandline.dfy b/Source/IntegrationTests/TestFiles/LitTests/LitTest/server/counterexample_commandline.dfy index 71c45e8bb7..d23397890f 100644 --- a/Source/IntegrationTests/TestFiles/LitTests/LitTest/server/counterexample_commandline.dfy +++ b/Source/IntegrationTests/TestFiles/LitTests/LitTest/server/counterexample_commandline.dfy @@ -19,6 +19,7 @@ module Patterns { s[n] == p[n] || p[n] == '?' { + b := false; var i := 0; while (i < |s|) invariant i <= |s| @@ -27,7 +28,7 @@ module Patterns { p[n] == '?' { if (s[i] != p[i]) { // must add && (p[i] != '?') to verify - return false; + return; } i := i + 1; } diff --git a/Source/IntegrationTests/TestFiles/LitTests/LitTest/server/counterexample_commandline.dfy.expect b/Source/IntegrationTests/TestFiles/LitTests/LitTest/server/counterexample_commandline.dfy.expect index eb9c2fad07..1a0deba5df 100644 --- a/Source/IntegrationTests/TestFiles/LitTests/LitTest/server/counterexample_commandline.dfy.expect +++ b/Source/IntegrationTests/TestFiles/LitTests/LitTest/server/counterexample_commandline.dfy.expect @@ -1,25 +1,16 @@ -counterexample_commandline.dfy(30,20): Error: a postcondition could not be proved on this return path +counterexample_commandline.dfy(31,20): Error: a postcondition could not be proved on this return path Related counterexample: + WARNING: the following counterexample may be inconsistent or invalid. See dafny.org/dafny/DafnyRef/DafnyRef#sec-counterexamples + Temporary variables to describe counterexamples: + ghost var counterexampleLoopGuard0 : bool := false; counterexample_commandline.dfy(21,8): initial state: - this : Patterns.Simple = (p := @0) - s : seq = ['A'] - @0 : seq = ['?'] + assume this != null && |this.p| > 0 && |s| > 0 && 1 == |s| && '\U{0002}' == s[0] && 1 == |this.p| && '?' == this.p[0]; counterexample_commandline.dfy(22,22): - this : Patterns.Simple = (p := @0) - s : seq = ['A'] - i : int = 0 - @0 : seq = ['?'] - counterexample_commandline.dfy(23,12): after some loop iterations: - this : Patterns.Simple = (p := @0) - s : seq = ['A'] - i : int = 0 - @0 : seq = ['?'] - counterexample_commandline.dfy(30,32): - this : Patterns.Simple = (p := @0) - s : seq = ['A'] - i : int = 0 - b : bool = false - @0 : seq = ['?'] + assume this != null && |this.p| > 0 && |s| > 0 && 1 == |s| && '\U{0002}' == s[0] && false == b && 1 == |this.p| && '?' == this.p[0]; + counterexample_commandline.dfy(23,22): + assume this != null && |this.p| > 0 && |s| > 0 && 1 == |s| && '\U{0002}' == s[0] && false == b && 0 == i && '?' == this.p[0] && 1 == |this.p|; + counterexample_commandline.dfy(24,12): after some loop iterations: + counterexampleLoopGuard0 := this != null && |this.p| > 0 && |s| > 0 && 1 == |s| && '\U{0002}' == s[0] && false == b && 0 == i && '?' == this.p[0] && 1 == |this.p|; counterexample_commandline.dfy(18,22): Related location: this is the postcondition that could not be proved diff --git a/Source/IntegrationTests/TestFiles/LitTests/LitTest/server/git-issue223.transcript b/Source/IntegrationTests/TestFiles/LitTests/LitTest/server/git-issue223.transcript deleted file mode 100644 index 7bec0961f2..0000000000 --- a/Source/IntegrationTests/TestFiles/LitTests/LitTest/server/git-issue223.transcript +++ /dev/null @@ -1,9 +0,0 @@ -# RUN: %server "%s" > "%t" -# RUN: %diff "%s.expect" "%t" - -counterexample -eyJhcmdzIjpbXSwiZmlsZW5hbWUiOiJhYnMuZGZ5Iiwic291cmNlIjoibWV0aG9kIEFicyh4OiBpbnQpIFxyXG4gICAgcmV0dXJucyAoeTogaW50KVxyXG5lbnN1cmVzIHkgPj0gMCB7XHJcbiAgICByZXR1cm4geDtcclxufSIsInNvdXJjZUlzRmlsZSI6ZmFsc2V9 -[[DAFNY-CLIENT: EOM]] -counterexample -eyJhcmdzIjpbXSwiZmlsZW5hbWUiOiJhYnMuZGZ5Iiwic291cmNlIjoibWV0aG9kIEFicyh4OiBpbnQpIFxyXG4gICAgcmV0dXJucyAoeTogaW50KVxyXG5lbnN1cmVzIHkgPj0gMCB7XHJcbiAgICByZXR1cm4geDtcclxufSIsInNvdXJjZUlzRmlsZSI6ZmFsc2V9 -[[DAFNY-CLIENT: EOM]] diff --git a/Source/IntegrationTests/TestFiles/LitTests/LitTest/server/git-issue223.transcript.expect b/Source/IntegrationTests/TestFiles/LitTests/LitTest/server/git-issue223.transcript.expect deleted file mode 100644 index ceb6bdf079..0000000000 --- a/Source/IntegrationTests/TestFiles/LitTests/LitTest/server/git-issue223.transcript.expect +++ /dev/null @@ -1,17 +0,0 @@ -# Reading from git-issue223.transcript - -Verifying Abs (correctness) ... - [1 proof obligation] error -abs.dfy(4,4): Error: a postcondition could not be proved on this return path -abs.dfy(3,10): Related location: this is the postcondition that could not be proved -COUNTEREXAMPLE_START {"States":[{"Column":0,"Line":0,"Name":"","Variables":[{"CanonicalName":"-1","Name":"x","RealName":null,"Value":"-1"}]},{"Column":15,"Line":3,"Name":"abs.dfy(3,15): initial state","Variables":[{"CanonicalName":"-1","Name":"x","RealName":null,"Value":"-1"}]},{"Column":12,"Line":4,"Name":"abs.dfy(4,12)","Variables":[{"CanonicalName":"-1","Name":"x","RealName":null,"Value":"-1"},{"CanonicalName":"-1","Name":"y","RealName":null,"Value":"-1"}]}]} COUNTEREXAMPLE_END -Verification completed successfully! -[SUCCESS] [[DAFNY-SERVER: EOM]] - -Verifying Abs (correctness) ... - [0 proof obligations] error -abs.dfy(4,4): Error: a postcondition could not be proved on this return path -abs.dfy(3,10): Related location: this is the postcondition that could not be proved -COUNTEREXAMPLE_START {"States":[{"Column":0,"Line":0,"Name":"","Variables":[{"CanonicalName":"-1","Name":"x","RealName":null,"Value":"-1"}]},{"Column":15,"Line":3,"Name":"abs.dfy(3,15): initial state","Variables":[{"CanonicalName":"-1","Name":"x","RealName":null,"Value":"-1"}]},{"Column":12,"Line":4,"Name":"abs.dfy(4,12)","Variables":[{"CanonicalName":"-1","Name":"x","RealName":null,"Value":"-1"},{"CanonicalName":"-1","Name":"y","RealName":null,"Value":"-1"}]}]} COUNTEREXAMPLE_END -Verification completed successfully! -[SUCCESS] [[DAFNY-SERVER: EOM]] diff --git a/docs/DafnyRef/UserGuide.md b/docs/DafnyRef/UserGuide.md index 6e77931229..6ad372623c 100644 --- a/docs/DafnyRef/UserGuide.md +++ b/docs/DafnyRef/UserGuide.md @@ -1013,6 +1013,13 @@ This list is not exhaustive but can definitely be useful to provide the next ste `method m_mod(i) returns (j: T)`
  ` requires A(i)`
  ` modifies this, i`
  ` ensures B(i, j)`
`{`
  ` ...`
`}`

`method n_mod() {`
  ` ...`




  ` var x := m_mod(a);`
  ` assert P(x);` | `method m_mod(i) returns (j: T)`
  ` requires A(i)`
  ` modifies this, i`
  ` ensures B(i, j)`
`{`
  ` ...`
`}`

`method n_mod() {`
  ` ...`
  ` assert A(k);`
  ` modify this, i; // Temporarily`
  ` var x: T; // Temporarily`
  ` assume B(k, x);`
  `// var x := m_mod(k);`
  ` assert P(x);`
`modify x, y;`
`assert P(x, y, z);` | `assert x != z && y != z;`
`modify x, y;`
`assert P(x, y, z);` +#### 13.7.1.4. Counterexamples {#sec-counterexamples} + +When verification fails, we can rerun Dafny with `--extract-counterexample` flag to get a counterexample that can potentially explain the proof failure. +Note that Danfy cannot guarantee that the counterexample it reports provably violates the assertion it was generated for (see [^smt-encoding]) +The counterexample takes the form of assumptions that can be inserted into the code to describe the potential conditions under which the given assertion is violated. +This output should be inspected manually and treated as a hint. + ### 13.7.2. Verification debugging when verification is slow {#sec-verification-debugging-slow} In this section, we describe techniques to apply in the case when verification is slower than expected, does not terminate, or times out. @@ -2316,6 +2323,12 @@ and what information it produces about the verification process. * `--isolate-assertions` - verify assertions individually +* `--extract-counterexample` - if verification fails, report a potential + counterexample as a set of assumptions that can be inserted into the code. + Note that Danfy cannot guarantee that the counterexample + it reports provably violates the assertion or that the assumptions are not + mutually inconsistent (see [^smt-encoding]), so this output should be inspected manually and treated as a hint. + Controlling the proof engine: * `--cores:` - sets the number or percent of the available cores to be used for verification @@ -2469,13 +2482,6 @@ Legacy options: * `1` (default) - in the body of prefix lemmas, rewrite any use of a focal predicate `P` to `P#[_k-1]`. -* `-extractCounterexample` - control generation of counterexamples. If - verification fails, report a detailed counterexample for the first - failing assertion. Requires specifying the `-mv` option, to specify - where to write the counterexample, as well as the - `-proverOpt:O:model_compress=false` and - `-proverOpt:O:model.completion=true` options. - ### 13.9.8. Controlling compilation {#sec-controlling-compilation} These options control what code gets compiled, what target language is