1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#![allow(unsafe_code)]
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
extern crate libsqlite3_sys as ffi;
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
use sqlite_wasm_rs as ffi;
use crate::result::{DatabaseErrorKind, Error};
use crate::QueryResult;
use std::ops::Deref;
use std::ptr::NonNull;
/// Owns a database serialization returned by `sqlite3_serialize` and releases any allocated buffer with `sqlite3_free`.
#[derive(Debug)]
pub struct SerializedDatabase {
state: State,
}
#[derive(Debug)]
enum State {
/// A successful serialization owning a SQLite allocation.
Owned { data: NonNull<u8>, len: i64 },
/// A valid serialization of an empty deserialized database.
Empty,
/// SQLite failed to allocate the output buffer.
AllocationFailed,
}
impl SerializedDatabase {
/// Creates a new `SerializedDatabase` with the given data pointer and length.
///
/// # Safety
///
/// `data` must exclusively own a successful `sqlite3_serialize` allocation that this value may free, and, if `0 <= len <= isize::MAX`, that allocation must hold `len` initialized bytes.
pub(crate) unsafe fn new(data: NonNull<u8>, len: i64) -> Self {
Self {
state: State::Owned { data, len },
}
}
pub(crate) fn empty() -> Self {
Self {
state: State::Empty,
}
}
pub(crate) fn allocation_failed() -> Self {
Self {
state: State::AllocationFailed,
}
}
/// Returns a slice of the serialized database.
///
/// # Panics
///
/// Panics if SQLite failed to allocate the buffer holding the serialized database, or if it reported a size this platform cannot address.
#[deprecated(note = "Use `SerializedDatabase::try_as_slice` instead")]
#[cfg(all(feature = "with-deprecated", not(feature = "without-deprecated")))]
pub fn as_slice(&self) -> &[u8] {
self.expect_slice()
}
#[cfg(not(all(feature = "with-deprecated", not(feature = "without-deprecated"))))]
fn as_slice(&self) -> &[u8] {
self.expect_slice()
}
/// Returns a slice of the serialized database, the out of memory error if
/// SQLite failed to allocate the buffer holding it, or a conversion error
/// if SQLite reported a size this platform cannot address.
pub fn try_as_slice(&self) -> QueryResult<&[u8]> {
match self.state {
State::Owned { data, len } => {
// `from_raw_parts` requires a length no larger than `isize::MAX`.
let len = isize::try_from(len).map_err(Error::IntegerConversion)?;
let len = usize::try_from(len).map_err(Error::IntegerConversion)?;
// SAFETY: `new` guarantees an exclusively owned immutable allocation of `len` initialized bytes for every addressable `len`.
Ok(unsafe { core::slice::from_raw_parts(data.as_ptr(), len) })
}
State::Empty => Ok(&[]),
State::AllocationFailed => Err(Error::DatabaseError(
DatabaseErrorKind::Unknown,
Box::new("out of memory".to_string()),
)),
}
}
fn expect_slice(&self) -> &[u8] {
match self.try_as_slice() {
Ok(slice) => slice,
Err(e) => panic!("Cannot access the serialized database: {e}"),
}
}
}
impl Deref for SerializedDatabase {
type Target = [u8];
#[allow(deprecated)] // no other way to implement this
fn deref(&self) -> &Self::Target {
self.as_slice()
}
}
impl Drop for SerializedDatabase {
/// Deallocates the memory of the serialized database when it goes out of scope.
fn drop(&mut self) {
if let State::Owned { data, .. } = self.state {
// SAFETY: `new` transfers one SQLite allocation that remains owned until this single `Drop` call.
unsafe {
ffi::sqlite3_free(data.as_ptr() as _);
}
}
}
}
#[cfg(all(test, target_pointer_width = "32"))]
mod tests {
use super::{ffi, SerializedDatabase};
use crate::result::Error;
use core::ptr::NonNull;
// A serialization one byte past `isize::MAX` still fits `usize`, so only an `isize` bound
// keeps it from building a slice `from_raw_parts` rejects. Its allocation is still freed.
#[diesel_test_helper::test]
fn oversized_serialization_is_rejected() {
let len = i64::try_from(isize::MAX).expect("`isize::MAX` fits `i64`") + 1;
// SAFETY: SQLite's allocator requires no connection and returns an exclusive allocation.
let data = unsafe { ffi::sqlite3_malloc(1) };
let data = NonNull::new(data.cast::<u8>()).expect("SQLite allocates a single byte");
// SAFETY: `data` exclusively owns a SQLite allocation and `len` exceeds `isize::MAX`, so no byte count is required of it.
let serialized = unsafe { SerializedDatabase::new(data, len) };
assert!(matches!(
serialized.try_as_slice(),
Err(Error::IntegerConversion(_))
));
}
}