| | | 1 | | using Microsoft.Z3; |
| | | 2 | | using NanoCLang.Entities; |
| | | 3 | | using NanoCLang.Environemnts; |
| | | 4 | | using System; |
| | | 5 | | using System.Linq; |
| | | 6 | | |
| | | 7 | | namespace NanoCLang { |
| | | 8 | | /// <summary> |
| | | 9 | | /// Provides a class for validating subtyping refinements using an SMT solver. |
| | | 10 | | /// </summary> |
| | | 11 | | public class NanoCSMT : IDisposable { |
| | | 12 | | /// <summary> |
| | | 13 | | /// Gets the default instance of the NanoC SMT solver. |
| | | 14 | | /// </summary> |
| | 165 | 15 | | public static NanoCSMT Default { get; } = new NanoCSMT(); |
| | | 16 | | /// <summary> |
| | | 17 | | /// Gets the currently used instance of the SMT context. |
| | | 18 | | /// </summary> |
| | 4383 | 19 | | public Context Context { get; } |
| | | 20 | | /// <summary> |
| | | 21 | | /// Gets the number of SMT solver invocations of this solver instance. |
| | | 22 | | /// </summary> |
| | 347 | 23 | | public int InvocationCount { get; private set; } = 0; |
| | | 24 | | private readonly Solver solver; |
| | | 25 | | private readonly FuncDecl _bs; |
| | | 26 | | private readonly FuncDecl _be; |
| | | 27 | | /// <summary> |
| | | 28 | | /// Creates a new instance of the NanoC SMT solver. |
| | | 29 | | /// </summary> |
| | 2 | 30 | | private NanoCSMT() { |
| | 1 | 31 | | Context = new Context(); |
| | 1 | 32 | | solver = Context.MkSolver("QF_LIA"); |
| | 1 | 33 | | _bs = Context.MkFuncDecl(Context.MkSymbol("BS"), Context.IntSort, Context.IntSort); |
| | 1 | 34 | | _be = Context.MkFuncDecl(Context.MkSymbol("BE"), Context.IntSort, Context.IntSort); |
| | 1 | 35 | | } |
| | | 36 | | /// <inheritdoc cref="Entities.Index.CollidesWith(int, Entities.Index, int)"/> |
| | 44 | 37 | | public bool IndicesCollide(Entities.Index index1, int size, Entities.Index index2, int otherSize) => Scoped(solv |
| | 22 | 38 | | (var guard1, var value1) = index1.SMTPossibleValues(this, size, "l_"); |
| | 22 | 39 | | (var guard2, var value2) = index2.SMTPossibleValues(this, otherSize, "r_"); |
| | 22 | 40 | | solver.Assert(guard1, guard2, Context.MkEq(value1, value2)); |
| | 22 | 41 | | VerbConsole.WriteLine(VerbosityLevel.Default, "Solving: {0} with type {1} collides {2} with type {3}!", inde |
| | 22 | 42 | | switch (InvokeSolver(solver)) { |
| | 10 | 43 | | case Status.UNSATISFIABLE: return false; |
| | 22 | 44 | | case Status.SATISFIABLE: |
| | 12 | 45 | | VerbConsole.WriteLine(VerbosityLevel.Default, "Bad: Indices collide at offset {0}!", solver.Model.Eval(v |
| | 12 | 46 | | return true; |
| | 0 | 47 | | default: throw new IllFormedException(index1, $"Cannot determine index collision with fallback SMT solver!") |
| | 22 | 48 | | } |
| | 44 | 49 | | }); |
| | | 50 | | /// <summary> |
| | | 51 | | /// Invokes the SMT solver to solve if the expression is always truthy. |
| | | 52 | | /// </summary> |
| | | 53 | | /// <param name="gamma">Current environment.</param> |
| | | 54 | | /// <param name="exp">Antecedent of the implication.</param> |
| | | 55 | | /// <returns>Boolean value that represents the value of the expression or <see langword="null"/> if the expressi |
| | 42 | 56 | | public bool? TryEvalConstExpression(LocalEnvironment gamma, PureExpression exp) => Scoped(solver => { |
| | 21 | 57 | | if (gamma.Count > 0) |
| | 17 | 58 | | solver.Assert(gamma.ToBoolean(this)); |
| | 42 | 59 | | return Scoped(solver => { |
| | 21 | 60 | | solver.Assert(!exp.ToBoolean(gamma, this)); |
| | 21 | 61 | | VerbConsole.WriteLine(VerbosityLevel.Default, "Solving: {0} always true\n{1}", exp, solver); |
| | 21 | 62 | | switch (InvokeSolver(solver)) { |
| | 12 | 63 | | case Status.UNSATISFIABLE: return true; |
| | 0 | 64 | | case Status.UNKNOWN: return false; |
| | 9 | 65 | | case Status.SATISFIABLE: return false; |
| | 0 | 66 | | default: throw new IllFormedException(exp, $"Unexpected status in SMT solver!"); |
| | 21 | 67 | | } |
| | 21 | 68 | | }) |
| | 21 | 69 | | ? true |
| | 9 | 70 | | : Scoped(solver => { |
| | 9 | 71 | | solver.Assert(exp.ToBoolean(gamma, this)); |
| | 9 | 72 | | VerbConsole.WriteLine(VerbosityLevel.Default, "Solving: {0} always false\n{1}", exp, solver); |
| | 9 | 73 | | switch (InvokeSolver(solver)) { |
| | 4 | 74 | | case Status.UNSATISFIABLE: return true; |
| | 0 | 75 | | case Status.UNKNOWN: return false; |
| | 5 | 76 | | case Status.SATISFIABLE: return false; |
| | 0 | 77 | | default: throw new IllFormedException(exp, $"Unexpected status in SMT solver!"); |
| | 21 | 78 | | } |
| | 9 | 79 | | }) ? false |
| | 21 | 80 | | : (bool?)null; |
| | 42 | 81 | | }); |
| | | 82 | | /// <summary> |
| | | 83 | | /// Safely pushes a solver scope and evaluates the function with the scoped solver. |
| | | 84 | | /// </summary> |
| | | 85 | | /// <typeparam name="T">Type of the return value to evaluate.</typeparam> |
| | | 86 | | /// <param name="p">Function to evaluate the return value with the solver.</param> |
| | | 87 | | /// <returns>Evaluated result inside the scope.</returns> |
| | 194 | 88 | | private T Scoped<T>(Func<Solver, T> p) { |
| | 194 | 89 | | solver.Push(); |
| | 194 | 90 | | try { |
| | 194 | 91 | | return p(solver); |
| | 194 | 92 | | } finally { |
| | 194 | 93 | | solver.Pop(); |
| | 194 | 94 | | } |
| | 194 | 95 | | } |
| | | 96 | | /// <summary> |
| | | 97 | | /// Invokes the SMT solver to solve Valid([gamma] and [a1] implies [a2]). |
| | | 98 | | /// This checks if <paramref name="a1"/> implies that <paramref name="a2"/> holds given the environment <paramre |
| | | 99 | | /// </summary> |
| | | 100 | | /// <param name="gamma">Current environment.</param> |
| | | 101 | | /// <param name="a1">Antecedent of the implication.</param> |
| | | 102 | | /// <param name="a2">Consequent of the implication.</param> |
| | | 103 | | /// <returns><see langword="true"/> iff the implication holds, otherwise <see langword="false"/>.</returns> |
| | | 104 | | /// <exception cref="IllFormedException">Solver is unable to check if the implication holds.</exception> |
| | 242 | 105 | | public bool ValidImplication(LocalEnvironment gamma, PureExpression a1, PureExpression a2) => Scoped(solver => { |
| | 121 | 106 | | VerbConsole.WriteLine(VerbosityLevel.Default, $"Checking implication {a1} => {a2}..."); |
| | 121 | 107 | | if (gamma.Count > 0) |
| | 121 | 108 | | solver.Assert(gamma.ToBoolean(this)); |
| | 121 | 109 | | solver.Assert(!Context.MkImplies(a1.ToBoolean(gamma, this), a2.ToBoolean(gamma, this))); |
| | 121 | 110 | | VerbConsole.WriteLine(VerbosityLevel.Default, "Solving: {0}", solver); |
| | 121 | 111 | | switch (InvokeSolver(solver)) { |
| | 109 | 112 | | case Status.UNSATISFIABLE: return true; |
| | 0 | 113 | | case Status.UNKNOWN: throw new IllFormedException(a2, $"Refinement cannot be verified!"); |
| | 121 | 114 | | case Status.SATISFIABLE: |
| | 35 | 115 | | VerbConsole.WriteLine(VerbosityLevel.Default, $"Subtyping broken for values:\n\t{string.Join("\n\t", sol |
| | 12 | 116 | | return false; |
| | 0 | 117 | | default: throw new IllFormedException(a2, $"Unexpected status in SMT solver!"); |
| | 121 | 118 | | } |
| | 242 | 119 | | }); |
| | | 120 | | |
| | 173 | 121 | | private Status InvokeSolver(Solver solver) { |
| | 173 | 122 | | InvocationCount++; |
| | 173 | 123 | | return solver.Check(); |
| | 173 | 124 | | } |
| | | 125 | | |
| | | 126 | | /// <inheritdoc/> |
| | 0 | 127 | | public void Dispose() { |
| | 0 | 128 | | ((IDisposable)Context).Dispose(); |
| | 0 | 129 | | ((IDisposable)solver).Dispose(); |
| | 0 | 130 | | } |
| | | 131 | | #region Support functions |
| | | 132 | | /// <summary> |
| | | 133 | | /// Creates a new boolean expression that ensures the pointer <paramref name="v"/> is is correctly offset by <pa |
| | | 134 | | /// </summary> |
| | | 135 | | /// <param name="v">Initial pointer.</param> |
| | | 136 | | /// <param name="x">Other pointer in the same block.</param> |
| | | 137 | | /// <param name="i">Offset between the pointers.</param> |
| | | 138 | | /// <returns><see langword="true"/> expression iff the pointer is offset correctly.</returns> |
| | 32 | 139 | | public BoolExpr PAdd(IntExpr v, IntExpr x, ArithExpr i) => Context.MkAnd(Context.MkEq(v, x + i), Context.MkEq(BS |
| | | 140 | | /// <summary> |
| | | 141 | | /// Creates a new boolean expression that ensures that the pointer <paramref name="v"/> points to the start of a |
| | | 142 | | /// </summary> |
| | | 143 | | /// <param name="v">Start of the current block (index 0).</param> |
| | | 144 | | /// <param name="n">Length of the block in bytes.</param> |
| | | 145 | | /// <returns><see langword="true"/> expression iff the block has the given length and position.</returns> |
| | 153 | 146 | | public BoolExpr BLen(IntExpr v, ArithExpr n) => Context.MkAnd(Context.MkEq(BS(v), v), Context.MkEq(BE(v), v + n) |
| | | 147 | | /// <summary> |
| | | 148 | | /// Creates a new boolean expression that ensures that a pointer is safely within bounds of the current block. |
| | | 149 | | /// </summary> |
| | | 150 | | /// <param name="v">Pointer to check for memory safety.</param> |
| | | 151 | | /// <returns><see langword="true"/> expression iff the pointer is within safe bounds.</returns> |
| | 159 | 152 | | public BoolExpr Safe(IntExpr v) => Context.MkAnd(v > 0, BS(v) <= v, v < BE(v)); |
| | | 153 | | /// <summary> |
| | | 154 | | /// Creates a new boolean expression that ensures that a pointer is safely within bounds of the current block an |
| | | 155 | | /// This is essentially equal to SafeN(v, n) <=> AND_{0 <= i <= n} Safe(v + i) |
| | | 156 | | /// </summary> |
| | | 157 | | /// <param name="v">Pointer to check for memory safety.</param> |
| | | 158 | | /// <param name="n">Length of the memory block in bytes.</param> |
| | | 159 | | /// <returns><see langword="true"/> expression iff the pointer and its block is within safe bounds.</returns> |
| | 38 | 160 | | internal BoolExpr SafeN(IntExpr v, IntExpr n) => Context.MkAnd(v > 0, BS(v) <= v, (v + n) <= BE(v)); |
| | | 161 | | /// <summary> |
| | | 162 | | /// Creates a new integer expression that maps a pointer to the start of the block that it points to. |
| | | 163 | | /// </summary> |
| | | 164 | | /// <param name="v">Pointer in the same block.</param> |
| | | 165 | | /// <returns>Integer expression to the start of the block.</returns> |
| | 414 | 166 | | public IntExpr BS(IntExpr v) => (IntExpr)_bs[v]; |
| | | 167 | | /// <summary> |
| | | 168 | | /// Creates a new integer expression that maps a pointer to the end (exclusive) of the block that it points to. |
| | | 169 | | /// </summary> |
| | | 170 | | /// <param name="v">Pointer in the same block.</param> |
| | | 171 | | /// <returns>Integer expression to the end (exclusive) of the block.</returns> |
| | 414 | 172 | | public IntExpr BE(IntExpr v) => (IntExpr)_be[v]; |
| | | 173 | | #endregion |
| | | 174 | | } |
| | | 175 | | } |