Skip to content

Commit

Permalink
FEAT: Implement Symbol.hasInstance
Browse files Browse the repository at this point in the history
  • Loading branch information
0xe committed Dec 9, 2024
1 parent a8696c7 commit d13a4e4
Show file tree
Hide file tree
Showing 7 changed files with 412 additions and 18 deletions.
88 changes: 85 additions & 3 deletions rhino/src/main/java/org/mozilla/javascript/BaseFunction.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ static void init(Context cx, Scriptable scope, boolean sealed) {
if (cx.getLanguageVersion() >= Context.VERSION_ES6) {
obj.setStandardPropertyAttributes(READONLY | DONTENUM);
}
obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed);
IdFunctionObject constructor = obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed);
ScriptRuntimeES6.addSymbolHasInstance(cx, scope, constructor);
}

/**
Expand Down Expand Up @@ -80,7 +81,7 @@ protected boolean hasDefaultParameters() {
/**
* Gets the value returned by calling the typeof operator on this object.
*
* @see org.mozilla.javascript.ScriptableObject#getTypeOf()
* @see ScriptableObject#getTypeOf()
* @return "function" or "undefined" if {@link #avoidObjectDetection()} returns <code>true
* </code>
*/
Expand Down Expand Up @@ -156,6 +157,8 @@ protected int findInstanceIdInfo(String s) {
@Override
protected String getInstanceIdName(int id) {
switch (id) {
case SymbolId_hasInstance:
return "SymbolId_hasInstance";
case Id_length:
return "length";
case Id_arity:
Expand Down Expand Up @@ -265,6 +268,11 @@ protected void fillConstructorProperties(IdFunctionObject ctor) {

@Override
protected void initPrototypeId(int id) {
if (id == SymbolId_hasInstance) {
initPrototypeValue(id, SymbolKey.HAS_INSTANCE, makeHasInstance(), 0x0F);
return;
}

String s;
int arity;
switch (id) {
Expand Down Expand Up @@ -313,6 +321,53 @@ static boolean isApplyOrCall(IdFunctionObject f) {
return false;
}

private Object makeHasInstance() {
Context cx = Context.getCurrentContext();
ScriptableObject obj = null;

if (cx != null) {
Scriptable scope = this.getParentScope();
obj =
new LambdaFunction(
scope,
0,
new Callable() {
@Override
public Object call(
Context cx,
Scriptable scope,
Scriptable thisObj,
Object[] args) {
if (thisObj != null
&& args.length == 1
&& args[0] instanceof Scriptable) {
Scriptable obj = (Scriptable) args[0];
Object protoProp = null;
if (thisObj instanceof BoundFunction)
protoProp =
((NativeFunction)
((BoundFunction) thisObj)
.getTargetFunction())
.getPrototypeProperty();
else
protoProp =
ScriptableObject.getProperty(
thisObj, "prototype");
if (protoProp instanceof IdScriptableObject) {
return ScriptRuntime.jsDelegatesTo(
obj, (Scriptable) protoProp);
}
throw ScriptRuntime.typeErrorById(
"msg.instanceof.bad.prototype", getFunctionName());
} else {
return false; // NOT_FOUND, null etc.
}
}
});
}
return obj;
}

@Override
public Object execIdCall(
IdFunctionObject f, Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
Expand Down Expand Up @@ -509,6 +564,26 @@ protected boolean hasPrototypeProperty() {
return prototypeProperty != null || this instanceof NativeFunction;
}

@Override
ScriptableObject buildDataDescriptorHelper(
int instanceIdInfo, Scriptable scope, Object value, int attr) {
if (instanceIdInfo == SymbolId_hasInstance) {
return buildDataDescriptor(scope, value, attr, SymbolKey.HAS_INSTANCE.toString(), 1);
} else {
return super.buildDataDescriptorHelper(instanceIdInfo, scope, value, attr);
}
}

@Override
ScriptableObject buildDataDescriptorHelper(
Symbol key, Scriptable scope, Object value, int attr) {
if (key == SymbolKey.HAS_INSTANCE) {
return buildDataDescriptor(scope, value, attr, key.toString(), 1);
} else {
return super.buildDataDescriptorHelper(key, scope, value, attr);
}
}

public Object getPrototypeProperty() {
Object result = prototypeProperty;
if (result == null) {
Expand Down Expand Up @@ -627,6 +702,12 @@ private Object jsConstructor(Context cx, Scriptable scope, Object[] args) {
return cx.compileFunction(global, source, evaluator, reporter, sourceURI, 1, null);
}

@Override
protected int findPrototypeId(Symbol k) {
if (SymbolKey.HAS_INSTANCE.equals(k)) return SymbolId_hasInstance;
else return 0;
}

@Override
protected int findPrototypeId(String s) {
int id;
Expand Down Expand Up @@ -662,7 +743,8 @@ protected int findPrototypeId(String s) {
Id_apply = 4,
Id_call = 5,
Id_bind = 6,
MAX_PROTOTYPE_ID = Id_bind;
SymbolId_hasInstance = 7,
MAX_PROTOTYPE_ID = SymbolId_hasInstance;

private Object prototypeProperty;
private Object argumentsObj = NOT_FOUND;
Expand Down
29 changes: 25 additions & 4 deletions rhino/src/main/java/org/mozilla/javascript/IdScriptableObject.java
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,13 @@ final void delete(int id) {
Context cx = Context.getContext();
if (cx.isStrictMode()) {
int nameSlot = (id - 1) * SLOT_SPAN + NAME_SLOT;
String name = (String) valueArray[nameSlot];

String name = null;
if (valueArray[nameSlot] instanceof String)
name = (String) valueArray[nameSlot];
else if (valueArray[nameSlot] instanceof SymbolKey) {
name = valueArray[nameSlot].toString();
}
throw ScriptRuntime.typeErrorById(
"msg.delete.property.with.configurable.false", name);
}
Expand Down Expand Up @@ -936,6 +942,21 @@ protected ScriptableObject getOwnPropertyDescriptor(Context cx, Object id) {
return desc;
}

/**
* Overridden in the base class for different descriptors
*
* @return ScriptableObject
*/
ScriptableObject buildDataDescriptorHelper(
Symbol key, Scriptable scope, Object value, int attr) {
return buildDataDescriptor(scope, value, attr);
}

ScriptableObject buildDataDescriptorHelper(
int instanceIdInfo, Scriptable scope, Object value, int attr) {
return buildDataDescriptor(scope, value, attr);
}

private ScriptableObject getBuiltInDescriptor(String name) {
Object value = null;
int attr = EMPTY;
Expand All @@ -950,14 +971,14 @@ private ScriptableObject getBuiltInDescriptor(String name) {
int id = (info & 0xFFFF);
value = getInstanceIdValue(id);
attr = (info >>> 16);
return buildDataDescriptor(scope, value, attr);
return buildDataDescriptorHelper(info, scope, value, attr);
}
if (prototypeValues != null) {
int id = prototypeValues.findId(name);
if (id != 0) {
value = prototypeValues.get(id);
attr = prototypeValues.getAttributes(id);
return buildDataDescriptor(scope, value, attr);
return buildDataDescriptorHelper(info, scope, value, attr);
}
}
return null;
Expand All @@ -977,7 +998,7 @@ private ScriptableObject getBuiltInDescriptor(Symbol key) {
if (id != 0) {
value = prototypeValues.get(id);
attr = prototypeValues.getAttributes(id);
return buildDataDescriptor(scope, value, attr);
return buildDataDescriptorHelper(key, scope, value, attr);
}
}
return null;
Expand Down
2 changes: 1 addition & 1 deletion rhino/src/main/java/org/mozilla/javascript/Node.java
Original file line number Diff line number Diff line change
Expand Up @@ -1178,7 +1178,7 @@ private void toString(Map<Node, Integer> printIds, StringBuilder sb) {
Object[] a = (Object[]) x.objectValue;
sb.append("[");
for (int i = 0; i < a.length; i++) {
sb.append(a[i].toString());
if (a[i] != null) sb.append(a[i].toString());
if (i + 1 < a.length) sb.append(", ");
}
sb.append("]");
Expand Down
11 changes: 11 additions & 0 deletions rhino/src/main/java/org/mozilla/javascript/ScriptRuntimeES6.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,15 @@ public static void addSymbolUnscopables(
ScriptableObject.putProperty(unScopablesDescriptor, "writable", false);
constructor.defineOwnProperty(cx, SymbolKey.UNSCOPABLES, unScopablesDescriptor, false);
}

/** Registers the symbol <code>[Symbol.hasInstance]</code> on the given constructor function. */
public static void addSymbolHasInstance(
Context cx, Scriptable scope, IdScriptableObject constructor) {
ScriptableObject hasInstanceDescriptor = (ScriptableObject) cx.newObject(scope);
ScriptableObject.putProperty(hasInstanceDescriptor, "value", ScriptableObject.EMPTY);
ScriptableObject.putProperty(hasInstanceDescriptor, "enumerable", false);
ScriptableObject.putProperty(hasInstanceDescriptor, "configurable", false);
ScriptableObject.putProperty(hasInstanceDescriptor, "writable", false);
constructor.defineOwnProperty(cx, SymbolKey.HAS_INSTANCE, hasInstanceDescriptor, false);
}
}
28 changes: 26 additions & 2 deletions rhino/src/main/java/org/mozilla/javascript/ScriptableObject.java
Original file line number Diff line number Diff line change
Expand Up @@ -135,21 +135,39 @@ public abstract class ScriptableObject

protected static ScriptableObject buildDataDescriptor(
Scriptable scope, Object value, int attributes) {
return buildDataDescriptor(scope, value, attributes, null, -1);
}

protected static ScriptableObject buildDataDescriptor(
Scriptable scope, Object value, int attributes, String name, int length) {
ScriptableObject desc = new NativeObject();
ScriptRuntime.setBuiltinProtoAndParent(desc, scope, TopLevel.Builtins.Object);
desc.defineProperty("value", value, EMPTY);
desc.setCommonDescriptorProperties(attributes, true);
desc.setCommonDescriptorProperties(attributes, true, name, length);
return desc;
}

protected void setCommonDescriptorProperties(int attributes, boolean defineWritable) {
protected void setCommonDescriptorProperties(
int attributes, boolean defineWritable, String name, int length) {
if (name != null) {
defineProperty("name", "[Symbol.hasInstance]", attributes);
}

if (length != -1) {
defineProperty("length", length, attributes);
}

if (defineWritable) {
defineProperty("writable", (attributes & READONLY) == 0, EMPTY);
}
defineProperty("enumerable", (attributes & DONTENUM) == 0, EMPTY);
defineProperty("configurable", (attributes & PERMANENT) == 0, EMPTY);
}

protected void setCommonDescriptorProperties(int attributes, boolean defineWritable) {
setCommonDescriptorProperties(attributes, defineWritable, null, -1);
}

static void checkValidAttributes(int attributes) {
final int mask = READONLY | DONTENUM | PERMANENT | UNINITIALIZED_CONST;
if ((attributes & ~mask) != 0) {
Expand Down Expand Up @@ -831,6 +849,12 @@ public boolean hasInstance(Scriptable instance) {
// chasing. This will be overridden in NativeFunction and non-JS
// objects.

Context cx = Context.getCurrentContext();
Object hasInstance = ScriptRuntime.getObjectElem(this, SymbolKey.HAS_INSTANCE, cx);
if (hasInstance instanceof Callable) {
return (boolean)
((Callable) hasInstance).call(cx, getParentScope(), this, new Object[] {this});
}
return ScriptRuntime.jsDelegatesTo(instance, this);
}

Expand Down
Loading

0 comments on commit d13a4e4

Please sign in to comment.