Skip to content

Commit

Permalink
built-ins/Proxy 134/306 (43.79%)
Browse files Browse the repository at this point in the history
  • Loading branch information
rbri committed Aug 16, 2023
1 parent 9ba8fa1 commit 64b4cf3
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 89 deletions.
4 changes: 2 additions & 2 deletions src/org/mozilla/javascript/Interpreter.java
Original file line number Diff line number Diff line change
Expand Up @@ -1909,12 +1909,12 @@ private static Object interpretLoop(Context cx, CallFrame frame, Object throwabl
continue StateLoop;
}
}
if (!(lhs instanceof Function)) {
if (!(lhs instanceof Constructable)) {
if (lhs == DBL_MRK)
lhs = ScriptRuntime.wrapNumber(sDbl[stackTop]);
throw ScriptRuntime.notFunctionError(lhs);
}
Function fun = (Function) lhs;
Constructable fun = (Constructable) lhs;

if (fun instanceof IdFunctionObject) {
IdFunctionObject ifun = (IdFunctionObject) fun;
Expand Down
4 changes: 4 additions & 0 deletions src/org/mozilla/javascript/LambdaConstructor.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ public LambdaConstructor(
this.flags = flags;
}

protected Constructable getTargetConstructor() {
return targetConstructor;
}

@Override
public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
if ((flags & CONSTRUCTOR_FUNCTION) == 0) {
Expand Down
151 changes: 69 additions & 82 deletions src/org/mozilla/javascript/NativeProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
*
* @author Ronald Brill
*/
final class NativeProxy extends IdScriptableObject implements Callable {
final class NativeProxy extends ScriptableObject implements Callable, Constructable {
private static final long serialVersionUID = 6676871870513494844L;

private static final Object PROXY_TAG = "Proxy";
private static final String PROXY_TAG = "Proxy";

private static final String TRAP_GET_PROTOTYPE_OF = "getPrototypeOf";
private static final String TRAP_SET_PROTOTYPE_OF = "setPrototypeOf";
Expand All @@ -30,7 +30,6 @@ final class NativeProxy extends IdScriptableObject implements Callable {
private static final String TRAP_APPLY = "apply";
private static final String TRAP_CONSTRUCT = "construct";

private final boolean isCtor;
private ScriptableObject target;
private Scriptable handler;
private final String typeOf;
Expand All @@ -54,16 +53,40 @@ public Object call(Context cx, Scriptable scope, Scriptable thisObj,
}
}

static void init(Scriptable scope, boolean sealed) {
NativeProxy constructor = new NativeProxy(null, null, true);
IdFunctionObject ctor = constructor.exportAsJSClass(MAX_PROTOTYPE_ID, scope, false);
ctor.setPrototypeProperty(null);

public static void init(Context cx, Scriptable scope, boolean sealed) {
LambdaConstructor constructor =
new LambdaConstructor(
scope,
PROXY_TAG,
2,
LambdaConstructor.CONSTRUCTOR_NEW,
NativeProxy::constructor) {

@Override
public Scriptable construct(Context cx, Scriptable scope, Object[] args) {
NativeProxy obj = (NativeProxy) getTargetConstructor().construct(cx, scope, args);
// avoid getting trapped
obj.setPrototypeDirect(getClassPrototype());
obj.setParentScope(scope);
return obj;
}
};
// constructor.setPrototypePropertyAttributes(DONTENUM | READONLY | PERMANENT);
constructor.setPrototypeProperty(null);

constructor.defineConstructorMethod(
scope, "revocable", 2, NativeProxy::revocable, DONTENUM, DONTENUM | READONLY);

ScriptableObject.defineProperty(scope, PROXY_TAG, constructor, DONTENUM);
if (sealed) {
constructor.sealObject();
}
}

private NativeProxy(ScriptableObject target, Scriptable handler, boolean isCtor) {
private NativeProxy(ScriptableObject target, Scriptable handler) {
this.target = target;
this.handler = handler;
this.isCtor = isCtor;

if (target == null || !(target instanceof Callable)) {
typeOf = super.getTypeOf();
Expand All @@ -75,52 +98,24 @@ private NativeProxy(ScriptableObject target, Scriptable handler, boolean isCtor)

@Override
public String getClassName() {
if (isCtor) {
return "Proxy";
}

assertNotRevoked();
return target.getClassName();
}

@Override
protected void initPrototypeId(int id) {
if (id <= MAX_PROTOTYPE_ID) {
String name;
int arity;
switch (id) {
case Id_constructor:
arity = 2;
name = "constructor";
break;

default:
throw new IllegalStateException(String.valueOf(id));
}
initPrototypeMethod(PROXY_TAG, id, name, arity);
}
}
public Scriptable construct(Context cx, Scriptable scope, Object[] args) {
assertNotRevoked();

@Override
public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
if (!f.hasTag(PROXY_TAG)) {
return super.execIdCall(f, cx, scope, thisObj, args);
Callable trap = getTrap(TRAP_CONSTRUCT);
if (trap != null) {
Object result = callTrap(trap, new Object[] {target, args, this});
if (!(result instanceof Scriptable) || ScriptRuntime.isSymbol(result)) {
throw ScriptRuntime.typeError("Constructor trap has to return a scriptable.");
}
return (ScriptableObject) result;
}

int methodId = f.methodId();
switch (methodId) {
case Id_constructor:
if (thisObj != null && cx.getLanguageVersion() >= Context.VERSION_ES6) {
throw ScriptRuntime.typeErrorById("msg.only.from.new", getClassName());
}
return js_constructor(cx, scope, args);

case ConstructorId_revocable:
return js_revocable(cx, scope, args);

default:
throw new IllegalStateException(String.valueOf(methodId));
}
return ((Constructable) target).construct(cx, scope, args);
}

@Override
Expand Down Expand Up @@ -161,10 +156,6 @@ public boolean has(Symbol key, Scriptable start) {

@Override
public Object[] getIds() {
if (isCtor) {
return super.getIds();
}

assertNotRevoked();

Callable trap = getTrap(TRAP_OWN_KEYS);
Expand Down Expand Up @@ -383,6 +374,23 @@ public Scriptable getPrototype() {
return target.getPrototype();
}

private void setPrototypeDirect(Scriptable prototype) {
super.setPrototype(prototype);
}

@Override
public void setPrototype(Scriptable prototype) {
assertNotRevoked();

Callable trap = getTrap(TRAP_SET_PROTOTYPE_OF);
if (trap != null) {
callTrap(trap, new Object[] {target, prototype});
return;
}

target.setPrototype(prototype);
}

@Override
public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
assertNotRevoked();
Expand All @@ -397,7 +405,7 @@ public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] ar
return ScriptRuntime.applyOrCall(true, cx, scope, target, new Object[] {thisObj, argumentsList});
}

private NativeProxy js_constructor(Context cx, Scriptable scope, Object[] args) {
private static NativeProxy constructor(Context cx, Scriptable scope, Object[] args) {
if (args.length < 2) {
throw ScriptRuntime.typeErrorById(
"msg.method.missing.parameter",
Expand All @@ -411,14 +419,18 @@ private NativeProxy js_constructor(Context cx, Scriptable scope, Object[] args)
s = ScriptRuntime.toObject(cx, scope, args[1]);
ScriptableObject hndlr = ensureScriptableObject(s);

NativeProxy proxy = new NativeProxy(trgt, hndlr, false);
proxy.setPrototype(ScriptableObject.getClassPrototype(scope, proxy.getClassName()));
NativeProxy proxy = new NativeProxy(trgt, hndlr);
proxy.setPrototypeDirect(ScriptableObject.getClassPrototype(scope, PROXY_TAG));
proxy.setParentScope(scope);
return proxy;
}

private NativeObject js_revocable(Context cx, Scriptable scope, Object[] args) {
NativeProxy proxy = js_constructor(cx, scope, args);
// Proxy.revocable
private static Object revocable(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
if (!ScriptRuntime.isObject(thisObj)) {
throw ScriptRuntime.typeErrorById("msg.arg.not.object", ScriptRuntime.typeof(thisObj));
}
NativeProxy proxy = constructor(cx, scope, args);

NativeObject revocable = (NativeObject) cx.newObject(scope);

Expand Down Expand Up @@ -447,33 +459,8 @@ private Object callTrap(Callable trap, Object[] args) {
}

private void assertNotRevoked() {
if (!isCtor && target == null) {
if (target == null) {
throw ScriptRuntime.typeError("Illegal operation attempted on a revoked proxy");
}
}

@Override
protected int findPrototypeId(String s) {
int id;
switch (s) {
case "constructor":
id = Id_constructor;
break;

default:
id = 0;
break;
}
return id;
}

@Override
protected void fillConstructorProperties(IdFunctionObject ctor) {
addIdFunctionProperty(ctor, PROXY_TAG, ConstructorId_revocable, "revocable", 2);
super.fillConstructorProperties(ctor);
}

private static final int ConstructorId_revocable = -1,
Id_constructor = 1,
MAX_PROTOTYPE_ID = Id_constructor;
}
6 changes: 3 additions & 3 deletions src/org/mozilla/javascript/ScriptRuntime.java
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ public static ScriptableObject initSafeStandardObjects(
NativeWeakSet.init(scope, sealed);
NativeBigInt.init(scope, sealed);

NativeProxy.init(scope, sealed);
NativeProxy.init(cx, scope, sealed);
NativeReflect.init(scope, sealed);
}

Expand Down Expand Up @@ -2683,10 +2683,10 @@ public static Ref callRef(Callable function, Scriptable thisObj, Object[] args,
* <p>See ECMA 11.2.2
*/
public static Scriptable newObject(Object fun, Context cx, Scriptable scope, Object[] args) {
if (!(fun instanceof Function)) {
if (!(fun instanceof Constructable)) {
throw notFunctionError(fun);
}
Function function = (Function) fun;
Constructable function = (Constructable) fun;
return function.construct(cx, scope, args);
}

Expand Down
51 changes: 49 additions & 2 deletions testsrc/org/mozilla/javascript/tests/es6/NativeProxyTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class NativeProxyTest {

@Test
public void testToString() {
testString("function Proxy() { [native code for Proxy.Proxy, arity=2] }\n", "Proxy.toString()");
testString("function Proxy() {\n\t[native code, arity=2]\n}\n", "Proxy.toString()");

testString("[object Object]", "Object.prototype.toString.call(new Proxy({}, {}))");
testString("[object Array]", "Object.prototype.toString.call(new Proxy([], {}))");
Expand Down Expand Up @@ -51,7 +51,35 @@ public void ctorMissingArgs() {

@Test
public void ctorAsFunction() {
testString("TypeError: \"Constructor Proxy\" may only be invoked from a \"new\" expression.", "try { Proxy() } catch(e) { '' + e }");
testString("TypeError: The constructor for Proxy may not be invoked as a function", "try { Proxy() } catch(e) { '' + e }");
}

@Test
public void construct() {
String js =
"var _target, _handler, _args, _P;\n"

+ "function Target() {}\n"

+ "var handler = {\n"
+ " construct: function(t, args, newTarget) {\n"
+ " _handler = this;\n"
+ " _target = t;\n"
+ " _args = args;\n"
+ " _P = newTarget;\n"
+ " return new t(args[0], args[1]);\n"
+ " }\n"
+ "};\n"

+ "var P = new Proxy(Target, handler);\n"

+ "new P(1, 4);\n"
+ "'' + (_handler === handler)\n"
+ "+ ' ' + (_target === Target)"
+ "+ ' ' + (_P === P)"
+ "+ ' ' + _args.length + ' ' + _args[0] + ' ' + _args[1]";

testString("true true true 2 1 4", js);
}

@Test
Expand Down Expand Up @@ -600,6 +628,18 @@ public void getPropertyWithoutHandler() {
testString("value 1, undefined, foo, 42, undefined", js);
}

@Test
public void getPrototypeOfNull() {
String js =
"var plainObjectTarget = new Proxy(Object.create(null), {});\n"
+ "var plainObjectProxy = new Proxy(plainObjectTarget, {\n"
+ " getPrototypeOf: null,\n"
+ "});\n"
+ "'' + Object.getPrototypeOf(plainObjectProxy);\n";
testString("null", js);
}


@Test
public void setPrototypeOfWithoutHandler() {
String js =
Expand Down Expand Up @@ -656,6 +696,13 @@ public void typeof() {
public void typeofRevocable() {
testString("object", "var rev = Proxy.revocable({}, {}); rev.revoke(); typeof rev.proxy");
testString("function", "var rev = Proxy.revocable(function() {}, {}); rev.revoke(); typeof rev.proxy");

String js = "var revocableTarget = Proxy.revocable(function() {}, {});\n"
+ "revocableTarget.revoke();\n"

+ "var revocable = Proxy.revocable(revocableTarget.proxy, {});\n"
+ "'' + typeof revocable.proxy;\n";
testString("function", js);
}

@Test
Expand Down

0 comments on commit 64b4cf3

Please sign in to comment.