Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: Implement frame-related asserted exprs and TraitDecreases #5542

Merged
merged 10 commits into from
Jun 7, 2024
Prev Previous commit
Next Next commit
implement ReadFrameSubset asserted expr
  • Loading branch information
alex-chew committed Jun 7, 2024
commit b7ebc001d587c6d1038f5871c250ea4373c85db8
6 changes: 6 additions & 0 deletions Source/DafnyCore/AST/Members/MethodOrFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,10 @@ public abstract class MethodOrFunction : MemberDecl {

protected MethodOrFunction(RangeToken tok, Name name, bool hasStaticKeyword, bool isGhost, Attributes attributes, bool isRefining) : base(tok, name, hasStaticKeyword, isGhost, attributes, isRefining) {
}

public Specification<FrameExpression> Reads => this switch {
Method m => m.Reads,
Function f => f.Reads,
_ => throw new cce.UnreachableException()
};
}
129 changes: 101 additions & 28 deletions Source/DafnyCore/Verifier/BoogieGenerator.ExpressionWellformed.cs
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,8 @@ private record ReadsCheckDelayer(ExpressionTranslator etran, Function selfCallsA
// Turn off LValueContext for any recursive call
wfOptions = wfOptions.WithLValueContext(false);
}

var readFrames = (codeContext as MethodOrFunction)?.Reads?.Expressions ?? new();

switch (expr) {
case StaticReceiverExpr stexpr: {
Expand Down Expand Up @@ -374,8 +376,10 @@ private record ReadsCheckDelayer(ExpressionTranslator etran, Function selfCallsA
}
}
if (!origOptions.LValueContext && wfOptions.DoReadsChecks && e.Member is Field { IsMutable: true } f) {
var requiredFrame = new FrameExpression(Token.NoToken, e.Obj, f.Name);
var desc = new PODesc.ReadFrameSubset("read field", requiredFrame, readFrames, selectExpr, etran.scope);
wfOptions.AssertSink(this, builder)(selectExpr.tok, Bpl.Expr.SelectTok(selectExpr.tok, etran.ReadsFrame(selectExpr.tok), etran.TrExpr(e.Obj), GetField(e)),
new PODesc.ReadFrameSubset("read field", selectExpr, etran.scope), wfOptions.AssertKv);
desc, wfOptions.AssertKv);
}

break;
Expand Down Expand Up @@ -429,8 +433,10 @@ private record ReadsCheckDelayer(ExpressionTranslator etran, Function selfCallsA
var i = etran.TrExpr(e.E0);
i = ConvertExpression(selectExpr.tok, i, e.E0.Type, Type.Int);
Bpl.Expr fieldName = FunctionCall(selectExpr.tok, BuiltinFunction.IndexField, null, i);
var requiredFrame = new FrameExpression(Token.NoToken, e.Seq, null);
var desc = new PODesc.ReadFrameSubset("read array element", requiredFrame, readFrames, e, etran.scope);
wfOptions.AssertSink(this, builder)(selectExpr.tok, Bpl.Expr.SelectTok(selectExpr.tok, etran.ReadsFrame(selectExpr.tok), seq, fieldName),
new PODesc.ReadFrameSubset("read array element", e, etran.scope), wfOptions.AssertKv);
desc, wfOptions.AssertKv);
} else {
Bpl.Expr lowerBound = e.E0 == null ? Bpl.Expr.Literal(0) : etran.TrExpr(e.E0);
Contract.Assert(eSeqType.AsArrayType.Dims == 1);
Expand All @@ -443,9 +449,9 @@ private record ReadsCheckDelayer(ExpressionTranslator etran, Function selfCallsA
var allowedToRead = Bpl.Expr.SelectTok(e.tok, etran.ReadsFrame(e.tok), seq, fieldName);
var trigger = BplTrigger(allowedToRead); // Note, the assertion we're about to produce only seems useful in the check-only mode (that is, with subsumption 0), but if it were to be assumed, we'll use this entire RHS as the trigger
var qq = new Bpl.ForallExpr(e.tok, new List<Variable> { iVar }, trigger, BplImp(range, allowedToRead));
wfOptions.AssertSink(this, builder)(selectExpr.tok, qq,
new PODesc.ReadFrameSubset("read the indicated range of array elements", e, etran.scope),
wfOptions.AssertKv);
var requiredFrame = new FrameExpression(Token.NoToken, e.Seq, null);
var desc = new PODesc.ReadFrameSubset("read the indicated range of array elements", requiredFrame, readFrames, e, etran.scope);
wfOptions.AssertSink(this, builder)(selectExpr.tok, qq, desc, wfOptions.AssertKv);
}
}

Expand Down Expand Up @@ -478,8 +484,10 @@ private record ReadsCheckDelayer(ExpressionTranslator etran, Function selfCallsA
}
if (wfOptions.DoReadsChecks) {
Bpl.Expr fieldName = etran.GetArrayIndexFieldName(e.tok, e.Indices);
var requiredFrame = new FrameExpression(Token.NoToken, e.Array, null);
var desc = new PODesc.ReadFrameSubset("read array element", requiredFrame, readFrames, selectExpr, etran.scope);
wfOptions.AssertSink(this, builder)(selectExpr.tok, Bpl.Expr.SelectTok(selectExpr.tok, etran.ReadsFrame(selectExpr.tok), array, fieldName),
new PODesc.ReadFrameSubset("read array element", selectExpr, etran.scope), wfOptions.AssertKv);
desc, wfOptions.AssertKv);
}

break;
Expand Down Expand Up @@ -586,18 +594,19 @@ private record ReadsCheckDelayer(ExpressionTranslator etran, Function selfCallsA
fnCoreType = fnCore.Type;
}

// unwrap renamed local lambdas
var unwrappedFunc = e.Function;
while (unwrappedFunc is ConcreteSyntaxExpression { ResolvedExpression: not null } cse) {
unwrappedFunc = cse.ResolvedExpression;
}
if (unwrappedFunc is IdentifierExpr { tok: var tok, DafnyName: var name }) {
unwrappedFunc = new IdentifierExpr(tok, name);
}

if (!fnCoreType.IsArrowTypeWithoutPreconditions) {
// unwrap renamed local lambdas
var funcExpr = e.Function;
while (funcExpr is ConcreteSyntaxExpression { ResolvedExpression: not null } cse) {
funcExpr = cse.ResolvedExpression;
}
if (funcExpr is IdentifierExpr ie) {
funcExpr = new IdentifierExpr(ie.tok, ie.DafnyName);
}
var dPrecond = new ApplyExpr(
Token.NoToken,
new ExprDotName(Token.NoToken, funcExpr, "requires", null),
new ExprDotName(Token.NoToken, unwrappedFunc, "requires", null),
e.Args,
Token.NoToken);

Expand All @@ -612,9 +621,20 @@ private record ReadsCheckDelayer(ExpressionTranslator etran, Function selfCallsA
Expression wrap = new BoogieWrapper(
FunctionCall(e.tok, Reads(arity), TrType(objset), args),
objset);
var reads = new FrameExpression(e.tok, wrap, null);
CheckFrameSubset(applyExpr.tok, new List<FrameExpression> { reads }, null, null,
etran, etran.ReadsFrame(applyExpr.tok), wfOptions.AssertSink(this, builder), new PODesc.ReadFrameSubset("invoke function"), wfOptions.AssertKv);
var wrappedReads = new FrameExpression(e.tok, wrap, null);

var readsCall = new ApplyExpr(
Token.NoToken,
new ExprDotName(Token.NoToken, unwrappedFunc, "reads", null),
e.Args,
Token.NoToken
);
readsCall.Type = objset;
var requiredFrame = new FrameExpression(Token.NoToken, readsCall, null);
var desc = new PODesc.ReadFrameSubset("invoke function", requiredFrame, readFrames);

CheckFrameSubset(applyExpr.tok, new List<FrameExpression> { wrappedReads }, null, null,
etran, etran.ReadsFrame(applyExpr.tok), wfOptions.AssertSink(this, builder), desc, wfOptions.AssertKv);
}

break;
Expand Down Expand Up @@ -753,8 +773,28 @@ private record ReadsCheckDelayer(ExpressionTranslator etran, Function selfCallsA
FunctionCall(expr.tok, Reads(e.Args.Count()), TrType(objset), arguments),
objset);
var reads = new FrameExpression(expr.tok, wrap, null);
List<FrameExpression> requiredFrames;
switch (e.Receiver.Resolved) {
case MemberSelectExpr { Member: MethodOrFunction readsReceiver }: {
var receiverReplacement = readsReceiver.IsStatic
? null
: new ImplicitThisExpr(Token.NoToken);
var receiverSubstMap = readsReceiver.Ins.Zip(e.Args)
.ToDictionary(fa => fa.First as IVariable, fa => fa.Second);
var subst = new Substituter(receiverReplacement, receiverSubstMap, e.GetTypeArgumentSubstitutions());
requiredFrames = (readsReceiver! switch {
Method m => m.Reads,
Function f => f.Reads,
_ => throw new cce.UnreachableException()
}).Expressions.ConvertAll(subst.SubstFrameExpr);
break;
}
default:
throw new cce.UnreachableException();
}
var desc = new PODesc.ReadFrameSubset("invoke function", requiredFrames, readFrames);
CheckFrameSubset(expr.tok, new List<FrameExpression> { reads }, null, null,
etran, etran.ReadsFrame(expr.tok), wfOptions.AssertSink(this, builder), new PODesc.ReadFrameSubset("invoke function"), wfOptions.AssertKv);
etran, etran.ReadsFrame(expr.tok), wfOptions.AssertSink(this, builder), desc, wfOptions.AssertKv);
}

} else {
Expand Down Expand Up @@ -786,10 +826,16 @@ private record ReadsCheckDelayer(ExpressionTranslator etran, Function selfCallsA
}
if (wfOptions.DoReadsChecks) {
// check that the callee reads only what the caller is already allowed to read
var s = new Substituter(null, new Dictionary<IVariable, Expression>(), e.GetTypeArgumentSubstitutions());

// substitute actual args for parameter is frames for the description expression...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not critical, but this comment isn't super clear to me. Maybe it could be reworded?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, there was a typo (and generally unclear wording). I've fixed this in the latest commit.

var requiredFrames = e.Function.Reads.Expressions.ConvertAll(directSub.SubstFrameExpr);
var desc = new PODesc.ReadFrameSubset("invoke function", requiredFrames, readFrames);

// ... but no need to do so for frames passed to CheckFrameSubset
var readsSubst = new Substituter(null, new Dictionary<IVariable, Expression>(), e.GetTypeArgumentSubstitutions());
CheckFrameSubset(callExpr.tok,
e.Function.Reads.Expressions.ConvertAll(s.SubstFrameExpr),
e.Receiver, substMap, etran, etran.ReadsFrame(callExpr.tok), wfOptions.AssertSink(this, builder), new PODesc.ReadFrameSubset("invoke function"), wfOptions.AssertKv);
e.Function.Reads.Expressions.ConvertAll(readsSubst.SubstFrameExpr),
e.Receiver, substMap, etran, etran.ReadsFrame(callExpr.tok), wfOptions.AssertSink(this, builder), desc, wfOptions.AssertKv);
}
}
Expression allowance = null;
Expand Down Expand Up @@ -889,6 +935,7 @@ private record ReadsCheckDelayer(ExpressionTranslator etran, Function selfCallsA
}
case UnchangedExpr unchangedExpr: {
var e = unchangedExpr;
var contextReadsFrames = GetContextReadsFrames();
foreach (var fe in e.Frame) {
CheckWellformed(fe.E, wfOptions, locals, builder, etran);

Expand All @@ -910,10 +957,11 @@ private record ReadsCheckDelayer(ExpressionTranslator etran, Function selfCallsA
}
// check that the 'unchanged' argument reads only what the context is allowed to read
if (wfOptions.DoReadsChecks) {
var desc = new PODesc.ReadFrameSubset($"read state of 'unchanged' {description}", fe, contextReadsFrames);
CheckFrameSubset(fe.E.tok,
new List<FrameExpression>() { fe },
null, new Dictionary<IVariable, Expression>(), etran, etran.ReadsFrame(fe.E.tok), wfOptions.AssertSink(this, builder),
new PODesc.ReadFrameSubset($"read state of 'unchanged' {description}"), wfOptions.AssertKv);
desc, wfOptions.AssertKv);
}
}

Expand Down Expand Up @@ -1567,9 +1615,17 @@ private record ReadsCheckDelayer(ExpressionTranslator etran, Function selfCallsA
// check precond
var pre = FunctionCall(tok, Requires(dims.Count), Bpl.Type.Bool, args);
var q = new Bpl.ForallExpr(tok, bvs, BplImp(ante, pre));
var desc = new PODesc.IndicesInDomain(forArray ? "array" : "sequence", dims, init);
builder.Add(AssertNS(tok, q, desc));
var indicesDesc = new PODesc.IndicesInDomain(forArray ? "array" : "sequence", dims, init);
builder.Add(AssertNS(tok, q, indicesDesc));
if (!forArray && options.DoReadsChecks) {
// unwrap renamed local lambdas
var unwrappedFunc = init;
while (unwrappedFunc is ConcreteSyntaxExpression { ResolvedExpression: not null } cse) {
unwrappedFunc = cse.ResolvedExpression;
}
if (unwrappedFunc is IdentifierExpr { tok: var initTok, DafnyName: var name }) {
unwrappedFunc = new IdentifierExpr(initTok, name);
}
// check read effects
Type objset = program.SystemModuleManager.ObjectSetType();
Expression wrap = new BoogieWrapper(
Expand All @@ -1580,10 +1636,27 @@ private record ReadsCheckDelayer(ExpressionTranslator etran, Function selfCallsA
var qe = new Bpl.ForallExpr(t, bvs, BplImp(ante, e));
options.AssertSink(this, builder)(t, qe, d, qk);
};

PODesc.Utils.MakeQuantifierVarsForDims(dims, out var indexVars, out var indexVarExprs, out var indicesRange);
var readsCall = new ApplyExpr(
Token.NoToken,
new ExprDotName(Token.NoToken, unwrappedFunc, "reads", null),
indexVarExprs,
Token.NoToken
);
readsCall.Type = objset;
var contextReads = GetContextReadsFrames();
var readsDescExpr = new ForallExpr(
Token.NoToken,
RangeToken.NoToken,
indexVars,
indicesRange,
PODesc.Utils.MakeDafnyFrameCheck(contextReads, readsCall, null),
null
);
var readsDesc = new PODesc.ReadFrameSubset("invoke the function passed as an argument to the sequence constructor", readsDescExpr);
CheckFrameSubset(tok, new List<FrameExpression> { reads }, null, null,
etran, etran.ReadsFrame(tok), maker,
new PODesc.ReadFrameSubset("invoke the function passed as an argument to the sequence constructor"),
options.AssertKv);
etran, etran.ReadsFrame(tok), maker, readsDesc, options.AssertKv);
}
// Check that the values coming out of the function satisfy any appropriate subset-type constraints
var apply = UnboxUnlessInherentlyBoxed(FunctionCall(tok, Apply(dims.Count), TrType(elementType), args), elementType);
Expand Down
31 changes: 21 additions & 10 deletions Source/DafnyCore/Verifier/BoogieGenerator.TrStatement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -351,8 +351,7 @@ public partial class BoogieGenerator {
// check well-formedness of the modifies clauses
CheckFrameWellFormed(new WFOptions(), s.Mod.Expressions, locals, builder, etran);
// check that the modifies is a subset
var contextModFrames = (codeContext as IMethodCodeContext)?.Modifies.Expressions ?? new();
var desc = new PODesc.ModifyFrameSubset("modify statement", s.Mod.Expressions, contextModFrames);
var desc = new PODesc.ModifyFrameSubset("modify statement", s.Mod.Expressions, GetContextModifiesFrames());
CheckFrameSubset(s.Tok, s.Mod.Expressions, null, null, etran, etran.ModifiesFrame(s.Tok), builder, desc, null);
// cause the change of the heap according to the given frame
var suffix = CurrentIdGenerator.FreshId("modify#");
Expand Down Expand Up @@ -1289,8 +1288,7 @@ public partial class BoogieGenerator {
MultiSelectExpr e => (e.Array, null),
_ => throw new cce.UnreachableException()
};
var contextModFrames = (codeContext as IMethodCodeContext)?.Modifies.Expressions ?? new();
var desc = new PODesc.Modifiable(description, contextModFrames, lhsObj, lhsField);
var desc = new PODesc.Modifiable(description, GetContextModifiesFrames(), lhsObj, lhsField);
definedness.Add(Assert(lhs.tok, Bpl.Expr.SelectTok(lhs.tok, etran.ModifiesFrame(lhs.tok), obj, F),
desc));
if (s0.Rhs is ExprRhs) {
Expand Down Expand Up @@ -1471,8 +1469,7 @@ public partial class BoogieGenerator {

if (s.Mod.Expressions != null) { // check well-formedness and that the modifies is a subset
CheckFrameWellFormed(new WFOptions(), s.Mod.Expressions, locals, builder, etran);
var contextModFrames = (codeContext as IMethodCodeContext)?.Modifies.Expressions ?? new();
var desc = new PODesc.ModifyFrameSubset("loop modifies clause", s.Mod.Expressions, contextModFrames);
var desc = new PODesc.ModifyFrameSubset("loop modifies clause", s.Mod.Expressions, GetContextModifiesFrames());
CheckFrameSubset(s.Tok, s.Mod.Expressions, null, null, etran, etran.ModifiesFrame(s.Tok), builder, desc, null);
DefineFrame(s.Tok, etran.ModifiesFrame(s.Tok), s.Mod.Expressions, builder, locals, loopFrameName);
}
Expand Down Expand Up @@ -2042,21 +2039,28 @@ public partial class BoogieGenerator {
}
}

var directSub = new Substituter(null, directSubstMap, tySubst);

// Check that the reads clause of a subcall is a subset of the current reads frame,
// but support the optimization that we don't define a reads frame at all if it's `reads *`.
if (etran.readsFrame != null) {
// substitute actual args for parameter in frames for the description expression...
var requiredFrames = callee.Reads.Expressions.ConvertAll(directSub.SubstFrameExpr);
var readFrames = (codeContext as MethodOrFunction)?.Reads?.Expressions ?? new();
var desc = new PODesc.ReadFrameSubset("call", requiredFrames, readFrames);

// ... but no need to do so for frames passed to CheckFrameSubset
var readsSubst = new Substituter(null, new Dictionary<IVariable, Expression>(), tySubst);
CheckFrameSubset(tok, callee.Reads.Expressions.ConvertAll(readsSubst.SubstFrameExpr),
receiver, substMap, etran, etran.ReadsFrame(tok), builder, new PODesc.ReadFrameSubset("call"), null);
receiver, substMap, etran, etran.ReadsFrame(tok), builder, desc, null);
}
// Check that the modifies clause of a subcall is a subset of the current modifies frame,
// but only if we're in a context that defines a modifies frame.
if (codeContext is IMethodCodeContext methodCodeContext) {
// substitute actual args for parameters in frames for the description expression...
var descSubst = new Substituter(null, directSubstMap, tySubst);
var desc = new PODesc.ModifyFrameSubset(
"call",
callee.Mod.Expressions.ConvertAll(descSubst.SubstFrameExpr),
callee.Mod.Expressions.ConvertAll(directSub.SubstFrameExpr),
methodCodeContext.Modifies.Expressions
);
// ... but no need to do so for frames passed to CheckFrameSubset
Expand Down Expand Up @@ -2508,7 +2512,7 @@ public partial class BoogieGenerator {

var lhsNameSet = new Dictionary<string, object>();

var contextModFrames = (codeContext as IMethodCodeContext)?.Modifies.Expressions ?? new();
var contextModFrames = GetContextModifiesFrames();

// Note, the resolver does not check for duplicate IdentifierExpr's in LHSs, so do it here.
foreach (var lhs in lhss) {
Expand Down Expand Up @@ -2960,6 +2964,13 @@ public partial class BoogieGenerator {
CheckWellformed(expr, options, locals, builder, etran);
builder.Add(TrAssumeCmd(expr.tok, etran.CanCallAssumption(expr)));
}

List<FrameExpression> GetContextReadsFrames() {
return (codeContext as MethodOrFunction)?.Reads?.Expressions ?? new();
}

List<FrameExpression> GetContextModifiesFrames() {
return (codeContext as IMethodCodeContext)?.Modifies?.Expressions ?? new();
}
}
}
Loading