mirror of
https://github.com/hanzoai/better-sqlite3.git
synced 2026-09-24 16:57:15 +00:00
fixed overly strict regression in parameter binding
This commit is contained in:
+10
-6
@@ -41,16 +41,20 @@ private:
|
||||
return Napi::Value(env, proto);
|
||||
}
|
||||
|
||||
// An object is "plain" if its prototype is null or Object.prototype. Unlike
|
||||
// the original V8 implementation, we cannot look up Object.prototype within
|
||||
// the object's creation context (Node-API has no such concept), so plain
|
||||
// objects from other contexts (e.g., the "vm" module) are not recognized.
|
||||
// An object is "plain" if its prototype is null or a top-level prototype
|
||||
// (i.e., some realm's Object.prototype). Rather than comparing against the
|
||||
// current realm's Object.prototype -- which would reject plain objects from
|
||||
// other contexts, such as the "vm" module -- we walk to the top of the
|
||||
// prototype chain. A plain object's chain is exactly "obj -> proto -> null",
|
||||
// so its immediate prototype must itself have a null prototype. This mirrors
|
||||
// the cross-realm heuristic used by lodash's isPlainObject.
|
||||
static bool IsPlainObject(Napi::Env env, Napi::Object obj) {
|
||||
Napi::Value proto = GetPrototype(env, obj);
|
||||
if (proto.IsEmpty()) return false;
|
||||
if (proto.IsNull()) return true;
|
||||
Napi::Value baseProto = GetPrototype(env, Napi::Object::New(env));
|
||||
return !baseProto.IsEmpty() && proto.StrictEquals(baseProto);
|
||||
Napi::Value grandproto = GetPrototype(env, proto.As<Napi::Object>());
|
||||
if (grandproto.IsEmpty()) return false;
|
||||
return grandproto.IsNull();
|
||||
}
|
||||
|
||||
void Fail(Napi::Value (*Throw)(Napi::Env, const char*), Napi::Env env, const char* message) {
|
||||
|
||||
@@ -81,6 +81,46 @@ describe('Statement#bind()', function () {
|
||||
stmt = this.db.prepare('INSERT INTO entries VALUES (@a, @a, ?)');
|
||||
stmt.bind({ a: '123', b: null }, null);
|
||||
});
|
||||
it('should not treat class instances as plain objects', function () {
|
||||
const stmt = this.db.prepare('INSERT INTO entries VALUES (@a, @a, ?)');
|
||||
expect(() => stmt.bind(new (class { get a() { return "123"; } })(), null)).to.throw(TypeError);
|
||||
expect(() => stmt.bind(Object.assign(new Date(), { a: 123 }), null)).to.throw(TypeError);
|
||||
expect(() => stmt.bind(Object.assign(new Number(123), { a: 123 }), null)).to.throw(TypeError);
|
||||
expect(() => stmt.bind(Object.assign(() => {}, { a: 123 }), null)).to.throw(TypeError);
|
||||
});
|
||||
it('should not treat class instances from other realms as plain objects', function () {
|
||||
const vm = require('vm');
|
||||
const context = vm.createContext({});
|
||||
|
||||
// A class instance from another realm is not a plain object, so passing it
|
||||
// where named parameters are expected must throw.
|
||||
const foreignInstance = vm.runInContext('(new (class { get a() { return "123"; } })())', context);
|
||||
const stmt = this.db.prepare('INSERT INTO entries VALUES (@a, @a, ?)');
|
||||
expect(() => stmt.bind(foreignInstance, null)).to.throw(TypeError);
|
||||
});
|
||||
it('should recognize plain objects created in other realms', function () {
|
||||
const vm = require('vm');
|
||||
const context = vm.createContext({});
|
||||
|
||||
// A plain object created in another realm has a different Object.prototype
|
||||
// identity, but should still be recognized as a plain object.
|
||||
const foreignPlainObject = vm.runInContext('({ a: "123", b: null })', context);
|
||||
expect(Object.getPrototypeOf(foreignPlainObject)).to.not.equal(Object.prototype);
|
||||
|
||||
const stmt = this.db.prepare('INSERT INTO entries VALUES (@a, @a, ?)');
|
||||
stmt.bind(foreignPlainObject, null);
|
||||
stmt.run();
|
||||
const row = this.db.prepare('SELECT * FROM entries WHERE rowid=1').get();
|
||||
expect(row.a).to.equal('123');
|
||||
|
||||
// An object created via Object.create(null) in another realm is plain too.
|
||||
const foreignNullProtoObject = vm.runInContext('Object.assign(Object.create(null), { a: "456", b: null })', context);
|
||||
const stmt2 = this.db.prepare('INSERT INTO entries VALUES (@a, @a, ?)');
|
||||
stmt2.bind(foreignNullProtoObject, null);
|
||||
stmt2.run();
|
||||
const row2 = this.db.prepare('SELECT * FROM entries WHERE rowid=2').get();
|
||||
expect(row2.a).to.equal('456');
|
||||
});
|
||||
it('should propagate exceptions thrown while accessing array/object members', function () {
|
||||
const arr = [22];
|
||||
const obj = {};
|
||||
|
||||
Reference in New Issue
Block a user