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
37 changes: 37 additions & 0 deletions Jint.Tests/Runtime/ProxyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,43 @@ public void ConstructHandlerInvariant()
Assert.True(_engine.Evaluate(Script).AsBoolean());
}

// https://tc39.es/ecma262/#sec-proxycreate
// A proxy only has [[Construct]] if its target is a constructor at creation time;
// a handler "construct" trap cannot confer constructability.
[Fact]
public void ProxyWithNonConstructorTargetIsNotConstructor()
{
var ex = Assert.Throws<JavaScriptException>(() => _engine.Evaluate("new (new Proxy(() => {}, { construct: () => ({}) }))()"));
Assert.Same(TypeErrorPrototype(_engine), ex.Error.AsObject().Prototype);
}

[Fact]
public void ProxyWithConstructorTargetUsesConstructTrap()
{
var result = _engine.Evaluate("new (new Proxy(function(){}, { construct: () => ({ x: 1 }) }))().x");
Assert.Equal(1, result.AsInteger());
}

[Fact]
public void ReflectConstructRequiresConstructorNewTarget()
{
var ex = Assert.Throws<JavaScriptException>(() => _engine.Evaluate("Reflect.construct(function(){ return; }, [], new Proxy(() => {}, { construct: () => ({}) }))"));
Assert.Same(TypeErrorPrototype(_engine), ex.Error.AsObject().Prototype);
}

[Fact]
public void RevokedConstructorProxyKeepsTypeofFunctionButConstructThrowsRevokedError()
{
_engine.Execute("var r = Proxy.revocable(function(){}, {}); r.revoke();");

// typeof relies on the [[Call]] slot captured at creation, revocation does not remove it
Assert.Equal("function", _engine.Evaluate("typeof r.proxy").AsString());

var ex = Assert.Throws<JavaScriptException>(() => _engine.Evaluate("new r.proxy()"));
Assert.Same(TypeErrorPrototype(_engine), ex.Error.AsObject().Prototype);
Assert.Contains("revoked", ex.Message);
}

[Fact]
public void ProxyHandlerGetDataPropertyShouldNotUseReferenceEquals()
{
Expand Down
38 changes: 17 additions & 21 deletions Jint/Native/JsProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ internal sealed class JsProxy : ObjectInstance, IConstructor, ICallable
internal ObjectInstance _target;
internal ObjectInstance? _handler;

// https://tc39.es/ecma262/#sec-proxycreate
// A proxy has [[Construct]] iff its target is a constructor at creation time;
// a handler "construct" trap cannot confer constructability. Captured here so
// IsConstructor probes are side-effect free (no handler getter invocation) and
// remain correct after revocation nulls the target.
private readonly bool _isConstructor;

private static readonly JsString TrapApply = new JsString("apply");
private static readonly JsString TrapGet = new JsString("get");
private static readonly JsString TrapSet = new JsString("set");
Expand All @@ -36,16 +43,19 @@ public JsProxy(
_target = target;
_handler = handler;
IsCallable = target.IsCallable;
_isConstructor = target.IsConstructor;
}

/// <summary>
/// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-call-thisargument-argumentslist
/// </summary>
JsValue ICallable.Call(JsValue thisObject, params JsCallArguments arguments)
{
if (_target is not ICallable)
AssertNotRevoked(TrapApply);

if (!IsCallable)
{
Throw.TypeError(_engine.Realm, "(intermediate value) is not a function");
Throw.TypeError(_engine.Realm, "Proxy target is not a function");
}

var jsValues = new[] { _target, thisObject, _engine.Realm.Intrinsics.Array.ConstructFast(arguments) };
Expand All @@ -68,9 +78,11 @@ JsValue ICallable.Call(JsValue thisObject, params JsCallArguments arguments)
/// </summary>
ObjectInstance IConstructor.Construct(JsCallArguments arguments, JsValue newTarget)
{
if (_target is not ICallable)
AssertNotRevoked(TrapConstruct);

if (!_isConstructor)
{
Throw.TypeError(_engine.Realm, "(intermediate value) is not a constructor");
Throw.TypeError(_engine.Realm, "Proxy target is not a constructor");
}

var argArray = _engine.Realm.Intrinsics.Array.Construct(arguments, _engine.Realm.Intrinsics.Array);
Expand Down Expand Up @@ -102,23 +114,7 @@ internal override bool IsArray()

public override object ToObject() => _target.ToObject();

internal override bool IsConstructor
{
get
{
if (_target is not null && _target.IsConstructor)
{
return true;
}

if (_handler is not null && _handler.TryGetValue(TrapConstruct, out var handlerFunction) && handlerFunction is IConstructor)
{
return true;
}

return false;
}
}
internal override bool IsConstructor => _isConstructor;

/// <summary>
/// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-get-p-receiver
Expand Down
Loading