story-kit: merge 260_refactor_upgrade_libsqlite3_sys
This commit is contained in:
Vendored
+55
@@ -0,0 +1,55 @@
|
||||
// Internal utilities
|
||||
pub(crate) mod param_cache;
|
||||
mod small_cstr;
|
||||
pub(crate) use param_cache::ParamIndexCache;
|
||||
pub(crate) use small_cstr::SmallCString;
|
||||
|
||||
// Doesn't use any modern features or vtab stuff, but is only used by them.
|
||||
mod sqlite_string;
|
||||
pub(crate) use sqlite_string::{alloc, SqliteMallocString};
|
||||
|
||||
#[cfg(any(
|
||||
feature = "collation",
|
||||
feature = "functions",
|
||||
feature = "vtab",
|
||||
feature = "pointer"
|
||||
))]
|
||||
pub(crate) unsafe extern "C" fn free_boxed_value<T>(p: *mut std::ffi::c_void) {
|
||||
drop(Box::from_raw(p.cast::<T>()));
|
||||
}
|
||||
|
||||
use crate::Result;
|
||||
use std::ffi::CStr;
|
||||
|
||||
pub enum Named<'a> {
|
||||
Small(SmallCString),
|
||||
C(&'a CStr),
|
||||
}
|
||||
impl std::ops::Deref for Named<'_> {
|
||||
type Target = CStr;
|
||||
#[inline]
|
||||
fn deref(&self) -> &CStr {
|
||||
match self {
|
||||
Named::Small(s) => s.as_cstr(),
|
||||
Named::C(s) => s,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Database, table, column, collation, function, module, vfs name
|
||||
pub trait Name: std::fmt::Debug {
|
||||
/// As C string
|
||||
fn as_cstr(&self) -> Result<Named<'_>>;
|
||||
}
|
||||
impl Name for &str {
|
||||
fn as_cstr(&self) -> Result<Named<'_>> {
|
||||
let ss = SmallCString::new(self)?;
|
||||
Ok(Named::Small(ss))
|
||||
}
|
||||
}
|
||||
impl Name for &CStr {
|
||||
#[inline]
|
||||
fn as_cstr(&self) -> Result<Named<'_>> {
|
||||
Ok(Named::C(self))
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
use super::SmallCString;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// Maps parameter names to parameter indices.
|
||||
#[derive(Default, Clone, Debug)]
|
||||
// BTreeMap seems to do better here unless we want to pull in a custom hash
|
||||
// function.
|
||||
pub(crate) struct ParamIndexCache(RefCell<BTreeMap<SmallCString, usize>>);
|
||||
|
||||
impl ParamIndexCache {
|
||||
pub fn get_or_insert_with<F>(&self, s: &str, func: F) -> Option<usize>
|
||||
where
|
||||
F: FnOnce(&std::ffi::CStr) -> Option<usize>,
|
||||
{
|
||||
let mut cache = self.0.borrow_mut();
|
||||
// Avoid entry API, needs allocation to test membership.
|
||||
if let Some(v) = cache.get(s) {
|
||||
return Some(*v);
|
||||
}
|
||||
// If there's an internal nul in the name it couldn't have been a
|
||||
// parameter, so early return here is ok.
|
||||
let name = SmallCString::new(s).ok()?;
|
||||
let val = func(&name)?;
|
||||
cache.insert(name, val);
|
||||
Some(val)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
||||
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_cache() {
|
||||
let p = ParamIndexCache::default();
|
||||
let v = p.get_or_insert_with("foo", |cstr| {
|
||||
assert_eq!(cstr.to_str().unwrap(), "foo");
|
||||
Some(3)
|
||||
});
|
||||
assert_eq!(v, Some(3));
|
||||
let v = p.get_or_insert_with("foo", |_| {
|
||||
panic!("shouldn't be called this time");
|
||||
});
|
||||
assert_eq!(v, Some(3));
|
||||
let v = p.get_or_insert_with("gar\0bage", |_| {
|
||||
panic!("shouldn't be called here either");
|
||||
});
|
||||
assert_eq!(v, None);
|
||||
let v = p.get_or_insert_with("bar", |cstr| {
|
||||
assert_eq!(cstr.to_str().unwrap(), "bar");
|
||||
None
|
||||
});
|
||||
assert_eq!(v, None);
|
||||
let v = p.get_or_insert_with("bar", |cstr| {
|
||||
assert_eq!(cstr.to_str().unwrap(), "bar");
|
||||
Some(30)
|
||||
});
|
||||
assert_eq!(v, Some(30));
|
||||
}
|
||||
}
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use std::ffi::{CStr, CString, NulError};
|
||||
|
||||
/// Similar to `std::ffi::CString`, but avoids heap allocating if the string is
|
||||
/// small enough. Also guarantees it's input is UTF-8 -- used for cases where we
|
||||
/// need to pass a NUL-terminated string to SQLite, and we have a `&str`.
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct SmallCString(SmallVec<[u8; 16]>);
|
||||
|
||||
impl SmallCString {
|
||||
#[inline]
|
||||
pub fn new(s: &str) -> Result<Self, NulError> {
|
||||
if s.as_bytes().contains(&0_u8) {
|
||||
return Err(Self::fabricate_nul_error(s));
|
||||
}
|
||||
let mut buf = SmallVec::with_capacity(s.len() + 1);
|
||||
buf.extend_from_slice(s.as_bytes());
|
||||
buf.push(0);
|
||||
let res = Self(buf);
|
||||
res.debug_checks();
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.debug_checks();
|
||||
// Constructor takes a &str so this is safe.
|
||||
unsafe { std::str::from_utf8_unchecked(self.as_bytes_without_nul()) }
|
||||
}
|
||||
|
||||
/// Get the bytes not including the NUL terminator. E.g. the bytes which
|
||||
/// make up our `str`:
|
||||
/// - `SmallCString::new("foo").as_bytes_without_nul() == b"foo"`
|
||||
/// - `SmallCString::new("foo").as_bytes_with_nul() == b"foo\0"`
|
||||
#[inline]
|
||||
pub fn as_bytes_without_nul(&self) -> &[u8] {
|
||||
self.debug_checks();
|
||||
&self.0[..self.len()]
|
||||
}
|
||||
|
||||
/// Get the bytes behind this str *including* the NUL terminator. This
|
||||
/// should never return an empty slice.
|
||||
#[inline]
|
||||
pub fn as_bytes_with_nul(&self) -> &[u8] {
|
||||
self.debug_checks();
|
||||
&self.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(debug_assertions)]
|
||||
fn debug_checks(&self) {
|
||||
debug_assert_ne!(self.0.len(), 0);
|
||||
debug_assert_eq!(self.0[self.0.len() - 1], 0);
|
||||
let strbytes = &self.0[..(self.0.len() - 1)];
|
||||
debug_assert!(!strbytes.contains(&0));
|
||||
debug_assert!(std::str::from_utf8(strbytes).is_ok());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(debug_assertions))]
|
||||
fn debug_checks(&self) {}
|
||||
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
debug_assert_ne!(self.0.len(), 0);
|
||||
self.0.len() - 1
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(unused)] // clippy wants this function.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_cstr(&self) -> &CStr {
|
||||
let bytes = self.as_bytes_with_nul();
|
||||
debug_assert!(CStr::from_bytes_with_nul(bytes).is_ok());
|
||||
unsafe { CStr::from_bytes_with_nul_unchecked(bytes) }
|
||||
}
|
||||
|
||||
#[cold]
|
||||
fn fabricate_nul_error(b: &str) -> NulError {
|
||||
CString::new(b).unwrap_err()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SmallCString {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self(smallvec![0])
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for SmallCString {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("SmallCString").field(&self.as_str()).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for SmallCString {
|
||||
type Target = CStr;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &CStr {
|
||||
self.as_cstr()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::borrow::Borrow<str> for SmallCString {
|
||||
#[inline]
|
||||
fn borrow(&self) -> &str {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_small_cstring() {
|
||||
// We don't go through the normal machinery for default, so make sure
|
||||
// things work.
|
||||
assert_eq!(SmallCString::default().0, SmallCString::new("").unwrap().0);
|
||||
assert_eq!(SmallCString::new("foo").unwrap().len(), 3);
|
||||
assert_eq!(
|
||||
SmallCString::new("foo").unwrap().as_bytes_with_nul(),
|
||||
b"foo\0"
|
||||
);
|
||||
assert_eq!(
|
||||
SmallCString::new("foo").unwrap().as_bytes_without_nul(),
|
||||
b"foo",
|
||||
);
|
||||
|
||||
assert_eq!(SmallCString::new("😀").unwrap().len(), 4);
|
||||
assert_eq!(
|
||||
SmallCString::new("😀").unwrap().0.as_slice(),
|
||||
b"\xf0\x9f\x98\x80\0",
|
||||
);
|
||||
assert_eq!(
|
||||
SmallCString::new("😀").unwrap().as_bytes_without_nul(),
|
||||
b"\xf0\x9f\x98\x80",
|
||||
);
|
||||
|
||||
assert_eq!(SmallCString::new("").unwrap().len(), 0);
|
||||
assert!(SmallCString::new("").unwrap().is_empty());
|
||||
|
||||
assert_eq!(SmallCString::new("").unwrap().0.as_slice(), b"\0");
|
||||
assert_eq!(SmallCString::new("").unwrap().as_bytes_without_nul(), b"");
|
||||
|
||||
SmallCString::new("\0").unwrap_err();
|
||||
SmallCString::new("\0abc").unwrap_err();
|
||||
SmallCString::new("abc\0").unwrap_err();
|
||||
}
|
||||
}
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
// This is used when either vtab or modern-sqlite is on. Different methods are
|
||||
// used in each feature. Avoid having to track this for each function. We will
|
||||
// still warn for anything that's not used by either, though.
|
||||
#![cfg_attr(not(feature = "vtab"), allow(dead_code))]
|
||||
use crate::ffi;
|
||||
use std::ffi::{c_char, CStr};
|
||||
use std::marker::PhantomData;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
// Space to hold this string must be obtained
|
||||
// from an SQLite memory allocation function
|
||||
pub(crate) fn alloc(s: &str) -> *mut c_char {
|
||||
SqliteMallocString::from_str(s).into_raw()
|
||||
}
|
||||
|
||||
/// A string we own that's allocated on the SQLite heap. Automatically calls
|
||||
/// `sqlite3_free` when dropped, unless `into_raw` (or `into_inner`) is called
|
||||
/// on it. If constructed from a rust string, `sqlite3_malloc64` is used.
|
||||
///
|
||||
/// It has identical representation to a nonnull `*mut c_char`, so you can use
|
||||
/// it transparently as one. It's nonnull, so Option<SqliteMallocString> can be
|
||||
/// used for nullable ones (it's still just one pointer).
|
||||
///
|
||||
/// Most strings shouldn't use this! Only places where the string needs to be
|
||||
/// freed with `sqlite3_free`. This includes `sqlite3_extended_sql` results,
|
||||
/// some error message pointers... Note that misuse is extremely dangerous!
|
||||
///
|
||||
/// Note that this is *not* a lossless interface. Incoming strings with internal
|
||||
/// NULs are modified, and outgoing strings which are non-UTF8 are modified.
|
||||
/// This seems unavoidable -- it tries very hard to not panic.
|
||||
#[repr(transparent)]
|
||||
pub(crate) struct SqliteMallocString {
|
||||
ptr: NonNull<c_char>,
|
||||
_boo: PhantomData<Box<[c_char]>>,
|
||||
}
|
||||
// This is owned data for a primitive type, and thus it's safe to implement
|
||||
// these. That said, nothing needs them, and they make things easier to misuse.
|
||||
|
||||
// unsafe impl Send for SqliteMallocString {}
|
||||
// unsafe impl Sync for SqliteMallocString {}
|
||||
|
||||
impl SqliteMallocString {
|
||||
/// SAFETY: Caller must be certain that `m` a nul-terminated c string
|
||||
/// allocated by `sqlite3_malloc64`, and that SQLite expects us to free it!
|
||||
#[inline]
|
||||
pub(crate) unsafe fn from_raw_nonnull(ptr: NonNull<c_char>) -> Self {
|
||||
Self {
|
||||
ptr,
|
||||
_boo: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// SAFETY: Caller must be certain that `m` a nul-terminated c string
|
||||
/// allocated by `sqlite3_malloc64`, and that SQLite expects us to free it!
|
||||
#[inline]
|
||||
pub(crate) unsafe fn from_raw(ptr: *mut c_char) -> Option<Self> {
|
||||
NonNull::new(ptr).map(|p| Self::from_raw_nonnull(p))
|
||||
}
|
||||
|
||||
/// Get the pointer behind `self`. After this is called, we no longer manage
|
||||
/// it.
|
||||
#[inline]
|
||||
pub(crate) fn into_inner(self) -> NonNull<c_char> {
|
||||
let p = self.ptr;
|
||||
std::mem::forget(self);
|
||||
p
|
||||
}
|
||||
|
||||
/// Get the pointer behind `self`. After this is called, we no longer manage
|
||||
/// it.
|
||||
#[inline]
|
||||
pub(crate) fn into_raw(self) -> *mut c_char {
|
||||
self.into_inner().as_ptr()
|
||||
}
|
||||
|
||||
/// Borrow the pointer behind `self`. We still manage it when this function
|
||||
/// returns. If you want to relinquish ownership, use `into_raw`.
|
||||
#[inline]
|
||||
pub(crate) fn as_ptr(&self) -> *const c_char {
|
||||
self.ptr.as_ptr()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn as_cstr(&self) -> &CStr {
|
||||
unsafe { CStr::from_ptr(self.as_ptr()) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn to_string_lossy(&self) -> std::borrow::Cow<'_, str> {
|
||||
self.as_cstr().to_string_lossy()
|
||||
}
|
||||
|
||||
/// Convert `s` into a SQLite string.
|
||||
///
|
||||
/// This should almost never be done except for cases like error messages or
|
||||
/// other strings that SQLite frees.
|
||||
///
|
||||
/// If `s` contains internal NULs, we'll replace them with
|
||||
/// `NUL_REPLACE_CHAR`.
|
||||
///
|
||||
/// Except for `debug_assert`s which may trigger during testing, this
|
||||
/// function never panics. If we hit integer overflow or the allocation
|
||||
/// fails, we call `handle_alloc_error` which aborts the program after
|
||||
/// calling a global hook.
|
||||
///
|
||||
/// This means it's safe to use in extern "C" functions even outside
|
||||
/// `catch_unwind`.
|
||||
pub(crate) fn from_str(s: &str) -> Self {
|
||||
let s = if s.as_bytes().contains(&0) {
|
||||
std::borrow::Cow::Owned(make_nonnull(s))
|
||||
} else {
|
||||
std::borrow::Cow::Borrowed(s)
|
||||
};
|
||||
debug_assert!(!s.as_bytes().contains(&0));
|
||||
let bytes: &[u8] = s.as_ref().as_bytes();
|
||||
let src_ptr: *const c_char = bytes.as_ptr().cast();
|
||||
let src_len = bytes.len();
|
||||
let maybe_len_plus_1 = s.len().checked_add(1).and_then(|v| v.try_into().ok());
|
||||
unsafe {
|
||||
let res_ptr = maybe_len_plus_1
|
||||
.and_then(|len_to_alloc| {
|
||||
// `>` because we added 1.
|
||||
debug_assert!(len_to_alloc > 0);
|
||||
debug_assert_eq!((len_to_alloc - 1) as usize, src_len);
|
||||
NonNull::new(ffi::sqlite3_malloc64(len_to_alloc).cast::<c_char>())
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
use std::alloc::{handle_alloc_error, Layout};
|
||||
// Report via handle_alloc_error so that it can be handled with any
|
||||
// other allocation errors and properly diagnosed.
|
||||
//
|
||||
// This is safe:
|
||||
// - `align` is never 0
|
||||
// - `align` is always a power of 2.
|
||||
// - `size` needs no realignment because it's guaranteed to be aligned
|
||||
// (everything is aligned to 1)
|
||||
// - `size` is also never zero, although this function doesn't actually require
|
||||
// it now.
|
||||
let len = s.len().saturating_add(1).min(isize::MAX as usize);
|
||||
let layout = Layout::from_size_align_unchecked(len, 1);
|
||||
// Note: This call does not return.
|
||||
handle_alloc_error(layout);
|
||||
});
|
||||
let buf: *mut c_char = res_ptr.as_ptr().cast::<c_char>();
|
||||
src_ptr.copy_to_nonoverlapping(buf, src_len);
|
||||
buf.add(src_len).write(0);
|
||||
debug_assert_eq!(CStr::from_ptr(res_ptr.as_ptr()).to_bytes(), bytes);
|
||||
Self::from_raw_nonnull(res_ptr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const NUL_REPLACE: &str = "␀";
|
||||
|
||||
#[cold]
|
||||
fn make_nonnull(v: &str) -> String {
|
||||
v.replace('\0', NUL_REPLACE)
|
||||
}
|
||||
|
||||
impl Drop for SqliteMallocString {
|
||||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
unsafe { ffi::sqlite3_free(self.ptr.as_ptr().cast()) };
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
||||
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_from_str() {
|
||||
let to_check = [
|
||||
("", ""),
|
||||
("\0", "␀"),
|
||||
("␀", "␀"),
|
||||
("\0bar", "␀bar"),
|
||||
("foo\0bar", "foo␀bar"),
|
||||
("foo\0", "foo␀"),
|
||||
("a\0b\0c\0\0d", "a␀b␀c␀␀d"),
|
||||
("foobar0123", "foobar0123"),
|
||||
];
|
||||
|
||||
for &(input, output) in &to_check {
|
||||
let s = SqliteMallocString::from_str(input);
|
||||
assert_eq!(s.to_string_lossy(), output);
|
||||
assert_eq!(s.as_cstr().to_str().unwrap(), output);
|
||||
}
|
||||
}
|
||||
|
||||
// This will trigger an asan error if into_raw still freed the ptr.
|
||||
#[test]
|
||||
fn test_lossy() {
|
||||
let p = SqliteMallocString::from_str("abcd").into_raw();
|
||||
// Make invalid
|
||||
let s = unsafe {
|
||||
p.cast::<u8>().write(b'\xff');
|
||||
SqliteMallocString::from_raw(p).unwrap()
|
||||
};
|
||||
assert_eq!(s.to_string_lossy().as_ref(), "\u{FFFD}bcd");
|
||||
}
|
||||
|
||||
// This will trigger an asan error if into_raw still freed the ptr.
|
||||
#[test]
|
||||
fn test_into_raw() {
|
||||
let mut v = vec![];
|
||||
for i in 0..1000 {
|
||||
v.push(SqliteMallocString::from_str(&i.to_string()).into_raw());
|
||||
v.push(SqliteMallocString::from_str(&format!("abc {i} 😀")).into_raw());
|
||||
}
|
||||
unsafe {
|
||||
for (i, s) in v.chunks_mut(2).enumerate() {
|
||||
let s0 = std::mem::replace(&mut s[0], std::ptr::null_mut());
|
||||
let s1 = std::mem::replace(&mut s[1], std::ptr::null_mut());
|
||||
assert_eq!(CStr::from_ptr(s0).to_str().unwrap(), &i.to_string());
|
||||
assert_eq!(CStr::from_ptr(s1).to_str().unwrap(), &format!("abc {i} 😀"));
|
||||
let _ = SqliteMallocString::from_raw(s0).unwrap();
|
||||
let _ = SqliteMallocString::from_raw(s1).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_alloc() {
|
||||
let err = alloc("error");
|
||||
unsafe { ffi::sqlite3_free(err.cast()) };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user