Skip to content

Commit 844b730

Browse files
Merge pull request #435 from RedisLabsModules/push-qowsolyxxttn
fix: add `to_raw_parts` method & fix unsoundness bug
2 parents 8837582 + b7031b9 commit 844b730

File tree

4 files changed

+36
-21
lines changed

4 files changed

+36
-21
lines changed

examples/scan_keys.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ fn scan_keys(ctx: &Context, _args: Vec<RedisString>) -> RedisResult {
1717
let cursor = KeysCursor::new();
1818
let mut res = Vec::new();
1919

20-
let scan_callback = |_ctx: &Context, key_name: RedisString, _key: Option<&RedisKey>| {
21-
res.push(RedisValue::BulkRedisString(key_name));
20+
let scan_callback = |ctx: &Context, key_name: &RedisString, _key: Option<&RedisKey>| {
21+
res.push(RedisValue::BulkRedisString(key_name.safe_clone(ctx)));
2222
};
2323

2424
while cursor.scan(ctx, &scan_callback) {

src/context/key_cursor.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::{
22
ffi::c_void,
3+
mem,
34
ptr::{self},
45
};
56

@@ -24,7 +25,7 @@ use crate::{key::RedisKey, raw, RedisString};
2425
/// fn example_scan_key_for_each(ctx: &Context) -> RedisResult {
2526
/// let key = ctx.open_key_with_flags("user:123", KeyFlags::NOEFFECTS | KeyFlags::NOEXPIRE | KeyFlags::ACCESS_EXPIRED );
2627
/// let cursor = ScanKeyCursor::new(key);
27-
///
28+
///
2829
/// let res = RefCell::new(Vec::new());
2930
/// cursor.for_each(|_key, field, value| {
3031
/// let mut res = res.borrow_mut();
@@ -92,11 +93,10 @@ impl ScanKeyCursor {
9293
let callback = unsafe { &mut *(data.cast::<F>()) };
9394
callback(&key, &field, &value);
9495

95-
// we're not the owner of field and value strings
96-
field.take();
97-
value.take();
98-
99-
key.take(); // we're not the owner of the key either
96+
// We don't own any of the passed in pointers, so we must ensure we don't run their destructors here
97+
mem::forget(field);
98+
mem::forget(value);
99+
mem::forget(key);
100100
}
101101

102102
// Safety: The c-side initialized the function ptr and it is is never changed,

src/context/keys_cursor.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ use crate::key::RedisKey;
33
use crate::raw;
44
use crate::redismodule::RedisString;
55
use std::ffi::c_void;
6+
use std::mem;
67
use std::ptr::NonNull;
78

89
pub struct KeysCursor {
910
inner_cursor: *mut raw::RedisModuleScanCursor,
1011
}
1112

12-
extern "C" fn scan_callback<C: FnMut(&Context, RedisString, Option<&RedisKey>)>(
13+
extern "C" fn scan_callback<C: FnMut(&Context, &RedisString, Option<&RedisKey>)>(
1314
ctx: *mut raw::RedisModuleCtx,
1415
key_name: *mut raw::RedisModuleString,
1516
key: *mut raw::RedisModuleKey,
@@ -20,13 +21,17 @@ extern "C" fn scan_callback<C: FnMut(&Context, RedisString, Option<&RedisKey>)>(
2021
let redis_key = if key.is_null() {
2122
None
2223
} else {
23-
Some(RedisKey::from_raw_parts(ctx, key))
24+
// Safety: The returned `RedisKey` does not outlive this callbacks and so by necessity
25+
// the pointers passed in as parameters are valid for its entire lifetime.
26+
Some(unsafe { RedisKey::from_raw_parts(ctx, key) })
2427
};
2528
let callback = unsafe { &mut *(private_data.cast::<C>()) };
26-
callback(&context, key_name, redis_key.as_ref());
29+
callback(&context, &key_name, redis_key.as_ref());
2730

28-
// we are not the owner of the key, so we must take the underline *mut raw::RedisModuleKey so it will not be freed.
29-
redis_key.map(|v| v.take());
31+
// We don't own any of the passed in pointers and have just created "temporary RAII types".
32+
// We must ensure we don't run their destructors here.
33+
mem::forget(redis_key);
34+
mem::forget(key_name);
3035
}
3136

3237
impl KeysCursor {
@@ -35,7 +40,7 @@ impl KeysCursor {
3540
Self { inner_cursor }
3641
}
3742

38-
pub fn scan<F: FnMut(&Context, RedisString, Option<&RedisKey>)>(
43+
pub fn scan<F: FnMut(&Context, &RedisString, Option<&RedisKey>)>(
3944
&self,
4045
ctx: &Context,
4146
callback: &F,

src/key.rs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,6 @@ pub struct RedisKey {
5959
}
6060

6161
impl RedisKey {
62-
pub(crate) fn take(mut self) -> *mut raw::RedisModuleKey {
63-
let res = self.key_inner;
64-
self.key_inner = std::ptr::null_mut();
65-
res
66-
}
67-
6862
pub fn open(ctx: *mut raw::RedisModuleCtx, key: &RedisString) -> Self {
6963
let key_inner = raw::open_key(ctx, key.inner, to_raw_mode(KeyMode::Read));
7064
Self { ctx, key_inner }
@@ -80,13 +74,29 @@ impl RedisKey {
8074
Self { ctx, key_inner }
8175
}
8276

83-
pub const fn from_raw_parts(
77+
/// Construct a new `RedisKey` from a pointer to a redismodule context and a key.
78+
///
79+
/// # Safety
80+
///
81+
/// The caller must ensure:
82+
/// 1. The `ctx` pointer remains valid for the lifetime of the `RedisKey`.
83+
/// 2. The `key_inner` pointer remains valid for the lifetime of the `RedisKey`.
84+
pub const unsafe fn from_raw_parts(
8485
ctx: *mut raw::RedisModuleCtx,
8586
key_inner: *mut raw::RedisModuleKey,
8687
) -> Self {
8788
Self { ctx, key_inner }
8889
}
8990

91+
/// Decomposes a `RedisKey` into its raw components: `(redismodule context pointer, key pointer)`.
92+
///
93+
/// After calling this function, the caller is responsible for cleaning up the raw key previously managed by the `RedisKey`.
94+
/// The only way to do this safely is to convert the raw redismodule context and key pointers back into a `RedisKey` with
95+
/// the [`from_raw_parts`][Self::from_raw_parts] function, allowing the destructor to perform the cleanup.
96+
pub fn to_raw_parts(self) -> (*mut raw::RedisModuleCtx, *mut raw::RedisModuleKey) {
97+
(self.ctx, self.key_inner)
98+
}
99+
90100
/// # Panics
91101
///
92102
/// Will panic if `RedisModule_ModuleTypeGetValue` is missing in redismodule.h

0 commit comments

Comments
 (0)