Skip to content

Commit 25a34f0

Browse files
Dr-EmannOyami-Srk
andauthored
feat(ffi): improve error buffer writing more (#331)
Builds on #276, but adds some improvements: No longer silently truncate to ERR_BUF_MAX_LEN, errors can now be any size the caller specifies Fixes a regression, there's no need for the error to be null terminated, and the value stored at *errbuf_len again matches the length of the actual error text, not the text plus a null terminator Runs safely under miri Closes #276 Commits: * feat(ffi): improve error buf writing * chore: make clippy happy * chore(ffi): Improve error buffer writing more - Take a `&mut usize` for errbuf_len, since the requirements we have for it match the requirements for &mut - Write directly into the provided buffer, rather than going through an intermediate string and cstring - Simplify doc strings - `size_of::<u8>()` is 1 by definition, and has no alignment requirements. * chore(docs): correct required length of `uuid_hex` parameter * mark ERR_BUF_MAX_LEN as deprecated --------- Co-authored-by: Shiroko <hhx.xxm@gmail.com>
1 parent b394693 commit 25a34f0

File tree

4 files changed

+77
-36
lines changed

4 files changed

+77
-36
lines changed

src/ffi/context.rs

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
use crate::ast::Value;
22
use crate::context::Context;
3-
use crate::ffi::{CValue, ERR_BUF_MAX_LEN};
3+
use crate::ffi::{write_errbuf, CValue};
44
use crate::schema::Schema;
5-
use std::cmp::min;
65
use std::ffi;
76
use std::os::raw::c_char;
87
use std::slice::from_raw_parts_mut;
@@ -78,28 +77,24 @@ pub unsafe extern "C" fn context_free(context: *mut Context) {
7877
/// * `field` must be a valid pointer to a C-style string,
7978
/// must be properply aligned, and must not have '\0' in the middle.
8079
/// * `value` must be a valid pointer to a [`CValue`].
81-
/// * `errbuf` must be valid to read and write for `errbuf_len * size_of::<u8>()` bytes,
82-
/// and it must be properly aligned.
83-
/// * `errbuf_len` must be vlaid to read and write for `size_of::<usize>()` bytes,
80+
/// * `errbuf` must be valid to read and write for `*errbuf_len` bytes.
81+
/// * `errbuf_len` must be valid to read and write for `size_of::<usize>()` bytes,
8482
/// and it must be properly aligned.
8583
#[no_mangle]
8684
pub unsafe extern "C" fn context_add_value(
8785
context: &mut Context,
8886
field: *const i8,
8987
value: &CValue,
9088
errbuf: *mut u8,
91-
errbuf_len: *mut usize,
89+
errbuf_len: &mut usize,
9290
) -> bool {
9391
let field = ffi::CStr::from_ptr(field as *const c_char)
9492
.to_str()
9593
.unwrap();
96-
let errbuf = from_raw_parts_mut(errbuf, ERR_BUF_MAX_LEN);
9794

9895
let value: Result<Value, _> = value.try_into();
9996
if let Err(e) = value {
100-
let errlen = min(e.len(), *errbuf_len);
101-
errbuf[..errlen].copy_from_slice(&e.as_bytes()[..errlen]);
102-
*errbuf_len = errlen;
97+
write_errbuf(e, errbuf, errbuf_len);
10398
return false;
10499
}
105100

@@ -165,9 +160,9 @@ pub unsafe extern "C" fn context_reset(context: &mut Context) {
165160
/// must be passed to [`router_execute`] before calling this function,
166161
/// and must not be reset by [`context_reset`] before calling this function.
167162
/// - If `uuid_hex` is not `NULL`, `uuid_hex` must be valid to read and write for
168-
/// `16 * size_of::<u8>()` bytes, and it must be properly aligned.
163+
/// `36` bytes.
169164
/// - If `matched_field` is not `NULL`,
170-
/// `matched_field` must be a vlaid pointer to a C-style string,
165+
/// `matched_field` must be a valid pointer to a C-style string,
171166
/// must be properly aligned, and must not have '\0' in the middle.
172167
/// - If `matched_value` is not `NULL`,
173168
/// `matched_value` must be valid to read and write for

src/ffi/expression.rs

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
use crate::ast::{BinaryOperator, Expression, LogicalExpression, Predicate};
2-
use crate::ffi::ERR_BUF_MAX_LEN;
2+
use crate::ffi::write_errbuf;
33
use crate::schema::Schema;
44
use bitflags::bitflags;
5-
use std::cmp::min;
65
use std::ffi;
76
use std::os::raw::c_char;
87
use std::slice::from_raw_parts_mut;
@@ -137,11 +136,11 @@ pub const ATC_ROUTER_EXPRESSION_VALIDATE_BUF_TOO_SMALL: i64 = 2;
137136
///
138137
/// - `atc` must be a valid pointer to a C-style string, properly aligned, and must not contain an internal `\0`.
139138
/// - `schema` must be a valid pointer returned by [`schema_new`].
140-
/// - `fields_buf`, must be valid for writing `fields_buf_len * size_of::<u8>()` bytes and properly aligned.
139+
/// - `fields_buf`, must be valid for writing `*fields_buf_len` bytes.
141140
/// - `fields_buf_len` must be a valid pointer to write `size_of::<usize>()` bytes and properly aligned.
142141
/// - `fields_total` must be a valid pointer to write `size_of::<usize>()` bytes and properly aligned.
143142
/// - `operators` must be a valid pointer to write `size_of::<u64>()` bytes and properly aligned.
144-
/// - `errbuf` must be valid for reading and writing `errbuf_len * size_of::<u8>()` bytes and properly aligned.
143+
/// - `errbuf` must be valid for reading and writing `*errbuf_len` bytes.
145144
/// - `errbuf_len` must be a valid pointer for reading and writing `size_of::<usize>()` bytes and properly aligned.
146145
///
147146
/// [`schema_new`]: crate::ffi::schema::schema_new
@@ -154,32 +153,27 @@ pub unsafe extern "C" fn expression_validate(
154153
fields_total: *mut usize,
155154
operators: *mut u64,
156155
errbuf: *mut u8,
157-
errbuf_len: *mut usize,
156+
errbuf_len: &mut usize,
158157
) -> i64 {
159158
use std::collections::HashSet;
160159

161160
use crate::parser::parse;
162161
use crate::semantics::Validate;
163162

164163
let atc = ffi::CStr::from_ptr(atc as *const c_char).to_str().unwrap();
165-
let errbuf = from_raw_parts_mut(errbuf, ERR_BUF_MAX_LEN);
166164

167165
// Parse the expression
168-
let result = parse(atc).map_err(|e| e.to_string());
166+
let result = parse(atc);
169167
if let Err(e) = result {
170-
let errlen = min(e.len(), *errbuf_len);
171-
errbuf[..errlen].copy_from_slice(&e.as_bytes()[..errlen]);
172-
*errbuf_len = errlen;
168+
write_errbuf(e, errbuf, errbuf_len);
173169
return ATC_ROUTER_EXPRESSION_VALIDATE_FAILED;
174170
}
175171
// Unwrap is safe since we've already checked for error
176172
let ast = result.unwrap();
177173

178174
// Validate expression with schema
179-
if let Err(e) = ast.validate(schema).map_err(|e| e.to_string()) {
180-
let errlen = min(e.len(), *errbuf_len);
181-
errbuf[..errlen].copy_from_slice(&e.as_bytes()[..errlen]);
182-
*errbuf_len = errlen;
175+
if let Err(e) = ast.validate(schema) {
176+
write_errbuf(e, errbuf, errbuf_len);
183177
return ATC_ROUTER_EXPRESSION_VALIDATE_FAILED;
184178
}
185179

@@ -224,6 +218,7 @@ pub unsafe extern "C" fn expression_validate(
224218
mod tests {
225219
use super::*;
226220
use crate::ast::Type;
221+
use crate::ffi::ERR_BUF_MAX_LEN;
227222

228223
fn expr_validate_on(
229224
schema: &Schema,
@@ -241,7 +236,7 @@ mod tests {
241236

242237
let result = unsafe {
243238
expression_validate(
244-
atc.as_bytes().as_ptr(),
239+
atc.as_ptr().cast(),
245240
schema,
246241
fields_buf.as_mut_ptr(),
247242
&mut fields_buf_len,

src/ffi/mod.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,17 @@ use crate::ast::Value;
77
use cidr::IpCidr;
88
use std::convert::TryFrom;
99
use std::ffi;
10+
use std::fmt::Display;
1011
use std::net::IpAddr;
1112
use std::os::raw::c_char;
1213
use std::slice::from_raw_parts;
14+
use std::slice::from_raw_parts_mut;
1315

16+
/// A _suggestion_ of the value to use for `errbuf_len`
17+
///
18+
/// This value is actually not used for anything in this library,
19+
/// any length can be passed.
20+
#[deprecated]
1421
pub const ERR_BUF_MAX_LEN: usize = 4096;
1522

1623
#[derive(Debug)]
@@ -56,3 +63,27 @@ impl TryFrom<&CValue> for Value {
5663
})
5764
}
5865
}
66+
67+
/// Write displayable error message to the error buffer.
68+
///
69+
/// # Arguments
70+
///
71+
/// - `err`: the displayable error message.
72+
/// - `errbuf`: a buffer to store the error message.
73+
/// - `errbuf_len`: a pointer to the length of the error message buffer.
74+
///
75+
/// # Safety
76+
///
77+
/// Violating any of the following constraints will result in undefined behavior:
78+
///
79+
/// * `errbuf` must be valid to read and write for `*errbuf_len` bytes.
80+
unsafe fn write_errbuf(err: impl Display, errbuf: *mut u8, errbuf_len: &mut usize) {
81+
use std::io::Write;
82+
83+
let orig_len = *errbuf_len;
84+
let mut errbuf = from_raw_parts_mut(errbuf, orig_len);
85+
// Ignore truncation error
86+
let _ = write!(errbuf, "{}", err);
87+
let remaining_len = errbuf.len();
88+
*errbuf_len = orig_len - remaining_len;
89+
}

src/ffi/router.rs

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
use crate::context::Context;
2-
use crate::ffi::ERR_BUF_MAX_LEN;
2+
use crate::ffi::write_errbuf;
33
use crate::router::Router;
44
use crate::schema::Schema;
5-
use std::cmp::min;
65
use std::ffi;
76
use std::os::raw::c_char;
87
use std::slice::from_raw_parts_mut;
@@ -87,8 +86,7 @@ pub unsafe extern "C" fn router_free(router: *mut Router<&Schema>) {
8786
/// and must not have '\0' in the middle.
8887
/// - `atc` must be a valid pointer to a C-style string, must be properly aligned,
8988
/// and must not have '\0' in the middle.
90-
/// - `errbuf` must be valid to read and write for `errbuf_len * size_of::<u8>()` bytes,
91-
/// and it must be properly aligned.
89+
/// - `errbuf` must be valid to read and write for `*errbuf_len` bytes.
9290
/// - `errbuf_len` must be valid to read and write for `size_of::<usize>()` bytes,
9391
/// and it must be properly aligned.
9492
#[no_mangle]
@@ -98,18 +96,15 @@ pub unsafe extern "C" fn router_add_matcher(
9896
uuid: *const i8,
9997
atc: *const i8,
10098
errbuf: *mut u8,
101-
errbuf_len: *mut usize,
99+
errbuf_len: &mut usize,
102100
) -> bool {
103101
let uuid = ffi::CStr::from_ptr(uuid as *const c_char).to_str().unwrap();
104102
let atc = ffi::CStr::from_ptr(atc as *const c_char).to_str().unwrap();
105-
let errbuf = from_raw_parts_mut(errbuf, ERR_BUF_MAX_LEN);
106103

107104
let uuid = Uuid::try_parse(uuid).expect("invalid UUID format");
108105

109106
if let Err(e) = router.add_matcher(priority, uuid, atc) {
110-
let errlen = min(e.len(), *errbuf_len);
111-
errbuf[..errlen].copy_from_slice(&e.as_bytes()[..errlen]);
112-
*errbuf_len = errlen;
107+
write_errbuf(e, errbuf, errbuf_len);
113108
return false;
114109
}
115110

@@ -246,6 +241,31 @@ pub unsafe extern "C" fn router_get_fields(
246241
#[cfg(test)]
247242
mod tests {
248243
use super::*;
244+
use crate::ffi::ERR_BUF_MAX_LEN;
245+
246+
#[test]
247+
fn test_short_error_buf() {
248+
unsafe {
249+
let schema = Schema::default();
250+
let mut router = Router::new(&schema);
251+
let uuid = ffi::CString::new("a921a9aa-ec0e-4cf3-a6cc-1aa5583d150c").unwrap();
252+
let junk = ffi::CString::new(vec![b'a'; ERR_BUF_MAX_LEN * 2]).unwrap();
253+
let mut errbuf = vec![b'X'; ERR_BUF_MAX_LEN];
254+
let mut errbuf_len = 10;
255+
256+
let result = router_add_matcher(
257+
&mut router,
258+
1,
259+
uuid.as_ptr() as *const i8,
260+
junk.as_ptr() as *const i8,
261+
errbuf[..errbuf_len].as_mut_ptr(),
262+
&mut errbuf_len,
263+
);
264+
assert!(!result);
265+
assert_eq!(errbuf_len, 10);
266+
assert_eq!(errbuf[10], b'X');
267+
}
268+
}
249269

250270
#[test]
251271
fn test_long_error_message() {

0 commit comments

Comments
 (0)