Skip to content

Commit bf0648c

Browse files
lazergTrevorBurnham
andcommitted
sqlite: address review feedback on backup() validation
Store the DatabaseSync constructor template behind DatabaseSync::GetConstructorTemplate(), matching StatementSync, StatementSyncIterator, Session and SQLTagStore, which all already have one. DatabaseSync was the only class in the file setting its template up inline in Initialize(), which also meant the Environment slot would be reassigned if Initialize() ever ran for more than one realm, leaving instances from the earlier realm failing HasInstance(). This moves SetSideEffectFreeGetter() to the top of the file so the accessor can use it; its body is unchanged. Drop the now-redundant IsObject() check in Backup() so a bad sourceDb reports one message instead of two, and use the wording lib/internal/errors.js generates for ERR_INVALID_ARG_TYPE. Stop discarding an exception thrown by an href getter in ValidateDatabasePath() and replacing it with ERR_INVALID_ARG_TYPE. Cover the remaining unwrap cases in tests: an array, a padded plain object, an object with DatabaseSync.prototype, a StatementSync and a Session, plus the second ValidateDatabasePath() caller reached through backup(db, { href: 'zzz' }). Co-authored-by: Trevor Burnham <trevorburnham@gmail.com> Signed-off-by: Lazizbek Ergashev <lazerg2@gmail.com>
1 parent 30eefae commit bf0648c

4 files changed

Lines changed: 131 additions & 87 deletions

File tree

‎src/node_sqlite.cc‎

Lines changed: 90 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,23 @@ inline MaybeLocal<String> Utf8StringMaybeOneByte(Isolate* isolate,
8282
isolate, input.data(), NewStringType::kNormal, len);
8383
}
8484

85+
static inline void SetSideEffectFreeGetter(
86+
Isolate* isolate,
87+
Local<FunctionTemplate> class_template,
88+
Local<String> name,
89+
FunctionCallback fn) {
90+
Local<FunctionTemplate> getter =
91+
FunctionTemplate::New(isolate,
92+
fn,
93+
Local<Value>(),
94+
v8::Signature::New(isolate, class_template),
95+
/* length */ 0,
96+
ConstructorBehavior::kThrow,
97+
SideEffectType::kHasNoSideEffect);
98+
class_template->InstanceTemplate()->SetAccessorProperty(
99+
name, getter, Local<FunctionTemplate>(), DontDelete);
100+
}
101+
85102
BindingData::BindingData(Realm* realm, Local<Object> wrap)
86103
: BaseObject(realm, wrap) {
87104
MakeWeak();
@@ -1279,11 +1296,17 @@ std::optional<std::string> ValidateDatabasePath(Environment* env,
12791296
} else if (path->IsObject()) { // When is URL
12801297
auto url = path.As<Object>();
12811298
Local<Value> href;
1282-
if (url->Get(env->context(), env->href_string()).ToLocal(&href) &&
1283-
href->IsString()) {
1299+
// Let an exception thrown by the href getter propagate instead of
1300+
// replacing it with ERR_INVALID_ARG_TYPE.
1301+
if (!url->Get(env->context(), env->href_string()).ToLocal(&href)) {
1302+
return std::nullopt;
1303+
}
1304+
if (href->IsString()) {
12841305
Utf8Value location_value(env->isolate(), href.As<String>());
12851306
auto location = location_value.ToStringView();
12861307
if (!has_null_bytes(location)) {
1308+
// A real URL always has a parseable href, but any object with a string
1309+
// href reaches this branch, so the value cannot be assumed to be one.
12871310
if (!ada::can_parse(location)) {
12881311
THROW_ERR_INVALID_URL(env->isolate(), "Invalid URL");
12891312
return std::nullopt;
@@ -1306,6 +1329,62 @@ std::optional<std::string> ValidateDatabasePath(Environment* env,
13061329
return std::nullopt;
13071330
}
13081331

1332+
Local<FunctionTemplate> DatabaseSync::GetConstructorTemplate(Environment* env) {
1333+
Local<FunctionTemplate> tmpl =
1334+
env->sqlite_database_sync_constructor_template();
1335+
if (tmpl.IsEmpty()) {
1336+
Isolate* isolate = env->isolate();
1337+
tmpl = NewFunctionTemplate(isolate, DatabaseSync::New);
1338+
tmpl->InstanceTemplate()->SetInternalFieldCount(
1339+
DatabaseSync::kInternalFieldCount);
1340+
SetProtoMethod(isolate, tmpl, "open", DatabaseSync::Open);
1341+
SetProtoMethod(isolate, tmpl, "close", DatabaseSync::Close);
1342+
SetProtoDispose(isolate, tmpl, DatabaseSync::Dispose);
1343+
SetProtoMethod(isolate, tmpl, "prepare", DatabaseSync::Prepare);
1344+
SetProtoMethod(isolate, tmpl, "exec", DatabaseSync::Exec);
1345+
SetProtoMethod(isolate, tmpl, "function", DatabaseSync::CustomFunction);
1346+
SetProtoMethod(
1347+
isolate, tmpl, "createTagStore", DatabaseSync::CreateTagStore);
1348+
SetProtoMethodNoSideEffect(
1349+
isolate, tmpl, "location", DatabaseSync::Location);
1350+
SetProtoMethod(isolate, tmpl, "aggregate", DatabaseSync::AggregateFunction);
1351+
SetProtoMethod(isolate, tmpl, "createSession", DatabaseSync::CreateSession);
1352+
SetProtoMethod(
1353+
isolate, tmpl, "applyChangeset", DatabaseSync::ApplyChangeset);
1354+
SetProtoMethod(isolate,
1355+
tmpl,
1356+
"enableLoadExtension",
1357+
DatabaseSync::EnableLoadExtension);
1358+
SetProtoMethod(
1359+
isolate, tmpl, "enableDefensive", DatabaseSync::EnableDefensive);
1360+
SetProtoMethod(isolate, tmpl, "loadExtension", DatabaseSync::LoadExtension);
1361+
SetProtoMethod(isolate, tmpl, "serialize", DatabaseSync::Serialize);
1362+
SetProtoMethod(isolate, tmpl, "deserialize", DatabaseSync::Deserialize);
1363+
SetProtoMethod(isolate, tmpl, "setAuthorizer", DatabaseSync::SetAuthorizer);
1364+
SetSideEffectFreeGetter(isolate,
1365+
tmpl,
1366+
FIXED_ONE_BYTE_STRING(isolate, "isOpen"),
1367+
DatabaseSync::IsOpenGetter);
1368+
SetSideEffectFreeGetter(isolate,
1369+
tmpl,
1370+
FIXED_ONE_BYTE_STRING(isolate, "isTransaction"),
1371+
DatabaseSync::IsTransactionGetter);
1372+
SetSideEffectFreeGetter(isolate,
1373+
tmpl,
1374+
FIXED_ONE_BYTE_STRING(isolate, "limits"),
1375+
DatabaseSync::LimitsGetter);
1376+
Local<String> sqlite_type_key =
1377+
FIXED_ONE_BYTE_STRING(isolate, "sqlite-type");
1378+
Local<v8::Symbol> sqlite_type_symbol =
1379+
v8::Symbol::For(isolate, sqlite_type_key);
1380+
Local<String> database_sync_string =
1381+
FIXED_ONE_BYTE_STRING(isolate, "node:sqlite");
1382+
tmpl->InstanceTemplate()->Set(sqlite_type_symbol, database_sync_string);
1383+
env->set_sqlite_database_sync_constructor_template(tmpl);
1384+
}
1385+
return tmpl;
1386+
}
1387+
13091388
void DatabaseSync::New(const FunctionCallbackInfo<Value>& args) {
13101389
Environment* env = Environment::GetCurrent(args);
13111390
if (!args.IsConstructCall()) {
@@ -2422,16 +2501,13 @@ void DatabaseSync::CreateSession(const FunctionCallbackInfo<Value>& args) {
24222501

24232502
void Backup(const FunctionCallbackInfo<Value>& args) {
24242503
Environment* env = Environment::GetCurrent(args);
2425-
if (args.Length() < 1 || !args[0]->IsObject()) {
2426-
THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
2427-
"The \"sourceDb\" argument must be an object.");
2428-
return;
2429-
}
2430-
2431-
if (!env->sqlite_database_sync_constructor_template()->HasInstance(args[0])) {
2504+
// Unlike the other unwrap sites in this file, which rely on V8's signature
2505+
// check for args.This(), this one takes a value out of args[] and so has to
2506+
// check the type itself before unwrapping it.
2507+
if (!DatabaseSync::GetConstructorTemplate(env)->HasInstance(args[0])) {
24322508
THROW_ERR_INVALID_ARG_TYPE(
24332509
env->isolate(),
2434-
"The \"sourceDb\" argument must be a DatabaseSync instance.");
2510+
"The \"sourceDb\" argument must be an instance of DatabaseSync.");
24352511
return;
24362512
}
24372513

@@ -3821,23 +3897,6 @@ SQLTagStore::SQLTagStore(Environment* env,
38213897
MakeWeak();
38223898
}
38233899

3824-
static inline void SetSideEffectFreeGetter(
3825-
Isolate* isolate,
3826-
Local<FunctionTemplate> class_template,
3827-
Local<String> name,
3828-
FunctionCallback fn) {
3829-
Local<FunctionTemplate> getter =
3830-
FunctionTemplate::New(isolate,
3831-
fn,
3832-
Local<Value>(),
3833-
v8::Signature::New(isolate, class_template),
3834-
/* length */ 0,
3835-
ConstructorBehavior::kThrow,
3836-
SideEffectType::kHasNoSideEffect);
3837-
class_template->InstanceTemplate()->SetAccessorProperty(
3838-
name, getter, Local<FunctionTemplate>(), DontDelete);
3839-
}
3840-
38413900
SQLTagStore::~SQLTagStore() {}
38423901

38433902
Local<FunctionTemplate> SQLTagStore::GetConstructorTemplate(Environment* env) {
@@ -4579,63 +4638,14 @@ static void Initialize(Local<Object> target,
45794638
}
45804639
});
45814640
}
4582-
Local<FunctionTemplate> db_tmpl =
4583-
NewFunctionTemplate(isolate, DatabaseSync::New);
4584-
db_tmpl->InstanceTemplate()->SetInternalFieldCount(
4585-
DatabaseSync::kInternalFieldCount);
4586-
env->set_sqlite_database_sync_constructor_template(db_tmpl);
45874641
Local<Object> constants = Object::New(isolate);
45884642

45894643
DefineConstants(constants);
45904644

4591-
SetProtoMethod(isolate, db_tmpl, "open", DatabaseSync::Open);
4592-
SetProtoMethod(isolate, db_tmpl, "close", DatabaseSync::Close);
4593-
SetProtoDispose(isolate, db_tmpl, DatabaseSync::Dispose);
4594-
SetProtoMethod(isolate, db_tmpl, "prepare", DatabaseSync::Prepare);
4595-
SetProtoMethod(isolate, db_tmpl, "exec", DatabaseSync::Exec);
4596-
SetProtoMethod(isolate, db_tmpl, "function", DatabaseSync::CustomFunction);
4597-
SetProtoMethod(
4598-
isolate, db_tmpl, "createTagStore", DatabaseSync::CreateTagStore);
4599-
SetProtoMethodNoSideEffect(
4600-
isolate, db_tmpl, "location", DatabaseSync::Location);
4601-
SetProtoMethod(
4602-
isolate, db_tmpl, "aggregate", DatabaseSync::AggregateFunction);
4603-
SetProtoMethod(
4604-
isolate, db_tmpl, "createSession", DatabaseSync::CreateSession);
4605-
SetProtoMethod(
4606-
isolate, db_tmpl, "applyChangeset", DatabaseSync::ApplyChangeset);
4607-
SetProtoMethod(isolate,
4608-
db_tmpl,
4609-
"enableLoadExtension",
4610-
DatabaseSync::EnableLoadExtension);
4611-
SetProtoMethod(
4612-
isolate, db_tmpl, "enableDefensive", DatabaseSync::EnableDefensive);
4613-
SetProtoMethod(
4614-
isolate, db_tmpl, "loadExtension", DatabaseSync::LoadExtension);
4615-
SetProtoMethod(isolate, db_tmpl, "serialize", DatabaseSync::Serialize);
4616-
SetProtoMethod(isolate, db_tmpl, "deserialize", DatabaseSync::Deserialize);
4617-
SetProtoMethod(
4618-
isolate, db_tmpl, "setAuthorizer", DatabaseSync::SetAuthorizer);
4619-
SetSideEffectFreeGetter(isolate,
4620-
db_tmpl,
4621-
FIXED_ONE_BYTE_STRING(isolate, "isOpen"),
4622-
DatabaseSync::IsOpenGetter);
4623-
SetSideEffectFreeGetter(isolate,
4624-
db_tmpl,
4625-
FIXED_ONE_BYTE_STRING(isolate, "isTransaction"),
4626-
DatabaseSync::IsTransactionGetter);
4627-
SetSideEffectFreeGetter(isolate,
4628-
db_tmpl,
4629-
FIXED_ONE_BYTE_STRING(isolate, "limits"),
4630-
DatabaseSync::LimitsGetter);
4631-
Local<String> sqlite_type_key = FIXED_ONE_BYTE_STRING(isolate, "sqlite-type");
4632-
Local<v8::Symbol> sqlite_type_symbol =
4633-
v8::Symbol::For(isolate, sqlite_type_key);
4634-
Local<String> database_sync_string =
4635-
FIXED_ONE_BYTE_STRING(isolate, "node:sqlite");
4636-
db_tmpl->InstanceTemplate()->Set(sqlite_type_symbol, database_sync_string);
4637-
4638-
SetConstructorFunction(context, target, "DatabaseSync", db_tmpl);
4645+
SetConstructorFunction(context,
4646+
target,
4647+
"DatabaseSync",
4648+
DatabaseSync::GetConstructorTemplate(env));
46394649
SetConstructorFunction(context,
46404650
target,
46414651
"StatementSync",

‎src/node_sqlite.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,8 @@ class DatabaseSync : public BaseObject {
226226
bool open,
227227
bool allow_load_extension);
228228
void MemoryInfo(MemoryTracker* tracker) const override;
229+
static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
230+
Environment* env);
229231
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
230232
static void Open(const v8::FunctionCallbackInfo<v8::Value>& args);
231233
static void IsOpenGetter(const v8::FunctionCallbackInfo<v8::Value>& args);

‎test/parallel/test-sqlite-backup.mjs‎

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,29 @@ describe('backup()', () => {
4848
backup();
4949
}, {
5050
code: 'ERR_INVALID_ARG_TYPE',
51-
message: 'The "sourceDb" argument must be an object.'
51+
message: 'The "sourceDb" argument must be an instance of DatabaseSync.'
5252
});
5353
});
5454

5555
test('throws if the source database is not a DatabaseSync', (t) => {
56-
t.assert.throws(() => {
57-
backup({}, nextDb());
58-
}, {
59-
code: 'ERR_INVALID_ARG_TYPE',
60-
message: 'The "sourceDb" argument must be a DatabaseSync instance.'
61-
});
56+
const database = makeSourceDb();
57+
const values = [
58+
{},
59+
[],
60+
{ p0: 1, p1: 2, p2: 3, p3: 4 },
61+
{ __proto__: DatabaseSync.prototype },
62+
database.prepare('SELECT 1'),
63+
database.createSession(),
64+
];
65+
66+
for (const value of values) {
67+
t.assert.throws(() => {
68+
backup(value, nextDb());
69+
}, {
70+
code: 'ERR_INVALID_ARG_TYPE',
71+
message: 'The "sourceDb" argument must be an instance of DatabaseSync.'
72+
});
73+
}
6274
});
6375

6476
test('throws if path is not a string, URL, or Buffer', (t) => {
@@ -97,6 +109,17 @@ describe('backup()', () => {
97109
});
98110
});
99111

112+
test('throws if the database path has an unparsable href', (t) => {
113+
const database = makeSourceDb();
114+
115+
t.assert.throws(() => {
116+
backup(database, { href: 'zzz' });
117+
}, {
118+
code: 'ERR_INVALID_URL',
119+
message: 'Invalid URL'
120+
});
121+
});
122+
100123
test('throws if options is not an object', (t) => {
101124
const database = makeSourceDb();
102125

‎test/parallel/test-sqlite-database-sync.js‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,15 @@ suite('DatabaseSync() constructor', () => {
6060
});
6161
});
6262

63+
test('propagates an exception thrown by the href getter', (t) => {
64+
t.assert.throws(() => {
65+
new DatabaseSync({ get href() { throw new RangeError('boom'); } });
66+
}, {
67+
name: 'RangeError',
68+
message: 'boom',
69+
});
70+
});
71+
6372
test('throws if options is provided but is not an object', (t) => {
6473
t.assert.throws(() => {
6574
new DatabaseSync('foo', null);

0 commit comments

Comments
 (0)