diff --git a/Jint.Tests/Runtime/ProxyTests.cs b/Jint.Tests/Runtime/ProxyTests.cs index 19b3d26b9..d97530ee5 100644 --- a/Jint.Tests/Runtime/ProxyTests.cs +++ b/Jint.Tests/Runtime/ProxyTests.cs @@ -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(() => _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(() => _engine.Evaluate("Object.setPrototypeOf(p, null)")); + Assert.Same(TypeErrorPrototype(_engine), ex.Error.AsObject().Prototype); + } + [Fact] public void ClrPropertySideEffect() { diff --git a/Jint/Native/JsProxy.cs b/Jint/Native/JsProxy.cs index 14b0f22fa..ccb0a3e71 100644 --- a/Jint/Native/JsProxy.cs +++ b/Jint/Native/JsProxy.cs @@ -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; } /// @@ -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); }