rusqlite/util/sqlite_string.rs
1// This is used when either vtab or modern-sqlite is on. Different methods are
2// used in each feature. Avoid having to track this for each function. We will
3// still warn for anything that's not used by either, though.
4#![cfg_attr(not(feature = "vtab"), allow(dead_code))]
5use crate::ffi;
6use std::ffi::{c_char, c_int, CStr};
7use std::marker::PhantomData;
8use std::ptr::NonNull;
9
10// Space to hold this string must be obtained
11// from an SQLite memory allocation function
12pub(crate) fn alloc(s: &str) -> *mut c_char {
13 SqliteMallocString::from_str(s).into_raw()
14}
15
16/// A string we own that's allocated on the SQLite heap. Automatically calls
17/// `sqlite3_free` when dropped, unless `into_raw` (or `into_inner`) is called
18/// on it. If constructed from a rust string, `sqlite3_malloc` is used.
19///
20/// It has identical representation to a nonnull `*mut c_char`, so you can use
21/// it transparently as one. It's nonnull, so Option<SqliteMallocString> can be
22/// used for nullable ones (it's still just one pointer).
23///
24/// Most strings shouldn't use this! Only places where the string needs to be
25/// freed with `sqlite3_free`. This includes `sqlite3_extended_sql` results,
26/// some error message pointers... Note that misuse is extremely dangerous!
27///
28/// Note that this is *not* a lossless interface. Incoming strings with internal
29/// NULs are modified, and outgoing strings which are non-UTF8 are modified.
30/// This seems unavoidable -- it tries very hard to not panic.
31#[repr(transparent)]
32pub(crate) struct SqliteMallocString {
33 ptr: NonNull<c_char>,
34 _boo: PhantomData<Box<[c_char]>>,
35}
36// This is owned data for a primitive type, and thus it's safe to implement
37// these. That said, nothing needs them, and they make things easier to misuse.
38
39// unsafe impl Send for SqliteMallocString {}
40// unsafe impl Sync for SqliteMallocString {}
41
42impl SqliteMallocString {
43 /// SAFETY: Caller must be certain that `m` a nul-terminated c string
44 /// allocated by `sqlite3_malloc`, and that SQLite expects us to free it!
45 #[inline]
46 pub(crate) unsafe fn from_raw_nonnull(ptr: NonNull<c_char>) -> Self {
47 Self {
48 ptr,
49 _boo: PhantomData,
50 }
51 }
52
53 /// SAFETY: Caller must be certain that `m` a nul-terminated c string
54 /// allocated by `sqlite3_malloc`, and that SQLite expects us to free it!
55 #[inline]
56 pub(crate) unsafe fn from_raw(ptr: *mut c_char) -> Option<Self> {
57 NonNull::new(ptr).map(|p| Self::from_raw_nonnull(p))
58 }
59
60 /// Get the pointer behind `self`. After this is called, we no longer manage
61 /// it.
62 #[inline]
63 pub(crate) fn into_inner(self) -> NonNull<c_char> {
64 let p = self.ptr;
65 std::mem::forget(self);
66 p
67 }
68
69 /// Get the pointer behind `self`. After this is called, we no longer manage
70 /// it.
71 #[inline]
72 pub(crate) fn into_raw(self) -> *mut c_char {
73 self.into_inner().as_ptr()
74 }
75
76 /// Borrow the pointer behind `self`. We still manage it when this function
77 /// returns. If you want to relinquish ownership, use `into_raw`.
78 #[inline]
79 pub(crate) fn as_ptr(&self) -> *const c_char {
80 self.ptr.as_ptr()
81 }
82
83 #[inline]
84 pub(crate) fn as_cstr(&self) -> &CStr {
85 unsafe { CStr::from_ptr(self.as_ptr()) }
86 }
87
88 #[inline]
89 pub(crate) fn to_string_lossy(&self) -> std::borrow::Cow<'_, str> {
90 self.as_cstr().to_string_lossy()
91 }
92
93 /// Convert `s` into a SQLite string.
94 ///
95 /// This should almost never be done except for cases like error messages or
96 /// other strings that SQLite frees.
97 ///
98 /// If `s` contains internal NULs, we'll replace them with
99 /// `NUL_REPLACE_CHAR`.
100 ///
101 /// Except for `debug_assert`s which may trigger during testing, this
102 /// function never panics. If we hit integer overflow or the allocation
103 /// fails, we call `handle_alloc_error` which aborts the program after
104 /// calling a global hook.
105 ///
106 /// This means it's safe to use in extern "C" functions even outside
107 /// `catch_unwind`.
108 pub(crate) fn from_str(s: &str) -> Self {
109 let s = if s.as_bytes().contains(&0) {
110 std::borrow::Cow::Owned(make_nonnull(s))
111 } else {
112 std::borrow::Cow::Borrowed(s)
113 };
114 debug_assert!(!s.as_bytes().contains(&0));
115 let bytes: &[u8] = s.as_ref().as_bytes();
116 let src_ptr: *const c_char = bytes.as_ptr().cast();
117 let src_len = bytes.len();
118 let maybe_len_plus_1 = s.len().checked_add(1).and_then(|v| c_int::try_from(v).ok());
119 unsafe {
120 let res_ptr = maybe_len_plus_1
121 .and_then(|len_to_alloc| {
122 // `>` because we added 1.
123 debug_assert!(len_to_alloc > 0);
124 debug_assert_eq!((len_to_alloc - 1) as usize, src_len);
125 NonNull::new(ffi::sqlite3_malloc(len_to_alloc).cast::<c_char>())
126 })
127 .unwrap_or_else(|| {
128 use std::alloc::{handle_alloc_error, Layout};
129 // Report via handle_alloc_error so that it can be handled with any
130 // other allocation errors and properly diagnosed.
131 //
132 // This is safe:
133 // - `align` is never 0
134 // - `align` is always a power of 2.
135 // - `size` needs no realignment because it's guaranteed to be aligned
136 // (everything is aligned to 1)
137 // - `size` is also never zero, although this function doesn't actually require
138 // it now.
139 let len = s.len().saturating_add(1).min(isize::MAX as usize);
140 let layout = Layout::from_size_align_unchecked(len, 1);
141 // Note: This call does not return.
142 handle_alloc_error(layout);
143 });
144 let buf: *mut c_char = res_ptr.as_ptr().cast::<c_char>();
145 src_ptr.copy_to_nonoverlapping(buf, src_len);
146 buf.add(src_len).write(0);
147 debug_assert_eq!(std::ffi::CStr::from_ptr(res_ptr.as_ptr()).to_bytes(), bytes);
148 Self::from_raw_nonnull(res_ptr)
149 }
150 }
151}
152
153const NUL_REPLACE: &str = "␀";
154
155#[cold]
156fn make_nonnull(v: &str) -> String {
157 v.replace('\0', NUL_REPLACE)
158}
159
160impl Drop for SqliteMallocString {
161 #[inline]
162 fn drop(&mut self) {
163 unsafe { ffi::sqlite3_free(self.ptr.as_ptr().cast()) };
164 }
165}
166
167#[cfg(test)]
168mod test {
169 use super::*;
170 #[test]
171 fn test_from_str() {
172 let to_check = [
173 ("", ""),
174 ("\0", "␀"),
175 ("␀", "␀"),
176 ("\0bar", "␀bar"),
177 ("foo\0bar", "foo␀bar"),
178 ("foo\0", "foo␀"),
179 ("a\0b\0c\0\0d", "a␀b␀c␀␀d"),
180 ("foobar0123", "foobar0123"),
181 ];
182
183 for &(input, output) in &to_check {
184 let s = SqliteMallocString::from_str(input);
185 assert_eq!(s.to_string_lossy(), output);
186 assert_eq!(s.as_cstr().to_str().unwrap(), output);
187 }
188 }
189
190 // This will trigger an asan error if into_raw still freed the ptr.
191 #[test]
192 fn test_lossy() {
193 let p = SqliteMallocString::from_str("abcd").into_raw();
194 // Make invalid
195 let s = unsafe {
196 p.cast::<u8>().write(b'\xff');
197 SqliteMallocString::from_raw(p).unwrap()
198 };
199 assert_eq!(s.to_string_lossy().as_ref(), "\u{FFFD}bcd");
200 }
201
202 // This will trigger an asan error if into_raw still freed the ptr.
203 #[test]
204 fn test_into_raw() {
205 let mut v = vec![];
206 for i in 0..1000 {
207 v.push(SqliteMallocString::from_str(&i.to_string()).into_raw());
208 v.push(SqliteMallocString::from_str(&format!("abc {i} 😀")).into_raw());
209 }
210 unsafe {
211 for (i, s) in v.chunks_mut(2).enumerate() {
212 let s0 = std::mem::replace(&mut s[0], std::ptr::null_mut());
213 let s1 = std::mem::replace(&mut s[1], std::ptr::null_mut());
214 assert_eq!(
215 std::ffi::CStr::from_ptr(s0).to_str().unwrap(),
216 &i.to_string()
217 );
218 assert_eq!(
219 std::ffi::CStr::from_ptr(s1).to_str().unwrap(),
220 &format!("abc {i} 😀")
221 );
222 let _ = SqliteMallocString::from_raw(s0).unwrap();
223 let _ = SqliteMallocString::from_raw(s1).unwrap();
224 }
225 }
226 }
227
228 #[test]
229 fn test_alloc() {
230 let err = alloc("error");
231 unsafe { ffi::sqlite3_free(err.cast()) };
232 }
233}