| // Copyright 2026 The Chromium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "sql/test/test_vfs.h" | |
| #include <stddef.h> | |
| #include <memory> | |
| #include <type_traits> | |
| #include <utility> | |
| #include "base/check.h" | |
| #include "base/check_deref.h" | |
| #include "base/check_op.h" | |
| #include "base/memory/raw_ref.h" | |
| #include "sql/initialization.h" | |
| #include "third_party/sqlite/sqlite3.h" | |
| namespace sql::test { | |
| namespace { | |
| // Functor for deleting memory allocated by SQLite. | |
| struct SqliteMemoryDeleter { | |
| void operator()(void* ptr) const { sqlite3_free(ptr); } | |
| }; | |
| // An `std::unique_ptr` that deletes objects using `sqlite3_free`. | |
| template <typename T> | |
| using UniqueSqlitePtr = std::unique_ptr<T, SqliteMemoryDeleter>; | |
| // A file created by `TestVfs`. | |
| // | |
| // `TestVfsFile` is a C-style child class of `sqlite3_file` (i.e. it's safe to | |
| // cast `TestVfsFile*` to `sqlite3_file*`), providing SQLite with callbacks to | |
| // invoke to do file IOs. These callbacks simply forward the calls to the | |
| // `TestVfs` that created this file, which then forward the call back to the | |
| // original `sqlite3_io_methods` of the file that this `TestVfsFile` wraps. | |
| // `TestVfs` uses virtual methods, allowing unit tests to customize all VFS and | |
| // file IO operations in a central place. | |
| class TestVfsFile { | |
| public: | |
| // Constructs an instance of `TestVfsFile` in the memory reserved in `file`. | |
| // This memory is pre-allocated by SQLite when calling `xOpen`. | |
| static void InplaceCreate(sqlite3_file* file, | |
| UniqueSqlitePtr<sqlite3_file> wrapped_file, | |
| TestVfs& test_vfs) { | |
| TestVfsFile* test_vfs_file = CastFrom(file); | |
| new (test_vfs_file) TestVfsFile(std::move(wrapped_file), test_vfs); | |
| } | |
| // Destroys the `TestVfsFile` object in `file`. | |
| static void Destroy(sqlite3_file* file) { | |
| TestVfsFile* test_vfs_file = CastFrom(file); | |
| test_vfs_file->~TestVfsFile(); | |
| } | |
| // Casts from `sqlite3_file*` to `TestVfsFile*`. This is safe because | |
| // `TestVfsFile` has a standard memory layout and its first member is an | |
| // instance of `sqlite3_file`. | |
| static TestVfsFile* CastFrom(sqlite3_file* file) { | |
| static_assert(std::is_standard_layout_v<TestVfsFile>, | |
| "Needed for the reinterpret_cast below"); | |
| static_assert(offsetof(TestVfsFile, file_) == 0, | |
| "file_ must be the first member of the class"); | |
| return reinterpret_cast<TestVfsFile*>(file); | |
| } | |
| sqlite3_file* wrapped_file() const { return wrapped_file_.get(); } | |
| TestVfs& test_vfs() const { return test_vfs_.get(); } | |
| private: | |
| TestVfsFile(UniqueSqlitePtr<sqlite3_file> wrapped_file, TestVfs& test_vfs) | |
| : file_({.pMethods = GetSqliteIoMethods()}), | |
| wrapped_file_(std::move(wrapped_file)), | |
| test_vfs_(test_vfs) {} | |
| // Returns a pointer to a static `sqlite3_io_methods` instance, telling SQLite | |
| // what callback to call for file IOs. | |
| static const sqlite3_io_methods* GetSqliteIoMethods(); | |
| // C-style base class, `file_` must be the first member in the class. | |
| const sqlite3_file file_; | |
| // The `sqlite3_file` from the original VFS that this `TestVfsFile` wraps. | |
| const UniqueSqlitePtr<sqlite3_file> wrapped_file_; | |
| // The TestVfs that created this TestVfsFile. | |
| const raw_ref<TestVfs> test_vfs_; | |
| }; | |
| // Returns the `TestVfs` instance associated with the given SQLite object. | |
| TestVfs& GetTestVfs(sqlite3_vfs* vfs) { | |
| return CHECK_DEREF(reinterpret_cast<TestVfs*>(vfs->pAppData)); | |
| } | |
| TestVfs& GetTestVfs(sqlite3_file* file) { | |
| return TestVfsFile::CastFrom(file)->test_vfs(); | |
| } | |
| // Defines a C-compatible function `Callback` forwarding the call to the | |
| // `TestVfs` method pointer `callback`. | |
| template <auto callback> | |
| struct ForwardTo; | |
| template <typename Return, | |
| typename SqliteObject, | |
| typename... Args, | |
| Return (TestVfs::*callback)(SqliteObject* obj, Args... args)> | |
| struct ForwardTo<callback> { | |
| static Return Callback(SqliteObject* obj, Args... args) { | |
| return (GetTestVfs(obj).*callback)(obj, args...); | |
| } | |
| }; | |
| const sqlite3_io_methods* TestVfsFile::GetSqliteIoMethods() { | |
| static const sqlite3_io_methods kIoMethods = { | |
| // VFS IO API entry points are listed at: | |
| // https://www.sqlite.org/c3ref/io_methods.html | |
| .iVersion = 3, | |
| .xClose = ForwardTo<&TestVfs::Close>::Callback, | |
| .xRead = ForwardTo<&TestVfs::Read>::Callback, | |
| .xWrite = ForwardTo<&TestVfs::Write>::Callback, | |
| .xTruncate = ForwardTo<&TestVfs::Truncate>::Callback, | |
| .xSync = ForwardTo<&TestVfs::Sync>::Callback, | |
| .xFileSize = ForwardTo<&TestVfs::FileSize>::Callback, | |
| .xLock = ForwardTo<&TestVfs::Lock>::Callback, | |
| .xUnlock = ForwardTo<&TestVfs::Unlock>::Callback, | |
| .xCheckReservedLock = ForwardTo<&TestVfs::CheckReservedLock>::Callback, | |
| .xFileControl = ForwardTo<&TestVfs::FileControl>::Callback, | |
| .xSectorSize = ForwardTo<&TestVfs::SectorSize>::Callback, | |
| .xDeviceCharacteristics = | |
| ForwardTo<&TestVfs::DeviceCharacteristics>::Callback, | |
| .xShmMap = ForwardTo<&TestVfs::ShmMap>::Callback, | |
| .xShmLock = ForwardTo<&TestVfs::ShmLock>::Callback, | |
| .xShmBarrier = ForwardTo<&TestVfs::ShmBarrier>::Callback, | |
| .xShmUnmap = ForwardTo<&TestVfs::ShmUnmap>::Callback, | |
| .xFetch = ForwardTo<&TestVfs::Fetch>::Callback, | |
| .xUnfetch = ForwardTo<&TestVfs::Unfetch>::Callback, | |
| }; | |
| return &kIoMethods; | |
| } | |
| } // namespace | |
| TestVfs::TestVfs() | |
| : vfs_({ | |
| // VFS API entry points are listed at: | |
| // https://www.sqlite.org/c3ref/vfs.html | |
| .iVersion = 3, | |
| .szOsFile = sizeof(TestVfsFile), | |
| .mxPathname = 512, | |
| .pNext = nullptr, | |
| .zName = TestVfs::kName, | |
| .pAppData = this, | |
| .xOpen = ForwardTo<&TestVfs::Open>::Callback, | |
| .xDelete = ForwardTo<&TestVfs::Delete>::Callback, | |
| .xAccess = ForwardTo<&TestVfs::Access>::Callback, | |
| .xFullPathname = ForwardTo<&TestVfs::FullPathname>::Callback, | |
| .xDlOpen = nullptr, | |
| .xDlError = nullptr, | |
| .xDlSym = nullptr, | |
| .xDlClose = nullptr, | |
| .xRandomness = ForwardTo<&TestVfs::Randomness>::Callback, | |
| .xSleep = ForwardTo<&TestVfs::Sleep>::Callback, | |
| .xCurrentTime = nullptr, | |
| .xGetLastError = ForwardTo<&TestVfs::GetLastError>::Callback, | |
| .xCurrentTimeInt64 = ForwardTo<&TestVfs::CurrentTimeInt64>::Callback, | |
| .xSetSystemCall = nullptr, | |
| .xGetSystemCall = nullptr, | |
| .xNextSystemCall = nullptr, | |
| }) { | |
| // Register `VfsWrapper` first, so that `TestVfs` wraps over `VfsWrapper`. | |
| // `VfsWrapper` is needed for the VFS to work on all platforms (e.g. Fuchsia). | |
| // `VfsWrapper` cannot wrap `TestVfs` because it's designed to wrap the | |
| // platform's default VFS directly (e.g. for Fuchsia, it explicitly wraps | |
| // "unix-none" instead of the default VFS). | |
| EnsureSqliteInitialized(); | |
| CHECK_EQ(sqlite3_vfs_find(TestVfs::kName), nullptr) | |
| << "Two instances of TestVfs can't be live at the same time."; | |
| original_vfs_ = sqlite3_vfs_find(nullptr); // Lookup default VFS. | |
| CHECK(original_vfs_) << "No default VFS found."; | |
| int rc = sqlite3_vfs_register(&vfs_, /*makeDflt=*/true); | |
| CHECK_EQ(rc, SQLITE_OK); | |
| } | |
| TestVfs::~TestVfs() { | |
| CHECK_EQ(sqlite3_vfs_unregister(&vfs_), SQLITE_OK); | |
| // Restore the original default because `sqlite3_vfs_unregister` choses an | |
| // arbitrary VFS as default. | |
| CHECK_EQ(sqlite3_vfs_register(original_vfs_, /*makeDflt=*/true), SQLITE_OK); | |
| } | |
| int TestVfs::Open(sqlite3_vfs* vfs, | |
| const char* full_path, | |
| sqlite3_file* result_file, | |
| int requested_flags, | |
| int* granted_flags) { | |
| UniqueSqlitePtr<sqlite3_file> wrapped_file( | |
| reinterpret_cast<sqlite3_file*>(sqlite3_malloc(original_vfs_->szOsFile))); | |
| if (!wrapped_file) { | |
| return SQLITE_NOMEM; | |
| } | |
| int rc = original_vfs_->xOpen(original_vfs_, full_path, wrapped_file.get(), | |
| requested_flags, granted_flags); | |
| if (rc != SQLITE_OK) { | |
| return rc; | |
| } | |
| TestVfsFile::InplaceCreate(result_file, std::move(wrapped_file), *this); | |
| return rc; | |
| } | |
| int TestVfs::Delete(sqlite3_vfs* vfs, const char* full_path, int sync_dir) { | |
| return original_vfs_->xDelete(original_vfs_, full_path, sync_dir); | |
| } | |
| int TestVfs::Access(sqlite3_vfs* vfs, | |
| const char* full_path, | |
| int flags, | |
| int* result) { | |
| return original_vfs_->xAccess(original_vfs_, full_path, flags, result); | |
| } | |
| int TestVfs::FullPathname(sqlite3_vfs* vfs, | |
| const char* file_path, | |
| int result_size, | |
| char* result) { | |
| return original_vfs_->xFullPathname(original_vfs_, file_path, result_size, | |
| result); | |
| } | |
| int TestVfs::Randomness(sqlite3_vfs* vfs, int result_size, char* result) { | |
| return original_vfs_->xRandomness(original_vfs_, result_size, result); | |
| } | |
| int TestVfs::Sleep(sqlite3_vfs* vfs, int microseconds) { | |
| return original_vfs_->xSleep(original_vfs_, microseconds); | |
| } | |
| int TestVfs::GetLastError(sqlite3_vfs* vfs, int message_size, char* message) { | |
| return original_vfs_->xGetLastError(original_vfs_, message_size, message); | |
| } | |
| int TestVfs::CurrentTimeInt64(sqlite3_vfs* vfs, sqlite3_int64* result_ms) { | |
| return original_vfs_->xCurrentTimeInt64(original_vfs_, result_ms); | |
| } | |
| int TestVfs::Close(sqlite3_file* file) { | |
| sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file(); | |
| int result = wrapped_file->pMethods->xClose(wrapped_file); | |
| TestVfsFile::Destroy(file); | |
| return result; | |
| } | |
| int TestVfs::Read(sqlite3_file* file, | |
| void* buffer, | |
| int size, | |
| sqlite3_int64 offset) { | |
| sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file(); | |
| return wrapped_file->pMethods->xRead(wrapped_file, buffer, size, offset); | |
| } | |
| int TestVfs::Write(sqlite3_file* file, | |
| const void* buffer, | |
| int size, | |
| sqlite3_int64 offset) { | |
| sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file(); | |
| return wrapped_file->pMethods->xWrite(wrapped_file, buffer, size, offset); | |
| } | |
| int TestVfs::Truncate(sqlite3_file* file, sqlite3_int64 size) { | |
| sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file(); | |
| return wrapped_file->pMethods->xTruncate(wrapped_file, size); | |
| } | |
| int TestVfs::Sync(sqlite3_file* file, int flags) { | |
| sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file(); | |
| return wrapped_file->pMethods->xSync(wrapped_file, flags); | |
| } | |
| int TestVfs::FileSize(sqlite3_file* file, sqlite3_int64* result_size) { | |
| sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file(); | |
| return wrapped_file->pMethods->xFileSize(wrapped_file, result_size); | |
| } | |
| int TestVfs::Lock(sqlite3_file* file, int mode) { | |
| sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file(); | |
| return wrapped_file->pMethods->xLock(wrapped_file, mode); | |
| } | |
| int TestVfs::Unlock(sqlite3_file* file, int mode) { | |
| sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file(); | |
| return wrapped_file->pMethods->xUnlock(wrapped_file, mode); | |
| } | |
| int TestVfs::CheckReservedLock(sqlite3_file* file, int* has_reserved_lock) { | |
| sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file(); | |
| return wrapped_file->pMethods->xCheckReservedLock(wrapped_file, | |
| has_reserved_lock); | |
| } | |
| int TestVfs::FileControl(sqlite3_file* file, int opcode, void* data) { | |
| sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file(); | |
| return wrapped_file->pMethods->xFileControl(wrapped_file, opcode, data); | |
| } | |
| int TestVfs::SectorSize(sqlite3_file* file) { | |
| sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file(); | |
| return wrapped_file->pMethods->xSectorSize(wrapped_file); | |
| } | |
| int TestVfs::DeviceCharacteristics(sqlite3_file* file) { | |
| sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file(); | |
| return wrapped_file->pMethods->xDeviceCharacteristics(wrapped_file); | |
| } | |
| int TestVfs::ShmMap(sqlite3_file* file, | |
| int page_index, | |
| int page_size, | |
| int extend_file_if_needed, | |
| void volatile** result) { | |
| sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file(); | |
| return wrapped_file->pMethods->xShmMap(wrapped_file, page_index, page_size, | |
| extend_file_if_needed, result); | |
| } | |
| int TestVfs::ShmLock(sqlite3_file* file, int offset, int size, int flags) { | |
| sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file(); | |
| return wrapped_file->pMethods->xShmLock(wrapped_file, offset, size, flags); | |
| } | |
| void TestVfs::ShmBarrier(sqlite3_file* file) { | |
| sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file(); | |
| return wrapped_file->pMethods->xShmBarrier(wrapped_file); | |
| } | |
| int TestVfs::ShmUnmap(sqlite3_file* file, int also_delete_file) { | |
| sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file(); | |
| return wrapped_file->pMethods->xShmUnmap(wrapped_file, also_delete_file); | |
| } | |
| int TestVfs::Fetch(sqlite3_file* file, | |
| sqlite3_int64 offset, | |
| int size, | |
| void** result) { | |
| sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file(); | |
| return wrapped_file->pMethods->xFetch(wrapped_file, offset, size, result); | |
| } | |
| int TestVfs::Unfetch(sqlite3_file* file, | |
| sqlite3_int64 offset, | |
| void* fetch_result) { | |
| sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file(); | |
| return wrapped_file->pMethods->xUnfetch(wrapped_file, offset, fetch_result); | |
| } | |
| } // namespace sql::test |