• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SRombauts / SQLiteCpp / 28436032586

30 Jun 2026 09:58AM UTC coverage: 99.709%. Remained the same
28436032586

Pull #552

github

SRombauts
Add CHANGELOG entry for the null-pointer guards
Pull Request #552: Guard null C pointers in Exception, Database, and Statement against UB

1 of 1 new or added line in 1 file covered. (100.0%)

2 existing lines in 2 files now uncovered.

686 of 688 relevant lines covered (99.71%)

30.48 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.47
/src/Database.cpp
1
/**
2
 * @file    Database.cpp
3
 * @ingroup SQLiteCpp
4
 * @brief   Management of a SQLite Database Connection.
5
 *
6
 * Copyright (c) 2012-2025 Sebastien Rombauts (sebastien.rombauts@gmail.com)
7
 *
8
 * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
9
 * or copy at http://opensource.org/licenses/MIT)
10
 */
11
#include <SQLiteCpp/Database.h>
12

13
#include <SQLiteCpp/Assertion.h>
14
#include <SQLiteCpp/Backup.h>
15
#include <SQLiteCpp/Exception.h>
16
#include <SQLiteCpp/Statement.h>
17

18
#include <sqlite3.h>
19
#include <fstream>
20
#include <string.h>
21

22
#ifndef SQLITE_DETERMINISTIC
23
#define SQLITE_DETERMINISTIC 0x800
24
#endif // SQLITE_DETERMINISTIC
25

26
namespace SQLite
27
{
28

29
const int   OK                = SQLITE_OK;
30
const int   OPEN_READONLY     = SQLITE_OPEN_READONLY;
31
const int   OPEN_READWRITE    = SQLITE_OPEN_READWRITE;
32
const int   OPEN_CREATE       = SQLITE_OPEN_CREATE;
33
const int   OPEN_URI          = SQLITE_OPEN_URI;
34
const int   OPEN_MEMORY       = SQLITE_OPEN_MEMORY;
35
const int   OPEN_NOMUTEX      = SQLITE_OPEN_NOMUTEX;
36
const int   OPEN_FULLMUTEX    = SQLITE_OPEN_FULLMUTEX;
37
const int   OPEN_SHAREDCACHE  = SQLITE_OPEN_SHAREDCACHE;
38
const int   OPEN_PRIVATECACHE = SQLITE_OPEN_PRIVATECACHE;
39
// check if sqlite version is >= 3.31.0 and SQLITE_OPEN_NOFOLLOW is defined
40
#if SQLITE_VERSION_NUMBER >= 3031000 && defined(SQLITE_OPEN_NOFOLLOW)
41
const int   OPEN_NOFOLLOW     = SQLITE_OPEN_NOFOLLOW;
42
#else
43
const int   OPEN_NOFOLLOW     = 0;
44
#endif
45

46
const char* const VERSION        = SQLITE_VERSION;
47
const int         VERSION_NUMBER = SQLITE_VERSION_NUMBER;
48

49
// Return SQLite version string using runtime call to the compiled library
50
const char* getLibVersion() noexcept
2✔
51
{
52
    return sqlite3_libversion();
2✔
53
}
54

55
// Return SQLite version number using runtime call to the compiled library
56
int getLibVersionNumber() noexcept
1✔
57
{
58
    return sqlite3_libversion_number();
1✔
59
}
60

61

62
// Open the provided database UTF-8 filename with SQLite::OPEN_xxx provided flags.
63
Database::Database(const char* apFilename,
72✔
64
                   const int   aFlags         /* = SQLite::OPEN_READONLY*/,
65
                   const int   aBusyTimeoutMs /* = 0 */,
66
                   const char* apVfs          /* = nullptr*/) :
72✔
67
    // Guard against a null filename: std::string(const char*) is UB on nullptr.
68
    mFilename(apFilename ? apFilename : "")
144✔
69
{
70
    sqlite3* handle;
71
    const int ret = sqlite3_open_v2(apFilename, &handle, aFlags, apVfs);
72✔
72
    mSQLitePtr.reset(handle);
72✔
73
    if (SQLITE_OK != ret)
72✔
74
    {
75
        throw SQLite::Exception(handle, ret);
3✔
76
    }
77
    if (aBusyTimeoutMs > 0)
69✔
78
    {
79
        setBusyTimeout(aBusyTimeoutMs);
2✔
80
    }
81
}
75✔
82

83
// Deleter functor to use with smart pointers to close the SQLite database connection in an RAII fashion.
84
void Database::Deleter::operator()(sqlite3* apSQLite)
72✔
85
{
86
    const int ret = sqlite3_close(apSQLite); // Calling sqlite3_close() with a nullptr argument is a harmless no-op.
72✔
87

88
    // Avoid unreferenced variable warning when build in release mode
89
    (void) ret;
90

91
    // Only case of error is SQLITE_BUSY: "database is locked" (some statements are not finalized)
92
    // Never throw an exception in a destructor :
93
    SQLITECPP_ASSERT(SQLITE_OK == ret, "database is locked");  // See SQLITECPP_ENABLE_ASSERT_HANDLER
72✔
94
}
72✔
95

96
// Set a busy handler that sleeps for a specified amount of time when a table is locked.
97
void Database::setBusyTimeout(const int aBusyTimeoutMs)
6✔
98
{
99
    const int ret = sqlite3_busy_timeout(getHandle(), aBusyTimeoutMs);
6✔
100
    check(ret);
6✔
101
}
6✔
102

103
// Shortcut to execute one or multiple SQL statements without results (UPDATE, INSERT, ALTER, COMMIT, CREATE...).
104
// Return the number of changes.
105
int Database::exec(const char* apQueries)
155✔
106
{
107
    const int ret = tryExec(apQueries);
155✔
108
    check(ret);
155✔
109

110
    // Return the number of rows modified by those SQL statements (INSERT, UPDATE or DELETE only)
111
    return sqlite3_changes(getHandle());
146✔
112
}
113

114
int Database::tryExec(const char* apQueries) noexcept
171✔
115
{
116
    return sqlite3_exec(getHandle(), apQueries, nullptr, nullptr, nullptr);
171✔
117
}
118

119
// Shortcut to execute a one step query and fetch the first column of the result.
120
// WARNING: Be very careful with this dangerous method: you have to
121
// make a COPY OF THE result, else it will be destroy before the next line
122
// (when the underlying temporary Statement and Column objects are destroyed)
123
// this is an issue only for pointer type result (ie. char* and blob)
124
// (use the Column copy-constructor)
125
Column Database::execAndGet(const char* apQuery)
14✔
126
{
127
    Statement query(*this, apQuery);
14✔
128
    (void)query.executeStep(); // Can return false if no result, which will throw next line in getColumn()
14✔
129
    return query.getColumn(0);
26✔
130
}
14✔
131

132
// Shortcut to test if a table exists.
133
bool Database::tableExists(const char* apTableName) const
32✔
134
{
135
    Statement query(*this, "SELECT count(*) FROM sqlite_master WHERE type='table' AND name=?");
32✔
136
    query.bind(1, apTableName);
32✔
137
    (void)query.executeStep(); // Cannot return false, as the above query always return a result
32✔
138
    return (1 == query.getColumn(0).getInt());
64✔
139
}
32✔
140

141
// Get the rowid of the most recent successful INSERT into the database from the current connection.
142
int64_t Database::getLastInsertRowid() const noexcept
40✔
143
{
144
    return sqlite3_last_insert_rowid(getHandle());
40✔
145
}
146

147
// Get number of rows modified by last INSERT, UPDATE or DELETE statement (not DROP table).
148
int Database::getChanges() const noexcept
19✔
149
{
150
    return sqlite3_changes(getHandle());
19✔
151
}
152

153
// Get total number of rows modified by all INSERT, UPDATE or DELETE statement since connection.
154
int Database::getTotalChanges() const noexcept
27✔
155
{
156
    return sqlite3_total_changes(getHandle());
27✔
157
}
158

159
// Return the numeric result code for the most recent failed API call (if any).
160
int Database::getErrorCode() const noexcept
59✔
161
{
162
    return sqlite3_errcode(getHandle());
59✔
163
}
164

165
// Return the extended numeric result code for the most recent failed API call (if any).
166
int Database::getExtendedErrorCode() const noexcept
21✔
167
{
168
    return sqlite3_extended_errcode(getHandle());
21✔
169
}
170

171
// Return UTF-8 encoded English language explanation of the most recent failed API call (if any).
172
const char* Database::getErrorMsg() const noexcept
10✔
173
{
174
    return sqlite3_errmsg(getHandle());
10✔
175
}
176

177
// Attach a custom function to your sqlite database. Assumes UTF8 text representation.
178
// Parameter details can be found here: http://www.sqlite.org/c3ref/create_function.html
179
void Database::createFunction(const char*   apFuncName,
1✔
180
                              int           aNbArg,
181
                              bool          abDeterministic,
182
                              void*         apApp,
183
                              void        (*apFunc)(sqlite3_context *, int, sqlite3_value **),
184
                              void        (*apStep)(sqlite3_context *, int, sqlite3_value **) /* = nullptr */,
185
                              void        (*apFinal)(sqlite3_context *) /* = nullptr */, // NOLINT(readability/casting)
186
                              void        (*apDestroy)(void *) /* = nullptr */)
187
{
188
    int textRep = SQLITE_UTF8;
1✔
189
    // optimization if deterministic function (e.g. of nondeterministic function random())
190
    if (abDeterministic)
1✔
191
    {
192
        textRep = textRep | SQLITE_DETERMINISTIC;
1✔
193
    }
194
    const int ret = sqlite3_create_function_v2(getHandle(), apFuncName, aNbArg, textRep,
1✔
195
                                               apApp, apFunc, apStep, apFinal, apDestroy);
196
    check(ret);
1✔
197
}
1✔
198

199
// Load an extension into the sqlite database. Only affects the current connection.
200
// Parameter details can be found here: http://www.sqlite.org/c3ref/load_extension.html
201
void Database::loadExtension(const char* apExtensionName, const char *apEntryPointName)
1✔
202
{
203
#ifdef SQLITE_OMIT_LOAD_EXTENSION
204
    // Unused
205
    (void)apExtensionName;
206
    (void)apEntryPointName;
207

208
    throw SQLite::Exception("sqlite extensions are disabled");
209
#else
210
#ifdef SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION // Since SQLite 3.13 (2016-05-18):
211
    // Security warning:
212
    // It is recommended that the SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION method be used to enable only this interface.
213
    // The use of the sqlite3_enable_load_extension() interface should be avoided to keep the SQL load_extension()
214
    // disabled and prevent SQL injections from giving attackers access to extension loading capabilities.
215
    // (NOTE: not using nullptr: cannot pass object of non-POD type 'std::__1::nullptr_t' through variadic function)
216
    int ret = sqlite3_db_config(getHandle(), SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 1, NULL); // NOTE: not using nullptr
1✔
217
#else
218
    int ret = sqlite3_enable_load_extension(getHandle(), 1);
219
#endif
220
    check(ret);
1✔
221

222
    ret = sqlite3_load_extension(getHandle(), apExtensionName, apEntryPointName, 0);
1✔
223
    check(ret);
1✔
224
#endif
UNCOV
225
}
×
226

227
// Set the key for the current sqlite database instance.
228
void Database::key(const std::string& aKey) const
2✔
229
{
230
    int passLen = static_cast<int>(aKey.length());
2✔
231
#ifdef SQLITE_HAS_CODEC
232
    if (passLen > 0)
233
    {
234
        const int ret = sqlite3_key(getHandle(), aKey.c_str(), passLen);
235
        check(ret);
236
    }
237
#else // SQLITE_HAS_CODEC
238
    if (passLen > 0)
2✔
239
    {
240
        throw SQLite::Exception("No encryption support, recompile with SQLITE_HAS_CODEC to enable.");
1✔
241
    }
242
#endif // SQLITE_HAS_CODEC
243
}
1✔
244

245
// Reset the key for the current sqlite database instance.
246
void Database::rekey(const std::string& aNewKey) const
1✔
247
{
248
#ifdef SQLITE_HAS_CODEC
249
    int passLen = aNewKey.length();
250
    if (passLen > 0)
251
    {
252
        const int ret = sqlite3_rekey(getHandle(), aNewKey.c_str(), passLen);
253
        check(ret);
254
    }
255
    else
256
    {
257
        const int ret = sqlite3_rekey(getHandle(), nullptr, 0);
258
        check(ret);
259
    }
260
#else // SQLITE_HAS_CODEC
261
    static_cast<void>(aNewKey); // silence unused parameter warning
262
    throw SQLite::Exception("No encryption support, recompile with SQLITE_HAS_CODEC to enable.");
1✔
263
#endif // SQLITE_HAS_CODEC
264
}
265

266
// Test if a file contains an unencrypted database.
267
bool Database::isUnencrypted(const std::string& aFilename)
8✔
268
{
269
    if (aFilename.empty())
8✔
270
    {
271
        throw SQLite::Exception("Could not open database, the aFilename parameter was empty.");
2✔
272
    }
273

274
    std::ifstream fileBuffer(aFilename.c_str(), std::ios::in | std::ios::binary);
6✔
275
    char header[16] = {};
6✔
276
    if (fileBuffer.is_open())
6✔
277
    {
278
        fileBuffer.seekg(0, std::ios::beg);
4✔
279
        fileBuffer.read(header, 16);
4✔
280
        fileBuffer.close();
4✔
281
        // A file shorter than the 16-byte header cannot match the magic string.
282
        if (fileBuffer.gcount() != 16)
4✔
283
        {
284
            return false;
1✔
285
        }
286
    }
287
    else
288
    {
289
        throw SQLite::Exception("Error opening file: " + aFilename);
2✔
290
    }
291

292
    return memcmp(header, "SQLite format 3\000", 16) == 0;
3✔
293
}
6✔
294

295
// Parse header data from a database.
296
Header Database::getHeaderInfo(const std::string& aFilename)
6✔
297
{
298
    Header h;
299
    unsigned char buf[100];
300
    char* pBuf = reinterpret_cast<char*>(&buf[0]);
6✔
301
    char* pHeaderStr = reinterpret_cast<char*>(&h.headerStr[0]);
6✔
302

303
    if (aFilename.empty())
6✔
304
    {
305
        throw SQLite::Exception("Filename parameter is empty");
1✔
306
    }
307

308
    {
309
        std::ifstream fileBuffer(aFilename.c_str(), std::ios::in | std::ios::binary);
5✔
310
        if (fileBuffer.is_open())
5✔
311
        {
312
            fileBuffer.seekg(0, std::ios::beg);
4✔
313
            fileBuffer.read(pBuf, 100);
4✔
314
            fileBuffer.close();
4✔
315
            if (fileBuffer.gcount() < 100)
4✔
316
            {
317
                throw SQLite::Exception("File " + aFilename + " is too short");
1✔
318
            }
319
        }
320
        else
321
        {
322
            throw SQLite::Exception("Error opening file " + aFilename);
1✔
323
        }
324
    }
5✔
325

326
    // If the "magic string" can't be found then header is invalid, corrupt or unreadable
327
    memcpy(pHeaderStr, pBuf, 16);
3✔
328
    pHeaderStr[15] = '\0';
3✔
329
    if (strncmp(pHeaderStr, "SQLite format 3", 15) != 0)
3✔
330
    {
331
        throw SQLite::Exception("Invalid or encrypted SQLite header in file " + aFilename);
1✔
332
    }
333

334
    h.pageSizeBytes = (buf[16] << 8) | buf[17];
2✔
335
    h.fileFormatWriteVersion = buf[18];
2✔
336
    h.fileFormatReadVersion = buf[19];
2✔
337
    h.reservedSpaceBytes = buf[20];
2✔
338
    h.maxEmbeddedPayloadFrac = buf[21];
2✔
339
    h.minEmbeddedPayloadFrac = buf[22];
2✔
340
    h.leafPayloadFrac = buf[23];
2✔
341

342
    h.fileChangeCounter =
2✔
343
        (buf[24] << 24) |
2✔
344
        (buf[25] << 16) |
2✔
345
        (buf[26] << 8)  |
2✔
346
        (buf[27] << 0);
2✔
347

348
    h.databaseSizePages =
2✔
349
        (buf[28] << 24) |
2✔
350
        (buf[29] << 16) |
2✔
351
        (buf[30] << 8)  |
2✔
352
        (buf[31] << 0);
2✔
353

354
    h.firstFreelistTrunkPage =
2✔
355
        (buf[32] << 24) |
2✔
356
        (buf[33] << 16) |
2✔
357
        (buf[34] << 8)  |
2✔
358
        (buf[35] << 0);
2✔
359

360
    h.totalFreelistPages =
2✔
361
        (buf[36] << 24) |
2✔
362
        (buf[37] << 16) |
2✔
363
        (buf[38] << 8)  |
2✔
364
        (buf[39] << 0);
2✔
365

366
    h.schemaCookie =
2✔
367
        (buf[40] << 24) |
2✔
368
        (buf[41] << 16) |
2✔
369
        (buf[42] << 8)  |
2✔
370
        (buf[43] << 0);
2✔
371

372
    h.schemaFormatNumber =
2✔
373
        (buf[44] << 24) |
2✔
374
        (buf[45] << 16) |
2✔
375
        (buf[46] << 8)  |
2✔
376
        (buf[47] << 0);
2✔
377

378
    h.defaultPageCacheSizeBytes =
2✔
379
        (buf[48] << 24) |
2✔
380
        (buf[49] << 16) |
2✔
381
        (buf[50] << 8)  |
2✔
382
        (buf[51] << 0);
2✔
383

384
    h.largestBTreePageNumber =
2✔
385
        (buf[52] << 24) |
2✔
386
        (buf[53] << 16) |
2✔
387
        (buf[54] << 8)  |
2✔
388
        (buf[55] << 0);
2✔
389

390
    h.databaseTextEncoding =
2✔
391
        (buf[56] << 24) |
2✔
392
        (buf[57] << 16) |
2✔
393
        (buf[58] << 8)  |
2✔
394
        (buf[59] << 0);
2✔
395

396
    h.userVersion =
2✔
397
        (buf[60] << 24) |
2✔
398
        (buf[61] << 16) |
2✔
399
        (buf[62] << 8)  |
2✔
400
        (buf[63] << 0);
2✔
401

402
    h.incrementalVaccumMode =
2✔
403
        (buf[64] << 24) |
2✔
404
        (buf[65] << 16) |
2✔
405
        (buf[66] << 8)  |
2✔
406
        (buf[67] << 0);
2✔
407

408
    h.applicationId =
2✔
409
        (buf[68] << 24) |
2✔
410
        (buf[69] << 16) |
2✔
411
        (buf[70] << 8)  |
2✔
412
        (buf[71] << 0);
2✔
413

414
    h.versionValidFor =
2✔
415
        (buf[92] << 24) |
2✔
416
        (buf[93] << 16) |
2✔
417
        (buf[94] << 8)  |
2✔
418
        (buf[95] << 0);
2✔
419

420
    h.sqliteVersion =
2✔
421
        (buf[96] << 24) |
2✔
422
        (buf[97] << 16) |
2✔
423
        (buf[98] << 8)  |
2✔
424
        (buf[99] << 0);
2✔
425

426
    return h;
4✔
427
}
428

429
void Database::backup(const char* apFilename, BackupType aType)
2✔
430
{
431
    // Open the database file identified by apFilename
432
    Database otherDatabase(apFilename, SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE);
2✔
433

434
    // For a 'Save' operation, data is copied from the current Database to the other. A 'Load' is the reverse.
435
    Database& src = (aType == BackupType::Save ? *this : otherDatabase);
2✔
436
    Database& dest = (aType == BackupType::Save ? otherDatabase : *this);
2✔
437

438
    // Set up the backup procedure to copy between the "main" databases of each connection
439
    Backup bkp(dest, src);
2✔
440
    bkp.executeStep(); // Execute all steps at once
2✔
441

442
    // RAII Finish Backup an Close the other Database
443
}
2✔
444

445
}  // namespace SQLite
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc