remove unnecessary string clone

This commit is contained in:
Dustin J. Mitchell 2022-01-24 04:12:58 +00:00
parent 65082c26e7
commit 40f30c6d89
4 changed files with 104 additions and 16 deletions

View file

@ -10,20 +10,52 @@ TEST_CASE("creating borrowed strings does not crash") {
TEST_CASE("creating cloned strings does not crash") { TEST_CASE("creating cloned strings does not crash") {
char *abcdef = strdup("abcdef"); char *abcdef = strdup("abcdef");
TCString *s = tc_string_clone(abcdef); TCString *s = tc_string_clone(abcdef);
REQUIRE(s != NULL);
free(abcdef); free(abcdef);
CHECK(strcmp(tc_string_content(s), "abcdef") == 0); CHECK(strcmp(tc_string_content(s), "abcdef") == 0);
tc_string_free(s); tc_string_free(s);
} }
TEST_CASE("strings echo back their content") { TEST_CASE("borrowed strings echo back their content") {
TCString *s = tc_string_new("abcdef"); TCString *s = tc_string_new("abcdef");
REQUIRE(s != NULL);
CHECK(strcmp(tc_string_content(s), "abcdef") == 0); CHECK(strcmp(tc_string_content(s), "abcdef") == 0);
size_t len;
const char *buf = tc_string_content_with_len(s, &len);
REQUIRE(buf != NULL);
CHECK(len == 6);
CHECK(strncmp(buf, "abcdef", len) == 0);
tc_string_free(s);
}
TEST_CASE("cloned strings echo back their content") {
char *orig = strdup("abcdef");
TCString *s = tc_string_clone(orig);
REQUIRE(s != NULL);
free(orig);
CHECK(strcmp(tc_string_content(s), "abcdef") == 0);
size_t len;
const char *buf = tc_string_content_with_len(s, &len);
REQUIRE(buf != NULL);
CHECK(len == 6);
CHECK(strncmp(buf, "abcdef", len) == 0);
tc_string_free(s); tc_string_free(s);
} }
TEST_CASE("tc_string_content returns NULL for strings containing embedded NULs") { TEST_CASE("tc_string_content returns NULL for strings containing embedded NULs") {
TCString *s = tc_string_clone_with_len("ab\0de", 5); TCString *s = tc_string_clone_with_len("ab\0de", 5);
REQUIRE(s != NULL); REQUIRE(s != NULL);
CHECK(tc_string_content(s) == NULL); CHECK(tc_string_content(s) == NULL);
size_t len;
const char *buf = tc_string_content_with_len(s, &len);
REQUIRE(buf != NULL);
CHECK(len == 5);
CHECK(strncmp(buf, "ab\0de", len) == 0);
tc_string_free(s); tc_string_free(s);
} }

View file

@ -10,6 +10,7 @@ fn main() {
.with_language(Language::C) .with_language(Language::C)
.with_config(Config { .with_config(Config {
cpp_compat: true, cpp_compat: true,
usize_is_size_t: true,
enumeration: EnumConfig { enumeration: EnumConfig {
// this appears to still default to true for C // this appears to still default to true for C
enum_class: false, enum_class: false,

View file

@ -6,11 +6,21 @@ use std::path::PathBuf;
/// ///
/// Unless specified otherwise, functions in this API take ownership of a TCString when it appears /// Unless specified otherwise, functions in this API take ownership of a TCString when it appears
/// as a function argument, and transfer ownership to the caller when the TCString appears as a /// as a function argument, and transfer ownership to the caller when the TCString appears as a
/// return value or otput argument. /// return value or output argument.
pub enum TCString<'a> { pub enum TCString<'a> {
CString(CString), CString(CString),
CStr(&'a CStr), CStr(&'a CStr),
String(String), String(String),
/// None is the default value for TCString, but this variant is never seen by C code or by Rust
/// code outside of this module.
None,
}
impl<'a> Default for TCString<'a> {
fn default() -> Self {
TCString::None
}
} }
impl<'a> TCString<'a> { impl<'a> TCString<'a> {
@ -33,6 +43,7 @@ impl<'a> TCString<'a> {
TCString::CString(cstring) => cstring.as_c_str().to_str(), TCString::CString(cstring) => cstring.as_c_str().to_str(),
TCString::CStr(cstr) => cstr.to_str(), TCString::CStr(cstr) => cstr.to_str(),
TCString::String(string) => Ok(string.as_ref()), TCString::String(string) => Ok(string.as_ref()),
TCString::None => unreachable!(),
} }
} }
@ -41,6 +52,7 @@ impl<'a> TCString<'a> {
TCString::CString(cstring) => cstring.as_bytes(), TCString::CString(cstring) => cstring.as_bytes(),
TCString::CStr(cstr) => cstr.to_bytes(), TCString::CStr(cstr) => cstr.to_bytes(),
TCString::String(string) => string.as_bytes(), TCString::String(string) => string.as_bytes(),
TCString::None => unreachable!(),
} }
} }
@ -101,33 +113,66 @@ pub extern "C" fn tc_string_clone_with_len(
} }
/// Get the content of the string as a regular C string. The given string must not be NULL. The /// Get the content of the string as a regular C string. The given string must not be NULL. The
/// returned value may be NULL if the string contains NUL bytes. /// returned value is NULL if the string contains NUL bytes. The returned string is valid until
/// the TCString is freed or passed to another TC API function.
///
/// This function does _not_ take ownership of the TCString. /// This function does _not_ take ownership of the TCString.
#[no_mangle] #[no_mangle]
pub extern "C" fn tc_string_content(tcstring: *mut TCString) -> *const libc::c_char { pub extern "C" fn tc_string_content(tcstring: *mut TCString) -> *const libc::c_char {
let tcstring = TCString::from_arg_ref(tcstring); let tcstring = TCString::from_arg_ref(tcstring);
// if we have a String, we need to consume it and turn it into // if we have a String, we need to consume it and turn it into
// a CString. // a CString.
if let TCString::String(string) = tcstring { if matches!(tcstring, TCString::String(_)) {
// TODO: get rid of this clone if let TCString::String(string) = std::mem::take(tcstring) {
match CString::new(string.clone()) { match CString::new(string) {
Ok(cstring) => { Ok(cstring) => {
*tcstring = TCString::CString(cstring); *tcstring = TCString::CString(cstring);
} }
Err(_) => { Err(nul_err) => {
// TODO: could recover the underlying String // recover the underlying String from the NulError
let original_bytes = nul_err.into_vec();
// SAFETY: original_bytes just came from a String, so must be valid utf8
let string = unsafe { String::from_utf8_unchecked(original_bytes) };
*tcstring = TCString::String(string);
// and return NULL as advertized
return std::ptr::null(); return std::ptr::null();
} }
} }
} else {
unreachable!()
}
} }
match tcstring { match tcstring {
TCString::CString(cstring) => cstring.as_ptr(), TCString::CString(cstring) => cstring.as_ptr(),
TCString::String(_) => unreachable!(), // just converted this TCString::String(_) => unreachable!(), // just converted to CString
TCString::CStr(cstr) => cstr.as_ptr(), TCString::CStr(cstr) => cstr.as_ptr(),
TCString::None => unreachable!(),
} }
} }
/// Get the content of the string as a pointer and length. The given string must not be NULL.
/// This function can return any string, even one including NUL bytes. The returned string is
/// valid until the TCString is freed or passed to another TC API function.
///
/// This function does _not_ take ownership of the TCString.
#[no_mangle]
pub extern "C" fn tc_string_content_with_len(
tcstring: *mut TCString,
len_out: *mut usize,
) -> *const libc::c_char {
let tcstring = TCString::from_arg_ref(tcstring);
let bytes = match tcstring {
TCString::CString(cstring) => cstring.as_bytes(),
TCString::String(string) => string.as_bytes(),
TCString::CStr(cstr) => cstr.to_bytes(),
TCString::None => unreachable!(),
};
unsafe { *len_out = bytes.len() };
bytes.as_ptr() as *const libc::c_char
}
/// Free a TCString. /// Free a TCString.
#[no_mangle] #[no_mangle]
pub extern "C" fn tc_string_free(string: *mut TCString) { pub extern "C" fn tc_string_free(string: *mut TCString) {

View file

@ -1,4 +1,5 @@
#include <cstdarg> #include <cstdarg>
#include <cstddef>
#include <cstdint> #include <cstdint>
#include <cstdlib> #include <cstdlib>
#include <ostream> #include <ostream>
@ -40,7 +41,7 @@ struct TCUuid {
extern "C" { extern "C" {
extern const uintptr_t TC_UUID_STRING_BYTES; extern const size_t TC_UUID_STRING_BYTES;
/// Create a new TCReplica. /// Create a new TCReplica.
/// ///
@ -81,13 +82,22 @@ TCString *tc_string_clone(const char *cstr);
/// Create a new TCString containing the given string with the given length. This allows creation /// Create a new TCString containing the given string with the given length. This allows creation
/// of strings containing embedded NUL characters. If the given string is not valid UTF-8, this /// of strings containing embedded NUL characters. If the given string is not valid UTF-8, this
/// function will return NULL. /// function will return NULL.
TCString *tc_string_clone_with_len(const char *buf, uintptr_t len); TCString *tc_string_clone_with_len(const char *buf, size_t len);
/// Get the content of the string as a regular C string. The given string must not be NULL. The /// Get the content of the string as a regular C string. The given string must not be NULL. The
/// returned value may be NULL if the string contains NUL bytes. /// returned value is NULL if the string contains NUL bytes. The returned string is valid until
/// the TCString is freed or passed to another TC API function.
///
/// This function does _not_ take ownership of the TCString. /// This function does _not_ take ownership of the TCString.
const char *tc_string_content(TCString *tcstring); const char *tc_string_content(TCString *tcstring);
/// Get the content of the string as a pointer and length. The given string must not be NULL.
/// This function can return any string, even one including NUL bytes. The returned string is
/// valid until the TCString is freed or passed to another TC API function.
///
/// This function does _not_ take ownership of the TCString.
const char *tc_string_content_with_len(TCString *tcstring, size_t *len_out);
/// Free a TCString. /// Free a TCString.
void tc_string_free(TCString *string); void tc_string_free(TCString *string);