mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-28 06:53:42 +08:00
Check comparison operands and walk instanceof chains as Node does
- The assert comparison methods read as function values (equal, strictEqual, deepEqual, partialDeepStrictEqual and their negations) throw Node's ERR_MISSING_ARGS TypeError, `The "actual" and "expected" arguments must be specified`, when called with fewer than two arguments, where `[assert.equal][0]()` passed. - Untyped `instanceof Object` starts at the value's prototype, so Object.prototype itself, whose prototype is null, is not an Object. - Untyped `instanceof Function` follows the whole prototype chain through objects and functions: an object whose prototype is a function, or a function whose prototype was set to such an object, is a Function.
This commit is contained in:
@@ -3066,8 +3066,18 @@ static ScrDyn *scr_av_fail(ScrClosure *c, ScrDyn *const *args, size_t argc) {
|
||||
return scr_av_done(msg);
|
||||
}
|
||||
|
||||
/* The comparison methods take both operands: Node's ERR_MISSING_ARGS
|
||||
* TypeError otherwise. False with it pending. */
|
||||
static bool scr_av_operands(size_t argc) {
|
||||
if (argc >= 2) return true;
|
||||
static const char text[] = "The \"actual\" and \"expected\" arguments must be specified";
|
||||
scr_throw_error_msg_code(SCR_ERR_TYPE, text, sizeof text - 1, "ERR_MISSING_ARGS");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* equal / notEqual: Node's loose comparison, NaN equal to NaN. */
|
||||
static ScrDyn *scr_av_loose(ScrDyn *const *args, size_t argc, bool negated) {
|
||||
if (!scr_av_operands(argc)) return NULL;
|
||||
const ScrDyn *a = scr_av_arg(args, argc, 0), *b = scr_av_arg(args, argc, 1);
|
||||
bool nan = a->kind == SCR_DYN_NUM && b->kind == SCR_DYN_NUM && isnan(a->v.num) && isnan(b->v.num);
|
||||
bool equal = nan || scr_dyn_abstract_eq(a, b);
|
||||
@@ -3097,6 +3107,7 @@ static ScrDyn *scr_av_not_equal(ScrClosure *c, ScrDyn *const *a, size_t n) {
|
||||
enum { SCR_AV_STRICT, SCR_AV_DEEP_STRICT, SCR_AV_DEEP, SCR_AV_PARTIAL };
|
||||
|
||||
static ScrDyn *scr_av_compare(ScrDyn *const *args, size_t argc, int form, bool negated) {
|
||||
if (!scr_av_operands(argc)) return NULL;
|
||||
ScrDyn *a = (ScrDyn *)scr_av_arg(args, argc, 0), *b = (ScrDyn *)scr_av_arg(args, argc, 1);
|
||||
ScrStr *msg;
|
||||
bool has;
|
||||
|
||||
@@ -4684,12 +4684,18 @@ static bool scr_dyn_instanceof_object(const ScrDyn *value) {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
/* Object.prototype is on the chain from the value's own prototype on:
|
||||
* Object.prototype itself has a null prototype, so it is not an Object. */
|
||||
ScrDyn *current = scr_dyn_retain((ScrDyn *)value);
|
||||
for (;;) {
|
||||
if (current == scr_builtin_object_prototype) {
|
||||
for (bool first = true;; first = false) {
|
||||
if (!first && current == scr_builtin_object_prototype) {
|
||||
scr_dyn_release(current);
|
||||
return true;
|
||||
}
|
||||
if (current == scr_builtin_object_prototype) {
|
||||
scr_dyn_release(current);
|
||||
return false;
|
||||
}
|
||||
ScrDyn *next = NULL;
|
||||
if (current->kind == SCR_DYN_FUNC) {
|
||||
/* A function's prototype is Function.prototype, whose own is
|
||||
@@ -4719,15 +4725,53 @@ static bool scr_dyn_instanceof_object(const ScrDyn *value) {
|
||||
}
|
||||
}
|
||||
|
||||
/* `value instanceof Function`: every function, unless the program set its
|
||||
* prototype to null or to a non-function. */
|
||||
/* `value instanceof Function`: whether a function is on the value's
|
||||
* prototype chain. A function's own prototype is Function.prototype unless
|
||||
* the program set it, so the walk ends true at the first function that
|
||||
* kept its prototype; it follows set prototypes through functions and
|
||||
* objects alike (`Object.create(function () {})` is a Function instance),
|
||||
* and ends false at null or at a value without a chain the runtime walks.
|
||||
* False with the exception pending when a prototype read throws. */
|
||||
static bool scr_dyn_instanceof_function(const ScrDyn *value) {
|
||||
if (value->kind != SCR_DYN_FUNC) return false;
|
||||
if (!scr_closure_identity(value->v.fn.clo)->props) return true;
|
||||
ScrDyn *table = scr_dyn_fn_properties((ScrDyn *)value);
|
||||
bool result = table->prototype ? scr_dyn_instanceof_function(table->prototype) : !table->null_proto;
|
||||
scr_dyn_release(table);
|
||||
return result;
|
||||
ScrDyn *current = NULL;
|
||||
if (value->kind == SCR_DYN_FUNC) {
|
||||
if (!scr_closure_identity(value->v.fn.clo)->props) return true;
|
||||
ScrDyn *table = scr_dyn_fn_properties((ScrDyn *)value);
|
||||
current = table->prototype ? scr_dyn_retain(table->prototype) : NULL;
|
||||
bool unset = !table->prototype && !table->null_proto;
|
||||
scr_dyn_release(table);
|
||||
if (unset) return true;
|
||||
} else if (value->kind == SCR_DYN_OBJ || value->kind == SCR_DYN_ARR) {
|
||||
current = scr_dyn_get_prototype((ScrDyn *)value);
|
||||
if (!current) return false; /* the read threw: pending */
|
||||
}
|
||||
while (current && current->kind != SCR_DYN_NULL) {
|
||||
ScrDyn *next = NULL;
|
||||
if (current->kind == SCR_DYN_FUNC) {
|
||||
if (!scr_closure_identity(current->v.fn.clo)->props) {
|
||||
scr_dyn_release(current);
|
||||
return true;
|
||||
}
|
||||
ScrDyn *table = scr_dyn_fn_properties(current);
|
||||
bool unset = !table->prototype && !table->null_proto;
|
||||
next = table->prototype ? scr_dyn_retain(table->prototype) : NULL;
|
||||
scr_dyn_release(table);
|
||||
if (unset) {
|
||||
scr_dyn_release(current);
|
||||
return true;
|
||||
}
|
||||
} else if (current->kind == SCR_DYN_OBJ || current->kind == SCR_DYN_ARR) {
|
||||
next = scr_dyn_get_prototype(current);
|
||||
if (!next) {
|
||||
scr_dyn_release(current);
|
||||
return false; /* the read threw: pending */
|
||||
}
|
||||
}
|
||||
scr_dyn_release(current);
|
||||
current = next;
|
||||
}
|
||||
if (current) scr_dyn_release(current);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool scr_bytes_instanceof_at(const ScrDyn *value, const ScrDyn *callee, const ScrStr *refusal) {
|
||||
|
||||
@@ -53,3 +53,25 @@ function describe(value) {
|
||||
return value instanceof Function ? 'function' : 'object';
|
||||
}
|
||||
console.log([nullProto, {}, describe, 0].map(describe).join(', '));
|
||||
|
||||
// Prototype chains through functions and objects, and Object.prototype
|
||||
// itself, whose own prototype is null.
|
||||
const base = function base() {};
|
||||
const fromFunction = {};
|
||||
Object.setPrototypeOf(fromFunction, base);
|
||||
const throughObject = {};
|
||||
Object.setPrototypeOf(throughObject, fromFunction);
|
||||
const nullRooted = function nullRooted() {};
|
||||
Object.setPrototypeOf(nullRooted, null);
|
||||
const reparented = function reparented() {};
|
||||
Object.setPrototypeOf(reparented, fromFunction);
|
||||
const chains = {
|
||||
objectPrototype: Object.getPrototypeOf({}),
|
||||
fromFunction,
|
||||
throughObject,
|
||||
nullRooted,
|
||||
reparented,
|
||||
};
|
||||
for (const [label, value] of Object.entries(chains)) {
|
||||
console.log(`${label}: Object ${value instanceof Object}, Function ${value instanceof Function}`);
|
||||
}
|
||||
|
||||
@@ -28,3 +28,18 @@ const checks = [assert.ok, assert.fail, assert.strictEqual];
|
||||
checks.forEach((check, i) => console.log(i, typeof check, check === checks[i], check.name));
|
||||
[1, 'x', true].forEach((value) => assert.ok(value));
|
||||
console.log('done');
|
||||
|
||||
// The comparison methods need both operands, as Node's ERR_MISSING_ARGS
|
||||
// TypeError says, also through function values.
|
||||
const comparisons = [assert.equal, assert.notEqual, assert.strictEqual, assert.notStrictEqual,
|
||||
assert.deepEqual, assert.notDeepEqual, assert.deepStrictEqual, assert.notDeepStrictEqual];
|
||||
for (const compare of comparisons) {
|
||||
for (const args of [[], [1]]) {
|
||||
try {
|
||||
compare(...args);
|
||||
console.log(compare.name, args.length, 'passed');
|
||||
} catch (e) {
|
||||
console.log(compare.name, args.length, e.name, e.code, e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user