Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,23 @@ jobs:
run: curl -LsSf https://get.nexte.st/latest/linux | tar zxf - -C /usr/local/bin

- name: Build
run: cargo build --all-features
run: |
cargo build --no-default-features --features static
cargo build --no-default-features --features static,std
cargo build --no-default-features --features dynamic
cargo build --no-default-features --features dynamic,std

- name: Test
run: cargo nextest run --all-features
run: |
cargo nextest run --no-default-features --features static,dynamic
cargo nextest run --no-default-features --features static,dynamic,std

- name: Test memvfs
run: |
cargo build --example memvfs --features dynamic
cargo build --example memvfs --features dynamic,std
cat examples/test_memvfs.sql | sqlite3
cat examples/test_memvfs_panic.sql | sqlite3 >/tmp/memvfs_panic 2>&1 \
|| grep -q "unknown error (2)" /tmp/memvfs_panic

- name: Clippy
uses: auguwu/clippy-action@94a9ff2f6920180b89e5c03d121d0af04a9d3e03 # 1.4.0
Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ bindgen = { version = "0.72", default-features = false }
default = ["static"]
static = []
dynamic = []
std = []

[[example]]
name = "memvfs"
crate-type = ["cdylib"]
required-features = ["dynamic"]
required-features = ["dynamic", "std"]
8 changes: 6 additions & 2 deletions examples/memvfs.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// cargo build --example memvfs --features dynamic
// cargo build --example memvfs --features dynamic,std

use std::{ffi::c_void, os::raw::c_char, sync::Arc};

Expand Down Expand Up @@ -181,7 +181,11 @@ impl Vfs for MemVfs {
pragma: Pragma<'_>,
) -> Result<Option<String>, PragmaErr> {
log::debug!("pragma: file={:?}, pragma={:?}", handle.name, pragma);
Err(PragmaErr::NotFound)
if pragma.name == "memvfs_panic" {
panic!("testing panic")
} else {
Err(PragmaErr::NotFound)
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion examples/test_memvfs.sql
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
-- Load the memvfs extension and open a new connection using it
-- Build the memvfs extension using the following command:
-- cargo build --example memvfs --features dynamic
-- cargo build --example memvfs --features dynamic,std

-- uncomment to enable verbose logs
-- .log stderr
Expand Down
20 changes: 20 additions & 0 deletions examples/test_memvfs_panic.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
-- Load the memvfs extension and open a new connection using it
-- Build the memvfs extension using the following command:
-- cargo build --example memvfs --features dynamic,std

-- uncomment to enable verbose logs
-- .log stderr

.load target/debug/examples/libmemvfs.so
.open main.db
.mode table
.log stdout

.databases
.vfsinfo

-- ensure that panics are handled
pragma memvfs_panic;

-- but they cause all future calls to also fail!
CREATE TABLE t1(a, b);
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#![no_std]
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;

pub mod vars {
Expand Down
26 changes: 26 additions & 0 deletions src/vfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ pub const DEFAULT_DEVICE_CHARACTERISTICS: i32 =
/// A `SQLite3` extended error code
pub type SqliteErr = i32;

// construct a custom SQLITE_INTERNAL error code for tracking panics
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The public constant ERROR_PANIC lacks documentation. Since this is a public API that defines a custom SQLite extended error code for panic tracking, it should have a doc comment explaining its purpose, when it's returned, and how it's constructed from SQLITE_INTERNAL.

Suggested change
// construct a custom SQLITE_INTERNAL error code for tracking panics
/// Custom SQLite extended error code for tracking panics in the VFS implementation.
///
/// This error code is returned when a panic is caught within the VFS and needs to be
/// reported to SQLite as an internal error. It is constructed by combining
/// `SQLITE_INTERNAL` with a custom extension (128 << 8), following the SQLite extended
/// error code convention.

Copilot uses AI. Check for mistakes.
pub const ERROR_PANIC: SqliteErr = vars::SQLITE_INTERNAL | (128 << 8);

pub type VfsResult<T> = Result<T, SqliteErr>;

// FileWrapper needs to be repr(C) and have sqlite3_file as it's first member
Expand Down Expand Up @@ -79,6 +82,29 @@ impl PragmaErr {
}
}

#[cfg(feature = "std")]
fn fallible(mut cb: impl FnMut() -> Result<i32, SqliteErr>) -> i32 {
use std::sync::atomic::Ordering;
use std::{panic::AssertUnwindSafe, sync::atomic::AtomicBool};

// once we panic, all future calls into the VFS will also panic as we can't
// be sure that we are unwind safe
static POISONED: AtomicBool = AtomicBool::new(false);
if POISONED.load(Ordering::Relaxed) {
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent atomic ordering may lead to visibility issues. The store uses SeqCst ordering but the load uses Relaxed ordering. For proper synchronization when checking if the VFS has been poisoned, the load should use at least Acquire ordering to establish a happens-before relationship with the SeqCst store. Consider changing the load to use Acquire ordering to ensure threads properly observe when the VFS has been poisoned.

Suggested change
if POISONED.load(Ordering::Relaxed) {
if POISONED.load(Ordering::Acquire) {

Copilot uses AI. Check for mistakes.
return ERROR_PANIC;
}

let inner = AssertUnwindSafe(|| cb().unwrap_or_else(|err| err));
match std::panic::catch_unwind(inner) {
Ok(i) => i,
Err(_err) => {
POISONED.store(true, Ordering::SeqCst);
ERROR_PANIC
}
}
}

#[cfg(not(feature = "std"))]
fn fallible(mut cb: impl FnMut() -> Result<i32, SqliteErr>) -> i32 {
cb().unwrap_or_else(|err| err)
}
Expand Down