Browse Source

Fix instanceof logic to comply with specification (#884)

Marko Lahma 4 years ago
parent
commit
d47f5525c1

+ 15 - 0
Jint.Tests.Test262/Language/Expressions/InstanceOfTests.cs

@@ -0,0 +1,15 @@
+using Xunit;
+
+namespace Jint.Tests.Test262.Language.Expressions
+{
+    public class InstanceOfTests : Test262Test
+    {
+        [Theory(DisplayName = "language\\expressions\\instanceof")]
+        [MemberData(nameof(SourceFiles), "language\\expressions\\instanceof", false)]
+        [MemberData(nameof(SourceFiles), "language\\expressions\\instanceof", true, Skip = "Skipped")]
+        protected void New(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+    }
+}

+ 15 - 0
Jint.Tests/Runtime/ArrayTests.cs

@@ -114,5 +114,20 @@ namespace Jint.Tests.Runtime
 
             _engine.Execute(code);
         }
+
+        [Fact]
+        public void ExtendingArrayAndInstanceOf()
+        {
+            const string script = @"
+                class MyArr extends Array {
+                    constructor(...args) {
+                        super(...args);
+                    } 
+                }";
+
+            _engine.Execute(script);
+            _engine.Execute("const a = new MyArr(1,2);");
+            Assert.True(_engine.Execute("a instanceof MyArr").GetCompletionValue().AsBoolean());
+        }
     }
 }

+ 4 - 4
Jint/Native/Function/BindFunctionInstance.cs

@@ -5,7 +5,7 @@ namespace Jint.Native.Function
 {
     public sealed class BindFunctionInstance : FunctionInstance, IConstructor
     {
-        public BindFunctionInstance(Engine engine) 
+        public BindFunctionInstance(Engine engine)
             : base(engine, name: null, thisMode: FunctionThisMode.Strict)
         {
         }
@@ -43,21 +43,21 @@ namespace Jint.Native.Function
             {
                 newTarget = TargetFunction;
             }
-            
+
             var value = target.Construct(args, newTarget);
             _engine._jsValueArrayPool.ReturnArray(args);
 
             return value;
         }
 
-        public override bool HasInstance(JsValue v)
+        internal override bool OrdinaryHasInstance(JsValue v)
         {
             var f = TargetFunction.TryCast<FunctionInstance>(x =>
             {
                 ExceptionHelper.ThrowTypeError(Engine);
             });
 
-            return f.HasInstance(v);
+            return f.OrdinaryHasInstance(v);
         }
 
         private JsValue[] CreateArguments(JsValue[] arguments)

+ 0 - 29
Jint/Native/Function/FunctionInstance.cs

@@ -70,35 +70,6 @@ namespace Jint.Native.Function
 
         internal override bool IsConstructor => this is IConstructor;
 
-        public virtual bool HasInstance(JsValue v)
-        {
-            if (!(v is ObjectInstance o))
-            {
-                return false;
-            }
-
-            var p = Get(CommonProperties.Prototype);
-            if (!(p is ObjectInstance prototype))
-            {
-                ExceptionHelper.ThrowTypeError(_engine, $"Function has non-object prototype '{TypeConverter.ToString(p)}' in instanceof check");
-            }
-
-            while (true)
-            {
-                o = o.Prototype;
-
-                if (o is null)
-                {
-                    return false;
-                }
-
-                if (SameValue(p, o))
-                {
-                    return true;
-                }
-            }
-        }
-
         public override IEnumerable<KeyValuePair<JsValue, PropertyDescriptor>> GetOwnProperties()
         {
             if (_prototypeDescriptor != null)

+ 6 - 6
Jint/Native/Function/FunctionPrototype.cs

@@ -45,7 +45,7 @@ namespace Jint.Native.Function
                 ["caller"] = _engine._callerCalleeArgumentsThrowerConfigurable
             };
             SetProperties(properties);
-            
+
             var symbols = new SymbolDictionary(1)
             {
                 [GlobalSymbolRegistry.HasInstance] = new PropertyDescriptor(new ClrFunctionInstance(_engine, "[Symbol.hasInstance]", HasInstance, 1, PropertyFlag.Configurable), PropertyFlag.AllForbidden)
@@ -55,12 +55,12 @@ namespace Jint.Native.Function
 
         private static JsValue HasInstance(JsValue thisObj, JsValue[] arguments)
         {
-            if (!(thisObj is FunctionInstance f))
+            if (thisObj is not FunctionInstance f)
             {
                 return false;
             }
-            
-            return f.HasInstance(arguments.At(0));
+
+            return f.OrdinaryHasInstance(arguments.At(0));
         }
 
         private JsValue Bind(JsValue thisObj, JsValue[] arguments)
@@ -96,7 +96,7 @@ namespace Jint.Native.Function
             }
 
             f._length = new PropertyDescriptor(l, PropertyFlag.Configurable);
-            
+
             var targetName = thisObj.Get(CommonProperties.Name);
             if (!targetName.IsString())
             {
@@ -158,7 +158,7 @@ namespace Jint.Native.Function
             var operations = ArrayOperations.For(argArrayObj);
             var allowedTypes = elementTypes ??
                                Types.Undefined | Types.Null | Types.Boolean | Types.String | Types.Symbol | Types.Number | Types.Object;
-            
+
             var argList = operations.GetAll(allowedTypes);
             return argList;
         }

+ 61 - 0
Jint/Native/JsValue.cs

@@ -404,6 +404,30 @@ namespace Jint.Native
             return ExceptionHelper.ThrowNotSupportedException<bool>();
         }
 
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-instanceofoperator
+        /// </summary>
+        internal bool InstanceofOperator(JsValue target)
+        {
+            if (target is not ObjectInstance oi)
+            {
+                return ExceptionHelper.ThrowTypeErrorNoEngine<bool>("not an object");
+            }
+
+            var instOfHandler = oi.GetMethod(GlobalSymbolRegistry.HasInstance);
+            if (instOfHandler is not null)
+            {
+                return TypeConverter.ToBoolean(instOfHandler.Call(target, new [] { this }));
+            }
+
+            if (!target.IsCallable)
+            {
+                return ExceptionHelper.ThrowTypeErrorNoEngine<bool>("not callable");
+            }
+
+            return target.OrdinaryHasInstance(this);
+        }
+
         public override string ToString()
         {
             return "None";
@@ -566,6 +590,43 @@ namespace Jint.Native
 
         internal virtual bool IsCallable => this is ICallable;
 
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-ordinaryhasinstance
+        /// </summary>
+        internal virtual bool OrdinaryHasInstance(JsValue v)
+        {
+            if (!IsCallable)
+            {
+                return false;
+            }
+
+            if (v is not ObjectInstance o)
+            {
+                return false;
+            }
+
+            var p = Get(CommonProperties.Prototype);
+            if (p is not ObjectInstance)
+            {
+                ExceptionHelper.ThrowTypeError(o.Engine, $"Function has non-object prototype '{TypeConverter.ToString(p)}' in instanceof check");
+            }
+
+            while (true)
+            {
+                o = o.Prototype;
+
+                if (o is null)
+                {
+                    return false;
+                }
+
+                if (SameValue(p, o))
+                {
+                    return true;
+                }
+            }
+        }
+
         internal static bool SameValue(JsValue x, JsValue y)
         {
             var typea = x.Type;

+ 5 - 7
Jint/Runtime/Interop/TypeReference.cs

@@ -103,16 +103,14 @@ namespace Jint.Runtime.Interop
             return ExceptionHelper.ThrowTypeError<ObjectInstance>(_engine, "No public methods with the specified arguments were found.");
         }
 
-        public override bool HasInstance(JsValue v)
+        internal override bool OrdinaryHasInstance(JsValue v)
         {
-            if (v.IsObject())
+            if (v is IObjectWrapper wrapper)
             {
-                var wrapper = v.AsObject() as IObjectWrapper;
-                if (wrapper != null)
-                    return wrapper.Target.GetType() == ReferenceType;
+                return wrapper.Target.GetType() == ReferenceType;
             }
 
-            return base.HasInstance(v);
+            return base.OrdinaryHasInstance(v);
         }
 
         public override bool DefineOwnProperty(JsValue property, PropertyDescriptor desc)
@@ -149,7 +147,7 @@ namespace Jint.Runtime.Interop
             {
                 return PropertyDescriptor.Undefined;
             }
-            
+
             var key = jsString._value;
             var descriptor = PropertyDescriptor.Undefined;
 

+ 18 - 24
Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs

@@ -1,7 +1,6 @@
 using System;
 using Esprima.Ast;
 using Jint.Native;
-using Jint.Native.Function;
 using Jint.Native.Object;
 using Jint.Runtime.Interop;
 
@@ -66,19 +65,19 @@ namespace Jint.Runtime.Interpreter.Expressions
                 case BinaryOperator.RightShift:
                 case BinaryOperator.UnsignedRightShift:
                     result = new BitwiseBinaryExpression(engine, expression);
-                    break;                
+                    break;
                 case BinaryOperator.InstanceOf:
                     result = new InstanceOfBinaryExpression(engine, expression);
-                    break;                
+                    break;
                 case BinaryOperator.Exponentiation:
                     result = new ExponentiationBinaryExpression(engine, expression);
-                    break;                
+                    break;
                 case BinaryOperator.Modulo:
                     result = new ModuloBinaryExpression(engine, expression);
-                    break;                
+                    break;
                 case BinaryOperator.In:
                     result = new InBinaryExpression(engine, expression);
-                    break;                
+                    break;
                 default:
                     result = ExceptionHelper.ThrowArgumentOutOfRangeException<JintBinaryExpression>(nameof(expression.Operator), "cannot handle operator");
                     break;
@@ -238,7 +237,7 @@ namespace Jint.Runtime.Interpreter.Expressions
                     : value;
             }
         }
-        
+
         private sealed class PlusBinaryExpression : JintBinaryExpression
         {
             public PlusBinaryExpression(Engine engine, BinaryExpression expression) : base(engine, expression)
@@ -249,7 +248,7 @@ namespace Jint.Runtime.Interpreter.Expressions
             {
                 var left = _left.GetValue();
                 var right = _right.GetValue();
-                
+
                 if (AreIntegerOperands(left, right))
                 {
                     return JsNumber.Create(left.AsInteger() + right.AsInteger());
@@ -258,7 +257,7 @@ namespace Jint.Runtime.Interpreter.Expressions
                 var lprim = TypeConverter.ToPrimitive(left);
                 var rprim = TypeConverter.ToPrimitive(right);
                 return lprim.IsString() || rprim.IsString()
-                    ? (JsValue) JsString.Create(TypeConverter.ToString(lprim) + TypeConverter.ToString(rprim))
+                    ? JsString.Create(TypeConverter.ToString(lprim) + TypeConverter.ToString(rprim))
                     : JsNumber.Create(TypeConverter.ToNumber(lprim) + TypeConverter.ToNumber(rprim));
             }
         }
@@ -272,7 +271,7 @@ namespace Jint.Runtime.Interpreter.Expressions
             {
                 var left = _left.GetValue();
                 var right = _right.GetValue();
-                
+
                 return AreIntegerOperands(left, right)
                     ? JsNumber.Create(left.AsInteger() - right.AsInteger())
                     : JsNumber.Create(TypeConverter.ToNumber(left) - TypeConverter.ToNumber(right));
@@ -289,7 +288,7 @@ namespace Jint.Runtime.Interpreter.Expressions
             {
                 var left = _left.GetValue();
                 var right = _right.GetValue();
-                
+
                 if (AreIntegerOperands(left, right))
                 {
                     return JsNumber.Create((long) left.AsInteger() * right.AsInteger());
@@ -352,7 +351,7 @@ namespace Jint.Runtime.Interpreter.Expressions
             {
                 var leftValue = _left.GetValue();
                 var rightValue = _right.GetValue();
-                
+
                 var left = _leftFirst ? leftValue : rightValue;
                 var right = _leftFirst ? rightValue : leftValue;
 
@@ -371,15 +370,10 @@ namespace Jint.Runtime.Interpreter.Expressions
 
             protected override object EvaluateInternal()
             {
-                var left = _left.GetValue();
-                var right = _right.GetValue();
-
-                if (!(right is FunctionInstance f))
-                {
-                    return ExceptionHelper.ThrowTypeError<JsValue>(_engine, "instanceof can only be used with a function object");
-                }
-
-                return f.HasInstance(left) ? JsBoolean.True : JsBoolean.False;
+                var value = _left.GetValue();
+                return value.InstanceofOperator(_right.GetValue())
+                    ? JsBoolean.True
+                    : JsBoolean.False;
             }
         }
 
@@ -464,7 +458,7 @@ namespace Jint.Runtime.Interpreter.Expressions
                 {
                     int leftValue = left.AsInteger();
                     int rightValue = right.AsInteger();
-                    
+
                     switch (_operator)
                     {
                         case BinaryOperator.BitwiseAnd:
@@ -490,9 +484,9 @@ namespace Jint.Runtime.Interpreter.Expressions
                             return ExceptionHelper.ThrowArgumentOutOfRangeException<object>(nameof(_operator),
                                 "unknown shift operator");
                     }
-  
+
                 }
-                
+
                 return EvaluateNonInteger(left, right);
             }