Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions Jint.Tests/Runtime/ProxyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,68 @@ public void ProxyHandlerSetInvariantsReturnsFalseInNonStrictMode()
""");
}

// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof
[Fact]
public void ProxyHandlerGetPrototypeOfCanReturnNull()
{
Assert.True(_engine.Evaluate("Object.getPrototypeOf(new Proxy({}, { getPrototypeOf: () => null }))").IsNull());
Assert.True(_engine.Evaluate("Reflect.getPrototypeOf(new Proxy({}, { getPrototypeOf: () => null }))").IsNull());
}

[Fact]
public void ProxyHandlerGetPrototypeOfCanReturnNullForNonExtensibleTargetWithNullPrototype()
{
Assert.True(_engine.Evaluate("""
const t = Object.create(null);
Object.preventExtensions(t);
Object.getPrototypeOf(new Proxy(t, { getPrototypeOf: () => null }));
""").IsNull());
}

// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-setprototypeof-v
[Fact]
public void ProxyHandlerSetPrototypeOfCanSetNullOnNonExtensibleTargetWithNullPrototype()
{
_engine.Execute("""
let t = Object.create(null);
Object.preventExtensions(t);
""");

Assert.True(_engine.Evaluate("let p1 = new Proxy(t, {}); Object.setPrototypeOf(p1, null) === p1").AsBoolean());
Assert.True(_engine.Evaluate("let p2 = new Proxy(t, { setPrototypeOf: () => true }); Object.setPrototypeOf(p2, null) === p2").AsBoolean());
Assert.True(_engine.Evaluate("Reflect.setPrototypeOf(new Proxy(t, {}), null)").AsBoolean());
Assert.True(_engine.Evaluate("Reflect.setPrototypeOf(new Proxy(t, { setPrototypeOf: () => true }), null)").AsBoolean());
}

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/getPrototypeOf#invariants
// If the target object is not extensible, the prototype reported
// by the trap must be the same as the target object's prototype.
[Fact]
public void ProxyHandlerGetPrototypeOfInvariantsNonExtensibleTargetDifferentPrototype()
{
_engine.Execute("""
let t = {};
Object.preventExtensions(t);
let p = new Proxy(t, { getPrototypeOf: () => ({}) });
""");
var ex = Assert.Throws<JavaScriptException>(() => _engine.Evaluate("Object.getPrototypeOf(p)"));
Assert.Same(TypeErrorPrototype(_engine), ex.Error.AsObject().Prototype);
}

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/setPrototypeOf#invariants
// If the target object is not extensible, the prototype cannot be changed.
[Fact]
public void ProxyHandlerSetPrototypeOfInvariantsNonExtensibleTargetDifferentPrototype()
{
_engine.Execute("""
let t = {};
Object.preventExtensions(t);
let p = new Proxy(t, { setPrototypeOf: () => true });
""");
var ex = Assert.Throws<JavaScriptException>(() => _engine.Evaluate("Object.setPrototypeOf(p, null)"));
Assert.Same(TypeErrorPrototype(_engine), ex.Error.AsObject().Prototype);
}

[Fact]
public void ClrPropertySideEffect()
{
Expand Down
12 changes: 8 additions & 4 deletions Jint/Native/JsProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -537,17 +537,19 @@ public override bool Extensible
Throw.TypeError(_engine.Realm, "'getPrototypeOf' on proxy: trap returned neither object nor null");
}

var proto = handlerProto.IsNull() ? null : (ObjectInstance) handlerProto;

if (_target.Extensible)
{
return (ObjectInstance) handlerProto;
return proto;
}

if (!ReferenceEquals(handlerProto, _target.Prototype))
if (!ReferenceEquals(proto, _target.Prototype))
{
Throw.TypeError(_engine.Realm);
}

return (ObjectInstance) handlerProto;
return proto;
}

/// <summary>
Expand All @@ -572,7 +574,9 @@ internal override bool SetPrototypeOf(JsValue value)
return true;
}

if (!ReferenceEquals(value, _target.Prototype))
// callers have validated that value is either an object or null
var proto = value.IsNull() ? null : value as ObjectInstance;
if (!ReferenceEquals(proto, _target.Prototype))
{
Throw.TypeError(_engine.Realm);
}
Expand Down
Loading