diff --git a/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs index 64b44f83e3..9d3bbf8387 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs @@ -281,6 +281,16 @@ protected override object EvaluateInternal(EvaluationContext context) var equal = left == right; return equal ? JsBoolean.True : JsBoolean.False; } + + public override bool GetBooleanValue(EvaluationContext context) + { + if (!TryEvaluateOperands(context, out var left, out var right)) + { + return false; + } + + return left == right; + } } private sealed class StrictlyNotEqualBinaryExpression : JintBinaryExpression @@ -298,6 +308,16 @@ protected override object EvaluateInternal(EvaluationContext context) return left == right ? JsBoolean.False : JsBoolean.True; } + + public override bool GetBooleanValue(EvaluationContext context) + { + if (!TryEvaluateOperands(context, out var left, out var right)) + { + return false; + } + + return left != right; + } } private sealed class LessBinaryExpression : JintBinaryExpression @@ -323,6 +343,23 @@ protected override object EvaluateInternal(EvaluationContext context) return value._type == InternalTypes.Undefined ? JsBoolean.False : value; } + + public override bool GetBooleanValue(EvaluationContext context) + { + if (!TryEvaluateOperands(context, out var left, out var right)) + { + return false; + } + + if (context.OperatorOverloadingAllowed + && TryOperatorOverloading(context, left, right, "op_LessThan", out var opResult)) + { + return TypeConverter.ToBoolean(JsValue.FromObject(context.Engine, opResult)); + } + + var value = Compare(left, right); + return value._type != InternalTypes.Undefined && ((JsBoolean) value)._value; + } } private sealed class GreaterBinaryExpression : JintBinaryExpression @@ -348,6 +385,23 @@ protected override object EvaluateInternal(EvaluationContext context) return value._type == InternalTypes.Undefined ? JsBoolean.False : value; } + + public override bool GetBooleanValue(EvaluationContext context) + { + if (!TryEvaluateOperands(context, out var left, out var right)) + { + return false; + } + + if (context.OperatorOverloadingAllowed + && TryOperatorOverloading(context, left, right, "op_GreaterThan", out var opResult)) + { + return TypeConverter.ToBoolean(JsValue.FromObject(context.Engine, opResult)); + } + + var value = Compare(right, left, false); + return value._type != InternalTypes.Undefined && ((JsBoolean) value)._value; + } } private sealed class PlusBinaryExpression : JintBinaryExpression @@ -374,6 +428,11 @@ protected override object EvaluateInternal(EvaluationContext context) return JsNumber.Create((long) left.AsInteger() + right.AsInteger()); } + if (left._type == InternalTypes.Number && right._type == InternalTypes.Number) + { + return JsNumber.Create(((JsNumber) left)._value + ((JsNumber) right)._value); + } + var lprim = TypeConverter.ToPrimitive(left); var rprim = TypeConverter.ToPrimitive(right); JsValue result; @@ -498,15 +557,21 @@ protected override object EvaluateInternal(EvaluationContext context) return JsValue.FromObject(context.Engine, opResult); } - JsValue number; - left = TypeConverter.ToNumeric(left); - right = TypeConverter.ToNumeric(right); - if (AreIntegerOperands(left, right)) { - number = JsNumber.Create((long) left.AsInteger() - right.AsInteger()); + return JsNumber.Create((long) left.AsInteger() - right.AsInteger()); } - else if (AreNonBigIntOperands(left, right)) + + if (left._type == InternalTypes.Number && right._type == InternalTypes.Number) + { + return JsNumber.Create(((JsNumber) left)._value - ((JsNumber) right)._value); + } + + left = TypeConverter.ToNumeric(left); + right = TypeConverter.ToNumeric(right); + + JsValue number; + if (AreNonBigIntOperands(left, right)) { number = JsNumber.Create(left.AsNumber() - right.AsNumber()); } @@ -543,6 +608,10 @@ protected override object EvaluateInternal(EvaluationContext context) { result = JsNumber.Create((long) left.AsInteger() * right.AsInteger()); } + else if (left._type == InternalTypes.Number && right._type == InternalTypes.Number) + { + result = JsNumber.Create(((JsNumber) left)._value * ((JsNumber) right)._value); + } else { var leftNumeric = TypeConverter.ToNumeric(left); @@ -582,6 +651,11 @@ protected override object EvaluateInternal(EvaluationContext context) return JsValue.FromObject(context.Engine, opResult); } + if (left._type == InternalTypes.Number && right._type == InternalTypes.Number) + { + return JsNumber.Create(((JsNumber) left)._value / ((JsNumber) right)._value); + } + left = TypeConverter.ToNumeric(left); right = TypeConverter.ToNumeric(right); return Divide(context, left, right); @@ -617,6 +691,26 @@ protected override object EvaluateInternal(EvaluationContext context) return equality == !_invert ? JsBoolean.True : JsBoolean.False; } + + public override bool GetBooleanValue(EvaluationContext context) + { + if (!TryEvaluateOperands(context, out var left, out var right)) + { + return false; + } + + if (context.OperatorOverloadingAllowed + && TryOperatorOverloading(context, left, right, _invert ? "op_Inequality" : "op_Equality", out var opResult)) + { + return TypeConverter.ToBoolean(JsValue.FromObject(context.Engine, opResult)); + } + + var equality = left.Type == right.Type + ? left.Equals(right) + : left.IsLooselyEqual(right); + + return _invert ? !equality : equality; + } } private sealed class CompareBinaryExpression : JintBinaryExpression @@ -647,6 +741,26 @@ protected override object EvaluateInternal(EvaluationContext context) var value = Compare(left, right, _leftFirst); return value.IsUndefined() || ((JsBoolean) value)._value ? JsBoolean.False : JsBoolean.True; } + + public override bool GetBooleanValue(EvaluationContext context) + { + if (!TryEvaluateOperands(context, out var leftValue, out var rightValue)) + { + return false; + } + + if (context.OperatorOverloadingAllowed + && TryOperatorOverloading(context, leftValue, rightValue, _leftFirst ? "op_GreaterThanOrEqual" : "op_LessThanOrEqual", out var opResult)) + { + return TypeConverter.ToBoolean(JsValue.FromObject(context.Engine, opResult)); + } + + var left = _leftFirst ? leftValue : rightValue; + var right = _leftFirst ? rightValue : leftValue; + + var value = Compare(left, right, _leftFirst); + return !value.IsUndefined() && !((JsBoolean) value)._value; + } } private sealed class InstanceOfBinaryExpression : JintBinaryExpression diff --git a/Jint/Runtime/Interpreter/Expressions/JintExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintExpression.cs index c1f31998c0..6f2228b3b5 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintExpression.cs @@ -54,6 +54,15 @@ internal object EvaluateWithoutNodeTracking(EvaluationContext context) protected abstract object EvaluateInternal(EvaluationContext context); + /// + /// Resolves this expression as a boolean value. + /// Comparison expressions override this to avoid creating a JsBoolean wrapper. + /// + public virtual bool GetBooleanValue(EvaluationContext context) + { + return TypeConverter.ToBoolean(GetValue(context)); + } + /// /// If we'd get Esprima source, we would just refer to it, but this makes error messages easier to decipher. /// @@ -147,8 +156,6 @@ protected internal static JintExpression Build(Expression expression) protected static JsValue Remainder(EvaluationContext context, JsValue left, JsValue right) { var result = JsValue.Undefined; - left = TypeConverter.ToNumeric(left); - right = TypeConverter.ToNumeric(right); if (AreIntegerOperands(left, right)) { var leftInteger = left.AsInteger(); @@ -171,50 +178,56 @@ protected static JsValue Remainder(EvaluationContext context, JsValue left, JsVa } } } - else if (JintBinaryExpression.AreNonBigIntOperands(left, right)) + else { - var n = left.AsNumber(); - var d = right.AsNumber(); + left = TypeConverter.ToNumeric(left); + right = TypeConverter.ToNumeric(right); - if (double.IsNaN(n) || double.IsNaN(d) || double.IsInfinity(n)) - { - result = JsNumber.DoubleNaN; - } - else if (double.IsInfinity(d)) - { - result = n; - } - else if (NumberInstance.IsPositiveZero(d) || NumberInstance.IsNegativeZero(d)) - { - result = JsNumber.DoubleNaN; - } - else if (NumberInstance.IsPositiveZero(n) || NumberInstance.IsNegativeZero(n)) + if (JintBinaryExpression.AreNonBigIntOperands(left, right)) { - result = n; + var n = left.AsNumber(); + var d = right.AsNumber(); + + if (double.IsNaN(n) || double.IsNaN(d) || double.IsInfinity(n)) + { + result = JsNumber.DoubleNaN; + } + else if (double.IsInfinity(d)) + { + result = n; + } + else if (NumberInstance.IsPositiveZero(d) || NumberInstance.IsNegativeZero(d)) + { + result = JsNumber.DoubleNaN; + } + else if (NumberInstance.IsPositiveZero(n) || NumberInstance.IsNegativeZero(n)) + { + result = n; + } + else + { + result = JsNumber.Create(n % d); + } } else { - result = JsNumber.Create(n % d); - } - } - else - { - JintBinaryExpression.AssertValidBigIntArithmeticOperands(left, right); + JintBinaryExpression.AssertValidBigIntArithmeticOperands(left, right); - var n = TypeConverter.ToBigInt(left); - var d = TypeConverter.ToBigInt(right); + var bn = TypeConverter.ToBigInt(left); + var bd = TypeConverter.ToBigInt(right); - if (d == 0) - { - Throw.RangeError(context.Engine.Realm, "Division by zero"); - } - else if (n == 0) - { - result = JsBigInt.Zero; - } - else - { - result = JsBigInt.Create(n % d); + if (bd == 0) + { + Throw.RangeError(context.Engine.Realm, "Division by zero"); + } + else if (bn == 0) + { + result = JsBigInt.Zero; + } + else + { + result = JsBigInt.Create(bn % bd); + } } } @@ -332,29 +345,29 @@ protected static JsValue Compare(JsValue x, JsValue y, bool leftFirst = true) => private static JsValue CompareNumber(JsValue x, JsValue y, bool leftFirst) { + if (x.IsInteger() && y.IsInteger()) + { + return x.AsInteger() < y.AsInteger() ? JsBoolean.True : JsBoolean.False; + } + double nx, ny; if (leftFirst) { - nx = x.AsNumber(); - ny = y.AsNumber(); + nx = ((JsNumber) x)._value; + ny = ((JsNumber) y)._value; } else { - ny = y.AsNumber(); - nx = x.AsNumber(); + ny = ((JsNumber) y)._value; + nx = ((JsNumber) x)._value; } - if (x.IsInteger() && y.IsInteger()) + if (double.IsNaN(nx) || double.IsNaN(ny)) { - return (int) nx < (int) ny ? JsBoolean.True : JsBoolean.False; - } - - if (!double.IsInfinity(nx) && !double.IsInfinity(ny) && !double.IsNaN(nx) && !double.IsNaN(ny)) - { - return nx < ny ? JsBoolean.True : JsBoolean.False; + return JsValue.Undefined; } - return CompareComplex(x, y, leftFirst); + return nx < ny ? JsBoolean.True : JsBoolean.False; } private static JsValue CompareComplex(JsValue x, JsValue y, bool leftFirst) diff --git a/Jint/Runtime/Interpreter/Statements/JintForStatement.cs b/Jint/Runtime/Interpreter/Statements/JintForStatement.cs index 0f82c93795..d06c594b2e 100644 --- a/Jint/Runtime/Interpreter/Statements/JintForStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintForStatement.cs @@ -261,16 +261,14 @@ private Completion ForBodyEvaluation(EvaluationContext context) { debugHandler?.OnStep(_test._expression); - var testValue = _test.GetValue(context); - - // Check for async suspension in test expression - if (context.IsSuspended()) + if (!_test.GetBooleanValue(context)) { - return new Completion(CompletionType.Return, JsValue.Undefined, ((JintStatement) this)._statement); - } + // Check for async suspension in test expression + if (context.IsSuspended()) + { + return new Completion(CompletionType.Return, JsValue.Undefined, ((JintStatement) this)._statement); + } - if (!TypeConverter.ToBoolean(testValue)) - { return new Completion(CompletionType.Normal, v, ((JintStatement) this)._statement); } } diff --git a/Jint/Runtime/Interpreter/Statements/JintIfStatement.cs b/Jint/Runtime/Interpreter/Statements/JintIfStatement.cs index fe9bff1cad..03b6079c2f 100644 --- a/Jint/Runtime/Interpreter/Statements/JintIfStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintIfStatement.cs @@ -28,7 +28,7 @@ protected override void Initialize(EvaluationContext context) protected override Completion ExecuteInternal(EvaluationContext context) { Completion result; - if (TypeConverter.ToBoolean(_test.GetValue(context))) + if (_test.GetBooleanValue(context)) { // B.3.2/B.3.3: IfStatement function declarations need runtime AnnexB handling if (_consequentIsFunctionDecl && !StrictModeScope.IsStrictModeCode) diff --git a/Jint/Runtime/Interpreter/Statements/JintWhileStatement.cs b/Jint/Runtime/Interpreter/Statements/JintWhileStatement.cs index 5d98cbab47..d6f6c8a3db 100644 --- a/Jint/Runtime/Interpreter/Statements/JintWhileStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintWhileStatement.cs @@ -44,18 +44,17 @@ protected override Completion ExecuteInternal(EvaluationContext context) context.Engine.Debugger.OnStep(_test._expression); } - var jsValue = _test.GetValue(context); - - // Check for suspension after evaluating the test expression - var suspendable = context.Engine.ExecutionContext.Suspendable; - if (context.IsSuspended()) + if (!_test.GetBooleanValue(context)) { - var suspendedValue = suspendable?.SuspendedValue ?? JsValue.Undefined; - return new Completion(CompletionType.Return, suspendedValue, _statement); - } + // GetBooleanValue returns false for both actual false condition + // and suspended evaluation (async/generator); check which case + if (context.IsSuspended()) + { + var suspendable = context.Engine.ExecutionContext.Suspendable; + var suspendedValue = suspendable?.SuspendedValue ?? JsValue.Undefined; + return new Completion(CompletionType.Return, suspendedValue, _statement); + } - if (!TypeConverter.ToBoolean(jsValue)) - { return new Completion(CompletionType.Normal, v, _statement); } @@ -69,7 +68,8 @@ protected override Completion ExecuteInternal(EvaluationContext context) // Check for suspension - if suspended, we need to exit the loop if (context.IsSuspended()) { - var suspendedValue = suspendable?.SuspendedValue ?? completion.Value; + var bodySuspendable = context.Engine.ExecutionContext.Suspendable; + var suspendedValue = bodySuspendable?.SuspendedValue ?? completion.Value; return new Completion(CompletionType.Return, suspendedValue, _statement); }