diff --git a/Cargo.lock b/Cargo.lock index e975866..50ada0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,14 +4,14 @@ version = 4 [[package]] name = "ns_error" -version = "0.1.7" +version = "0.1.8" dependencies = [ "thiserror", ] [[package]] name = "ns_io" -version = "0.1.7" +version = "0.1.8" dependencies = [ "ns_error", "ns_string", @@ -19,7 +19,10 @@ dependencies = [ [[package]] name = "ns_string" -version = "0.1.7" +version = "0.1.8" +dependencies = [ + "ns_error", +] [[package]] name = "proc-macro2" diff --git a/crates/ns_string/Cargo.toml b/crates/ns_string/Cargo.toml index 29246c0..68ec272 100644 --- a/crates/ns_string/Cargo.toml +++ b/crates/ns_string/Cargo.toml @@ -8,5 +8,6 @@ authors.workspace = true crate-type = ["lib", "staticlib", "cdylib"] [dependencies] +ns_error = { path = "../ns_error" } diff --git a/crates/ns_string/src/lib.rs b/crates/ns_string/src/lib.rs index 3f911f1..b1ea6c9 100644 --- a/crates/ns_string/src/lib.rs +++ b/crates/ns_string/src/lib.rs @@ -1,3 +1,4 @@ +use ns_error::NsError; use std::ffi::CStr; use std::mem::ManuallyDrop; use std::os::raw::c_char; @@ -24,61 +25,160 @@ pub struct NsString { pub data: NsStringData, } +/// Creates a new NsString from a C string. +/// +/// # Safety +/// +/// * `dest` must point to a valid, properly aligned `NsString` struct. +/// * `c_str` must be a valid, null-terminated C string, or a null pointer. #[allow(clippy::not_unsafe_ptr_arg_deref)] #[unsafe(no_mangle)] -pub extern "C" fn ns_string_new(c_str: *const c_char) -> NsString { +pub unsafe extern "C" fn ns_string_new(dest: *mut NsString, c_str: *const c_char) -> NsError { + if dest.is_null() { + return NsError::Any; + } + if c_str.is_null() { - return NsString { - len: 0, - is_heap: false, - data: NsStringData { - inline_data: [0; 24], - }, - }; + unsafe { + *dest = NsString { + len: 0, + is_heap: false, + data: NsStringData { + inline_data: [0; 24], + }, + }; + } + return NsError::Success; } let rust_str = unsafe { CStr::from_ptr(c_str) }.to_bytes(); - let len = rust_str.len(); - // If it is less than 24 bytes pack it into the struct itself if len < 24 { let mut inline = [0; 24]; inline[..len].copy_from_slice(rust_str); - NsString { - len, - is_heap: false, - data: NsStringData { - inline_data: inline, - }, + unsafe { + *dest = NsString { + len, + is_heap: false, + data: NsStringData { + inline_data: inline, + }, + }; } + NsError::Success } else { - // If it is more than 24 bytes, put it on the heap - let mut vec = rust_str.to_vec(); + let mut vec = Vec::new(); + if vec.try_reserve(len).is_err() { + return NsError::StringAllocFailed; + } + vec.extend_from_slice(rust_str); vec.shrink_to_fit(); let ptr = vec.as_mut_ptr(); - let capacity = vec.capacity(); + std::mem::forget(vec); + + unsafe { + *dest = NsString { + len, + is_heap: true, + data: NsStringData { + heap: ManuallyDrop::new(NsStringHeap { ptr, capacity }), + }, + }; + } + NsError::Success + } +} + +/// Concatenates two NsStrings safely. +/// +/// # Safety +/// +/// * `dest` must point to a valid, properly aligned `NsString` struct. +/// * `s1` and `s2` must be valid `NsString` structs initialized by this library. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn ns_string_concat( + dest: *mut NsString, + s1: NsString, + s2: NsString, +) -> NsError { + if dest.is_null() { + return NsError::Any; + } - // Preventing rust from immediately freeing the vector memory + let bytes1 = unsafe { + if s1.is_heap { + std::slice::from_raw_parts(s1.data.heap.ptr, s1.len) + } else { + std::slice::from_raw_parts(s1.data.inline_data.as_ptr(), s1.len) + } + }; + + let bytes2 = unsafe { + if s2.is_heap { + std::slice::from_raw_parts(s2.data.heap.ptr, s2.len) + } else { + std::slice::from_raw_parts(s2.data.inline_data.as_ptr(), s2.len) + } + }; + + let total_len = bytes1.len() + bytes2.len(); + + if total_len < 24 { + let mut inline = [0; 24]; + inline[..bytes1.len()].copy_from_slice(bytes1); + inline[bytes1.len()..total_len].copy_from_slice(bytes2); + + unsafe { + *dest = NsString { + len: total_len, + is_heap: false, + data: NsStringData { + inline_data: inline, + }, + }; + } + NsError::Success + } else { + let mut vec = Vec::new(); + if vec.try_reserve(total_len).is_err() { + return NsError::StringAllocFailed; + } + + vec.extend_from_slice(bytes1); + vec.extend_from_slice(bytes2); + vec.shrink_to_fit(); + + let ptr = vec.as_mut_ptr(); + let capacity = vec.capacity(); std::mem::forget(vec); - NsString { - len, - is_heap: true, - data: NsStringData { - heap: ManuallyDrop::new(NsStringHeap { ptr, capacity }), - }, + unsafe { + *dest = NsString { + len: total_len, + is_heap: true, + data: NsStringData { + heap: ManuallyDrop::new(NsStringHeap { ptr, capacity }), + }, + }; } + NsError::Success } } +/// Frees the heap memory associated with an NsString, if any. +/// +/// # Safety +/// +/// * `s` must be a valid pointer to an `NsString` previously initialized by this library. +/// * This function must not be called more than once on the same heap-allocated string. #[allow(clippy::not_unsafe_ptr_arg_deref)] #[unsafe(no_mangle)] -pub extern "C" fn ns_string_free(s: *mut NsString) { +pub unsafe extern "C" fn ns_string_free(s: *mut NsString) { if s.is_null() { return; } @@ -88,7 +188,6 @@ pub extern "C" fn ns_string_free(s: *mut NsString) { if s_ref.is_heap { unsafe { let heap = &s_ref.data.heap; - // Reclaim ownership of the memory and let it drop to free it let _ = Vec::from_raw_parts(heap.ptr, s_ref.len, heap.capacity); } } diff --git a/examples/08_string_error.c b/examples/08_string_error.c new file mode 100644 index 0000000..e8a9135 --- /dev/null +++ b/examples/08_string_error.c @@ -0,0 +1,72 @@ +#include "../include/ns.h" +#include "../include/ns_error.h" +#include "../include/ns_color.h" + +int main() { + ns_error_t err; + ns_string str1; + ns_string str2; + ns_string result; + + ns_println(NS_COLOR_CYAN NS_COLOR_BOLD "---- NextStd Safe String Example ----" NS_COLOR_RESET); + + // 1. Safely create the first string + NS_TRY(err, ns_string_new(&str1, "Hello, ")) { + ns_println(NS_COLOR_GREEN "Created str1 successfully." NS_COLOR_RESET); + } NS_EXCEPT(err, NS_ERROR_ANY) { + ns_println(NS_COLOR_RED "Failed to create str1." NS_COLOR_RESET); + return 1; + } + + // 2. Safely create the second string (Long enough to force a heap allocation!) + NS_TRY(err, ns_string_new(&str2, "World! This is a safe C string library.")) { + ns_println(NS_COLOR_GREEN "Created str2 successfully." NS_COLOR_RESET); + } NS_EXCEPT(err, NS_ERROR_ANY) { + ns_println(NS_COLOR_RED "Failed to create str2." NS_COLOR_RESET); + return 1; + } + + // 3. Safely concatenate them together + ns_println("\nAttempting to concatenate str1 and str2..."); + + NS_TRY(err, ns_string_concat(&result, str1, str2)) { + + ns_print(NS_COLOR_GREEN NS_COLOR_BOLD "Success! Result: " NS_COLOR_RESET); + + // Print the final string using your safe I/O macro! + ns_println_ns_string(result); + + } NS_EXCEPT(err, NS_ERROR_STRING_ALLOC) { + + ns_println(NS_COLOR_RED "Failed: Out of heap memory!" NS_COLOR_RESET); + + } NS_EXCEPT(err, NS_ERROR_ANY) { + + ns_print(NS_COLOR_RED "An unknown error occurred: "); + ns_println(ns_error_message(err)); + ns_print(NS_COLOR_RESET); + + } + + // 4. Test the NULL pointer crash-proofing + ns_println(NS_COLOR_CYAN NS_COLOR_BOLD "\n---- Testing NULL Pointer Safety ----" NS_COLOR_RESET); + + ns_string* bad_dest = 0; // NULL pointer + + NS_TRY(err, ns_string_concat(bad_dest, str1, str2)) { + ns_println(NS_COLOR_RED "CRITICAL FAILURE: This should not print!" NS_COLOR_RESET); + } NS_EXCEPT(err, NS_ERROR_ANY) { + ns_print(NS_COLOR_YELLOW "Safely caught the NULL pointer! Error Message: "); + ns_println(ns_error_message(err)); + ns_print(NS_COLOR_RESET); + } + + // 5. Clean up the memory to prevent leaks + ns_string_free(&str1); + ns_string_free(&str2); + ns_string_free(&result); + + ns_println(NS_COLOR_GREEN "\nMemory successfully freed. Exiting safely." NS_COLOR_RESET); + + return 0; +} diff --git a/include/ns_string.h b/include/ns_string.h index b5c21d2..9eeef4d 100644 --- a/include/ns_string.h +++ b/include/ns_string.h @@ -3,38 +3,49 @@ #include #include +#include "ns_error.h" // <-- Import your new error types! #ifdef __cplusplus extern "C" { #endif // Heap allocation details - typedef struct ns_string_heap{ + typedef struct ns_string_heap { char *ptr; size_t capacity; } ns_string_heap; // Union : 24 bytes of inline chars OR the heap struct - typedef union ns_string_data{ + typedef union ns_string_data { char inline_data[24]; ns_string_heap heap; } ns_string_data; // Final string struct - typedef struct ns_string{ + typedef struct ns_string { size_t len; bool is_heap; ns_string_data data; } ns_string; - // Core functions - ns_string ns_string_new(const char* c_str); + // ========================================== + // Core String Functions + // ========================================== + + // Safely creates a new string, returning an error if heap allocation fails. + // The result is written directly into the `dest` pointer. + ns_error_t ns_string_new(ns_string* dest, const char* c_str); + + // Safely concatenates two strings, returning an error if heap allocation fails. + // The result is written directly into the `dest` pointer. + ns_error_t ns_string_concat(ns_string* dest, ns_string s1, ns_string s2); + + // Safely frees heap memory if the string used it. + // Safe to call on inline strings (it just zeroes them out). void ns_string_free(ns_string* s); #ifdef __cplusplus - } - #endif // !__cplusplus #endif // !NS_STRING_H