Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/ns_string/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ authors.workspace = true
crate-type = ["lib", "staticlib", "cdylib"]

[dependencies]
ns_error = { path = "../ns_error" }


155 changes: 127 additions & 28 deletions crates/ns_string/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use ns_error::NsError;
use std::ffi::CStr;
use std::mem::ManuallyDrop;
use std::os::raw::c_char;
Expand All @@ -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;
}
Expand All @@ -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);
}
}
Expand Down
72 changes: 72 additions & 0 deletions examples/08_string_error.c
Original file line number Diff line number Diff line change
@@ -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;
}
25 changes: 18 additions & 7 deletions include/ns_string.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,49 @@

#include <stddef.h>
#include <stdbool.h>
#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