public override object[] Reduce(Enviroment env, ErrorHandling errorHandling) { BaseStatement statement; if (Status.Reducible) { var reduceValue = Status.Reduce(env); if (reduceValue.GetType().IsSubclassOf(typeof(BaseError))) { statement = new Throw((BaseError)reduceValue); } else { statement = new SetObjectActive(Object, reduceValue); } } else { if (Status.GetType() != typeof(IceKoriBool)) { statement = new Throw(new TypeError($"Constant value \"{Status}\" cannot be converted to a bool")); } else { Object.SetActive(((IceKoriBool)Status).Value); statement = new DoNothing(); } } return(new object[] { statement, env, errorHandling }); }
static BaseExpression HandleExpression(BaseExpression right) { return(right switch { Variable v => new Increment(v.Name), ConstantNumber n => new ConstantNumber(new Add(n, new ConstantNumber((Number)1)).StaticEvaluate().Number), ConstantString s => new ConstantString(s.Value + " "), Bracketed b => HandleExpression(b.Parameter), _ => throw new InvalidOperationException(right.GetType().Name) });
public override BaseStatement Reduce(Enviroment env) { if (Condition.Reducible) { return(new IfStatement(Condition.Reduce(env), Consequence, Alternative)); } if (Condition.GetType() == typeof(IceKoriBool)) { var context = (bool)((IceKoriBool)Condition).Unbox() ? Consequence : Alternative; if (context.Count == 0) { return(new DoNothing()); } return(new SequenceStatment(context)); } return(new Throw(new TypeError())); }
private void _VariableReduce(Dictionary <string, BaseExpression> variables) { foreach (var keyValuePair in variables) { BaseExpression value = keyValuePair.Value; while (true) { if (value.Reducible) { value = value.Reduce(this); if (value.GetType().IsSubclassOf(typeof(BaseError))) { Interpreter.ErrorHandling.ThrowError((BaseError)value, this); return; } } else { Variables[keyValuePair.Key] = (IceKoriBaseType)value; break; } } } }
[NotNull] protected virtual TResult VisitUnknown(BaseExpression expression) { throw new InvalidOperationException($"`Visit` not invalid for expression type `{expression.GetType().FullName}`"); }
internal void AssertEq(BaseExpression expr) { var ctx = _model.Context; var sol = _model.Solver; var m = _model; (BoolExpr nnt, BoolExpr sst, BoolExpr nst, BoolExpr snt) BinaryTypes(BaseBinaryExpression bin, DatatypeExpr?nn, DatatypeExpr?ss, DatatypeExpr?sn, DatatypeExpr?ns) { var l = GetOrCreateVar(bin.Left); var r = GetOrCreateVar(bin.Right); // set up type checks for the cases var nnt = ctx.MkEq(l._type, m.NumType) & ctx.MkEq(r._type, m.NumType); var sst = ctx.MkEq(l._type, m.StrType) & ctx.MkEq(r._type, m.StrType); var nst = ctx.MkEq(l._type, m.NumType) & ctx.MkEq(r._type, m.StrType); var snt = ctx.MkEq(l._type, m.StrType) & ctx.MkEq(r._type, m.NumType); // Assert the 4 type cases if (nn != null) { sol.Assert(ctx.MkImplies(nnt, ctx.MkEq(_type, nn))); } if (ss != null) { sol.Assert(ctx.MkImplies(sst, ctx.MkEq(_type, ss))); } if (ns != null) { sol.Assert(ctx.MkImplies(nst, ctx.MkEq(_type, ns))); } if (sn != null) { sol.Assert(ctx.MkImplies(snt, ctx.MkEq(_type, sn))); } return(nnt, sst, nst, snt); } void ComparisonOp(BaseBinaryExpression comparison, Func <IntExpr, IntExpr, BoolExpr>?nnr, Func <SeqExpr, SeqExpr, BoolExpr>?ssr) { var(nnt, sst, _, _) = BinaryTypes(comparison, m.NumType, m.NumType, m.NumType, m.NumType); var l = GetOrCreateVar(comparison.Left); var r = GetOrCreateVar(comparison.Right); // Note that this does _not_ propagate taint, in fact it removes it! Although the final result of equality with // tainted values cannot be calculated we still know the result is `0 OR 1`. // This also does not handle calculating the exact result of mixed equality (string==num, num==string) because // that would require converting numbers to strings, which is intractable. Instead it just constrains the value to // `0 OR 1`, which is still a useful bound. // Comparison are always 0 or 1 (multiplied by 1000 like all numbers) sol.Assert(ctx.MkEq(ctx.MkInt(0), _numValue) | ctx.MkEq(ctx.MkInt(1000), _numValue)); // number<->number comparison (only compute if values are not tainted) if (nnr != null) { sol.Assert(ctx.MkImplies( nnt & !l._valTaint & !r._valTaint, ctx.MkEq(_numValue, ctx.MkITE( nnr(l._numValue, r._numValue), ctx.MkInt(1000), ctx.MkInt(0) ) ))); } // string<->string equality (only compute if values are not tainted) if (ssr != null) { sol.Assert(ctx.MkIff( sst & !l._valTaint & !r._valTaint, ctx.MkEq(_numValue, ctx.MkITE( ssr(l._strValue, r._strValue), ctx.MkInt(1000), ctx.MkInt(0) ) ))); } } void LogicalOp(BaseBinaryExpression logical, Func <BoolExpr[], BoolExpr> op) { var(nnt, sst, nst, snt) = BinaryTypes(logical, m.NumType, m.NumType, m.NumType, m.NumType); var l = GetOrCreateVar(logical.Left); var r = GetOrCreateVar(logical.Right); // logical are always 0 or 1 (multiplied by 1000 like all numbers) sol.Assert(ctx.MkEq(ctx.MkInt(0), _numValue) | ctx.MkEq(ctx.MkInt(1000), _numValue)); // Create the condition for the operator in question var opp = op(new[] { ctx.MkNot(ctx.MkEq(l._numValue, ctx.MkInt(0))), ctx.MkNot(ctx.MkEq(r._numValue, ctx.MkInt(0))) }); // if either side is a string the result is `true`, regardless of the other side sol.Assert(ctx.MkImplies(sst | nst | snt, ctx.MkEq(_numValue, ctx.MkInt(1000)))); sol.Assert(ctx.MkImplies(nnt, ctx.MkEq(_numValue, ctx.MkITE( opp, ctx.MkInt(1000), ctx.MkInt(0) )))); } (BoolExpr str, BoolExpr num) UnaryTypes(BaseUnaryExpression unary, DatatypeExpr?n, DatatypeExpr?s) { var i = GetOrCreateVar(unary.Parameter); // set up type checks for the cases var str = ctx.MkEq(i._type, m.StrType); var num = ctx.MkEq(i._type, m.NumType); if (n != null) { sol.Assert(ctx.MkImplies(num, ctx.MkEq(_type, n))); } if (s != null) { sol.Assert(ctx.MkImplies(str, ctx.MkEq(_type, s))); } return(str, num); } switch (expr) { case ConstantNumber num: { AssertEq(new Value(num.Value)); return; } case ConstantString str: { AssertEq(new Value(str.Value)); return; } case Variable var: { AssertEq(_model.GetOrCreateVariable(var.Name)); return; } case Add add: { var(nnt, sst, nst, snt) = BinaryTypes(add, m.NumType, m.StrType, m.StrType, m.StrType); var l = GetOrCreateVar(add.Left); var r = GetOrCreateVar(add.Right); // Setup the 2 valid value cases: var nn = ctx.MkAdd(l._numValue, r._numValue); var ss = ctx.MkConcat(l._strValue, r._strValue); // Value is tained if we have one of the intractable type combinations sol.Assert(ctx.MkEq(_valTaint, l._valTaint | r._valTaint | nst | snt)); // Assert the values sol.Assert(ctx.MkIff(!_valTaint & nnt, ctx.MkEq(_numValue, nn))); sol.Assert(ctx.MkIff(!_valTaint & sst, ctx.MkEq(_strValue, ss))); return; } case Subtract sub: { var(nnt, sst, nst, snt) = BinaryTypes(sub, m.NumType, m.StrType, m.StrType, m.StrType); var l = GetOrCreateVar(sub.Left); var r = GetOrCreateVar(sub.Right); // Setup the 2 valid value cases: var nn = ctx.MkSub(l._numValue, r._numValue); var ss = ctx.MkITE( ctx.MkSuffixOf(r._strValue, l._strValue), ctx.MkExtract(l._strValue, ctx.MkInt(0), (IntExpr)(ctx.MkLength(l._strValue) - ctx.MkLength(r._strValue))), l._strValue ); // Value is tained if we have one of the intractable type combinations sol.Assert(ctx.MkEq(_valTaint, l._valTaint | r._valTaint | nst | snt)); // Assert the values sol.Assert(ctx.MkIff(!_valTaint & nnt, ctx.MkEq(_numValue, nn))); sol.Assert(ctx.MkIff(!_valTaint & sst, ctx.MkEq(_strValue, ss))); return; } case Multiply mul: { var(nnt, _, _, _) = BinaryTypes(mul, m.NumType, null, null, null); var l = GetOrCreateVar(mul.Left); var r = GetOrCreateVar(mul.Right); // Value is tained if either side is tainted, or we have an uncomputable type combination sol.Assert(ctx.MkEq(_valTaint, l._valTaint | r._valTaint | !nnt)); // Assert number value if type is number sol.Assert(ctx.MkImplies(!_valTaint & nnt, ctx.MkEq(_numValue, (l._numValue * r._numValue) / 1000))); return; } case Divide div: { var(nnt, _, _, _) = BinaryTypes(div, m.NumType, null, null, null); var l = GetOrCreateVar(div.Left); var r = GetOrCreateVar(div.Right); // Value is tained if either side is tainted, or we have an uncomputable type combination sol.Assert(ctx.MkEq(_valTaint, l._valTaint | r._valTaint | !nnt)); // Assert number value if type is number sol.Assert(ctx.MkImplies(!_valTaint & nnt, ctx.MkEq(_numValue, (l._numValue * 1000) / r._numValue))); return; } case Exponent exp: throw new NotImplementedException("exponent"); case Modulo mod: throw new NotImplementedException("modulo"); case EqualTo eq: { ComparisonOp(eq, ctx.MkEq, ctx.MkEq); return; } case NotEqualTo neq: { ComparisonOp(neq, (a, b) => ctx.MkNot(ctx.MkEq(a, b)), (a, b) => ctx.MkNot(ctx.MkEq(a, b))); return; } case LessThan lt: { ComparisonOp(lt, (a, b) => ctx.MkLt(a, b), null); return; } case LessThanEqualTo lteq: { ComparisonOp(lteq, (a, b) => ctx.MkLe(a, b), null); return; } case GreaterThan gt: { ComparisonOp(gt, (a, b) => ctx.MkGt(a, b), null); return; } case GreaterThanEqualTo gt: { ComparisonOp(gt, (a, b) => ctx.MkGe(a, b), null); return; } case And and: { LogicalOp(and, ctx.MkAnd); return; } case Or or: { LogicalOp(or, ctx.MkOr); return; } case Abs abs: { var(str, num) = UnaryTypes(abs, m.NumType, null); var i = GetOrCreateVar(abs.Parameter); // Value is tained if parameter is tainted, or we have an uncomputable type input sol.Assert(ctx.MkEq(_valTaint, i._valTaint | str)); // Assert number value if type is number sol.Assert(ctx.MkImplies(!_valTaint & num, ctx.MkEq(_numValue, ctx.MkITE(i._numValue < 0, -i._numValue, i._numValue)))); return; } case ArcCos acos: throw new NotImplementedException("acos"); case ArcSine asin: throw new NotImplementedException("asin"); case ArcTan atan: throw new NotImplementedException("atan"); case Cosine cos: { var(str, num) = UnaryTypes(cos, m.NumType, null); var i = GetOrCreateVar(cos.Parameter); // Value is tained if parameter is tainted, or we have an uncomputable type input sol.Assert(ctx.MkEq(_valTaint, i._valTaint | str)); // Z3 cannot reason about trancendental functions, assert range of result sol.Assert(ctx.MkImplies(!_valTaint & num, _numValue >= ctx.MkInt(-1000) & _numValue <= ctx.MkInt(1000))); return; } case Sine sin: { var(str, num) = UnaryTypes(sin, m.NumType, null); var i = GetOrCreateVar(sin.Parameter); // Value is tained if parameter is tainted, or we have an uncomputable type input sol.Assert(ctx.MkEq(_valTaint, i._valTaint | str)); // Z3 cannot reason about trancendental functions, assert range of result sol.Assert(ctx.MkImplies(!_valTaint & num, _numValue >= ctx.MkInt(-1000) & _numValue <= ctx.MkInt(1000))); return; } case Tangent tan: throw new NotImplementedException("tan"); case Negate neg: { var(str, num) = UnaryTypes(neg, m.NumType, null); var i = GetOrCreateVar(neg.Parameter); // Value is tained if parameter is tainted, or we have an uncomputable type input sol.Assert(ctx.MkEq(_valTaint, i._valTaint | str)); // Assert number value if type is number sol.Assert(ctx.MkImplies(!_valTaint & num, ctx.MkEq(_numValue, -i._numValue))); return; } case Not not: { var(str, num) = UnaryTypes(not, m.NumType, m.NumType); var i = GetOrCreateVar(not.Parameter); // logical are always 0 or 1 (multiplied by 1000 like all numbers) sol.Assert(ctx.MkEq(ctx.MkInt(0), _numValue) | ctx.MkEq(ctx.MkInt(1000), _numValue)); // All strings are considered true so `not "any_str" == 0`, with no taint sol.Assert(ctx.MkImplies(str, ctx.MkNot(_valTaint))); sol.Assert(ctx.MkImplies(str, ctx.MkEq(_numValue, ctx.MkInt(0)))); // If type is number and value is tainted then this value is also tainted sol.Assert(ctx.MkImplies(num & i._valTaint, _valTaint)); // Assert not tainted and the final number value if possible sol.Assert(ctx.MkImplies(num & !i._valTaint, ctx.MkNot(_valTaint))); sol.Assert(ctx.MkImplies(num & !i._valTaint, ctx.MkEq(_numValue, ctx.MkITE(ctx.MkEq(i._numValue, ctx.MkInt(0)), ctx.MkInt(1000), ctx.MkInt(0))))); return; } case Sqrt sqrt: { var(str, num) = UnaryTypes(sqrt, m.NumType, null); var i = GetOrCreateVar(sqrt.Parameter); // Z3 cannot calculate square root, so we have to taint the value sol.Assert(_valTaint); // Z3 can compute square roots like this: // // var num = (IntExpr)ctx.MkConst("num", ctx.IntSort); // var sqrt = (IntExpr)ctx.MkConst("lil", ctx.IntSort); // solver.Assert(sqrt * sqrt / 1000 <= num); // solver.Assert((sqrt + 1) * (sqrt + 1) / 1000 >= num); // solver.Assert(ctx.MkEq(big, ctx.MkInt(7000))); // solver.Assert(sqrt >= ctx.MkInt(0)); // // Here `sqrt` will have the value of the sqrt of num (7.000), in this example `2.645`. // // However, calculating this seems to be _extremely_ slow for Z3. Just this simple case // is enough to confuse z3 and cause it to timeout sometimes! return; } case Decrement dec: throw new NotImplementedException("dec"); case Increment inc: throw new NotImplementedException("inc"); case Phi phi: throw new NotImplementedException("phi"); case ErrorExpression _: throw new NotSupportedException("Error Expressions should be simplified to Error Statements before running SAT analysis"); default: // It's not known how to handle this expression, so just taint it sol.Assert(ctx.MkEq(_valTaint, ctx.MkTrue())); throw new NotImplementedException(expr.GetType().Name); } }
[NotNull] private JToken SerializeExpression([NotNull] BaseExpression expr) { switch (expr) { case ConstantNumber num: return(new JObject { ["type"] = "expression::number", ["num"] = num.Value.ToString() }); case ConstantString str: return(new JObject { ["type"] = "expression::string", ["str"] = str.Value }); case Variable var: return(SerializeIdentifier(var.Name)); case Bracketed brk: return(new JObject { ["type"] = "expression::unary_op::parentheses", ["operand"] = SerializeExpression(brk.Parameter) }); case PostDecrement postdec: return(new JObject { ["type"] = "expression::modify_op::post_decrement", ["operand"] = SerializeIdentifier(postdec.Name) }); case PreDecrement predec: return(new JObject { ["type"] = "expression::modify_op::pre_decrement", ["operand"] = SerializeIdentifier(predec.Name) }); case PostIncrement postinc: return(new JObject { ["type"] = "expression::modify_op::post_increment", ["operand"] = SerializeIdentifier(postinc.Name) }); case PreIncrement preinc: return(new JObject { ["type"] = "expression::modify_op::pre_increment", ["operand"] = SerializeIdentifier(preinc.Name) }); case Add add: return(SerializeBinary(add, "add")); case Subtract sub: return(SerializeBinary(sub, "subtract")); case Multiply mul: return(SerializeBinary(mul, "multiply")); case Divide div: return(SerializeBinary(div, "divide")); case Exponent exp: return(SerializeBinary(exp, "exponent")); case Modulo mod: return(SerializeBinary(mod, "modulo")); case And and: return(SerializeBinary(and, "and")); case Or or: return(SerializeBinary(or, "or")); case GreaterThan cmp: return(SerializeBinary(cmp, "greater_than")); case GreaterThanEqualTo cmp: return(SerializeBinary(cmp, "greater_than_or_equal_to")); case LessThan cmp: return(SerializeBinary(cmp, "less_than")); case LessThanEqualTo cmp: return(SerializeBinary(cmp, "less_than_or_equal_to")); case EqualTo cmp: return(SerializeBinary(cmp, "equal_to")); case NotEqualTo cmp: return(SerializeBinary(cmp, "not_equal_to")); default: throw new NotSupportedException($"Cannot serialize expression type `{expr.GetType().Name}`"); } }