Skip to content

Commit

Permalink
Fix instanceof logic to comply with specification (#884)
Browse files Browse the repository at this point in the history
  • Loading branch information
lahma committed May 4, 2021
1 parent dcaae15 commit d47f552
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 70 deletions.
15 changes: 15 additions & 0 deletions Jint.Tests.Test262/Language/Expressions/InstanceOfTests.cs
Original file line number Diff line number Diff line change
@@ -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 changes: 15 additions & 0 deletions Jint.Tests/Runtime/ArrayTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,5 +114,20 @@ public void ArraySortIsStable()

_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());
}
}
}
8 changes: 4 additions & 4 deletions Jint/Native/Function/BindFunctionInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
}
Expand Down Expand Up @@ -43,21 +43,21 @@ public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
{
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)
Expand Down
29 changes: 0 additions & 29 deletions Jint/Native/Function/FunctionInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,35 +70,6 @@ public abstract class FunctionInstance : ObjectInstance, ICallable

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)
Expand Down
12 changes: 6 additions & 6 deletions Jint/Native/Function/FunctionPrototype.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ protected override void Initialize()
["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)
Expand All @@ -55,12 +55,12 @@ protected override void Initialize()

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)
Expand Down Expand Up @@ -96,7 +96,7 @@ private JsValue Bind(JsValue thisObj, JsValue[] arguments)
}

f._length = new PropertyDescriptor(l, PropertyFlag.Configurable);

var targetName = thisObj.Get(CommonProperties.Name);
if (!targetName.IsString())
{
Expand Down Expand Up @@ -158,7 +158,7 @@ internal JsValue[] CreateListFromArrayLike(JsValue argArray, Types? elementTypes
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;
}
Expand Down
61 changes: 61 additions & 0 deletions Jint/Native/JsValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,30 @@ public virtual bool Set(JsValue property, JsValue value, JsValue receiver)
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";
Expand Down Expand Up @@ -566,6 +590,43 @@ internal virtual JsValue DoClone()

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;
Expand Down
12 changes: 5 additions & 7 deletions Jint/Runtime/Interop/TypeReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,16 +103,14 @@ public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
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)
Expand Down Expand Up @@ -149,7 +147,7 @@ public override PropertyDescriptor GetOwnProperty(JsValue property)
{
return PropertyDescriptor.Undefined;
}

var key = jsString._value;
var descriptor = PropertyDescriptor.Undefined;

Expand Down
42 changes: 18 additions & 24 deletions Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using Esprima.Ast;
using Jint.Native;
using Jint.Native.Function;
using Jint.Native.Object;
using Jint.Runtime.Interop;

Expand Down Expand Up @@ -66,19 +65,19 @@ internal static JintExpression Build(Engine engine, BinaryExpression expression)
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;
Expand Down Expand Up @@ -238,7 +237,7 @@ protected override object EvaluateInternal()
: value;
}
}

private sealed class PlusBinaryExpression : JintBinaryExpression
{
public PlusBinaryExpression(Engine engine, BinaryExpression expression) : base(engine, expression)
Expand All @@ -249,7 +248,7 @@ protected override object EvaluateInternal()
{
var left = _left.GetValue();
var right = _right.GetValue();

if (AreIntegerOperands(left, right))
{
return JsNumber.Create(left.AsInteger() + right.AsInteger());
Expand All @@ -258,7 +257,7 @@ protected override object EvaluateInternal()
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));
}
}
Expand All @@ -272,7 +271,7 @@ protected override object EvaluateInternal()
{
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));
Expand All @@ -289,7 +288,7 @@ protected override object EvaluateInternal()
{
var left = _left.GetValue();
var right = _right.GetValue();

if (AreIntegerOperands(left, right))
{
return JsNumber.Create((long) left.AsInteger() * right.AsInteger());
Expand Down Expand Up @@ -352,7 +351,7 @@ protected override object EvaluateInternal()
{
var leftValue = _left.GetValue();
var rightValue = _right.GetValue();

var left = _leftFirst ? leftValue : rightValue;
var right = _leftFirst ? rightValue : leftValue;

Expand All @@ -371,15 +370,10 @@ public InstanceOfBinaryExpression(Engine engine, BinaryExpression expression) :

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;
}
}

Expand Down Expand Up @@ -464,7 +458,7 @@ protected override object EvaluateInternal()
{
int leftValue = left.AsInteger();
int rightValue = right.AsInteger();

switch (_operator)
{
case BinaryOperator.BitwiseAnd:
Expand All @@ -490,9 +484,9 @@ protected override object EvaluateInternal()
return ExceptionHelper.ThrowArgumentOutOfRangeException<object>(nameof(_operator),
"unknown shift operator");
}

}

return EvaluateNonInteger(left, right);
}

Expand Down

0 comments on commit d47f552

Please sign in to comment.