summaryrefslogtreecommitdiffstats
path: root/third_party/rust/rusqlite/src/util
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/rusqlite/src/util
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/rusqlite/src/util')
-rw-r--r--third_party/rust/rusqlite/src/util/mod.rs11
-rw-r--r--third_party/rust/rusqlite/src/util/param_cache.rs60
-rw-r--r--third_party/rust/rusqlite/src/util/small_cstr.rs170
-rw-r--r--third_party/rust/rusqlite/src/util/sqlite_string.rs236
4 files changed, 477 insertions, 0 deletions
diff --git a/third_party/rust/rusqlite/src/util/mod.rs b/third_party/rust/rusqlite/src/util/mod.rs
new file mode 100644
index 0000000000..2b8dcfda1e
--- /dev/null
+++ b/third_party/rust/rusqlite/src/util/mod.rs
@@ -0,0 +1,11 @@
+// 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.
+#[cfg(any(feature = "modern_sqlite", feature = "vtab"))]
+mod sqlite_string;
+#[cfg(any(feature = "modern_sqlite", feature = "vtab"))]
+pub(crate) use sqlite_string::SqliteMallocString;
diff --git a/third_party/rust/rusqlite/src/util/param_cache.rs b/third_party/rust/rusqlite/src/util/param_cache.rs
new file mode 100644
index 0000000000..6faced98af
--- /dev/null
+++ b/third_party/rust/rusqlite/src/util/param_cache.rs
@@ -0,0 +1,60 @@
+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 {
+ 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));
+ }
+}
diff --git a/third_party/rust/rusqlite/src/util/small_cstr.rs b/third_party/rust/rusqlite/src/util/small_cstr.rs
new file mode 100644
index 0000000000..78e43bd0b2
--- /dev/null
+++ b/third_party/rust/rusqlite/src/util/small_cstr.rs
@@ -0,0 +1,170 @@
+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(crate) 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 PartialEq<SmallCString> for str {
+ #[inline]
+ fn eq(&self, s: &SmallCString) -> bool {
+ s.as_bytes_without_nul() == self.as_bytes()
+ }
+}
+
+impl PartialEq<str> for SmallCString {
+ #[inline]
+ fn eq(&self, s: &str) -> bool {
+ self.as_bytes_without_nul() == s.as_bytes()
+ }
+}
+
+impl std::borrow::Borrow<str> for SmallCString {
+ #[inline]
+ fn borrow(&self) -> &str {
+ self.as_str()
+ }
+}
+
+#[cfg(test)]
+mod 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"");
+
+ assert!(SmallCString::new("\0").is_err());
+ assert!(SmallCString::new("\0abc").is_err());
+ assert!(SmallCString::new("abc\0").is_err());
+ }
+}
diff --git a/third_party/rust/rusqlite/src/util/sqlite_string.rs b/third_party/rust/rusqlite/src/util/sqlite_string.rs
new file mode 100644
index 0000000000..da261ba3bc
--- /dev/null
+++ b/third_party/rust/rusqlite/src/util/sqlite_string.rs
@@ -0,0 +1,236 @@
+// 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(all(feature = "vtab", feature = "modern-sqlite")),
+ allow(dead_code)
+)]
+use crate::ffi;
+use std::marker::PhantomData;
+use std::os::raw::{c_char, c_int};
+use std::ptr::NonNull;
+
+/// 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_malloc` 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_malloc`, 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_malloc`, 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) -> &std::ffi::CStr {
+ unsafe { std::ffi::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 of
+ /// `catch_unwind`.
+ pub(crate) fn from_str(s: &str) -> Self {
+ use std::convert::TryFrom;
+ 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| c_int::try_from(v).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_malloc(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 layout = Layout::from_size_align_unchecked(s.len().saturating_add(1), 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!(std::ffi::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()) };
+ }
+}
+
+impl std::fmt::Debug for SqliteMallocString {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ self.to_string_lossy().fmt(f)
+ }
+}
+
+impl std::fmt::Display for SqliteMallocString {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ self.to_string_lossy().fmt(f)
+ }
+}
+
+#[cfg(test)]
+mod 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!(
+ std::ffi::CStr::from_ptr(s0).to_str().unwrap(),
+ &i.to_string()
+ );
+ assert_eq!(
+ std::ffi::CStr::from_ptr(s1).to_str().unwrap(),
+ &format!("abc {} πŸ˜€", i)
+ );
+ let _ = SqliteMallocString::from_raw(s0).unwrap();
+ let _ = SqliteMallocString::from_raw(s1).unwrap();
+ }
+ }
+ }
+}