diff --git a/lib/src/replica.rs b/lib/src/replica.rs index efa5dcbc2..07cb3f7a8 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -91,7 +91,7 @@ where /// Create a new TCReplica with an in-memory database. The contents of the database will be /// lost when it is freed. #[no_mangle] -pub extern "C" fn tc_replica_new_in_memory() -> *mut TCReplica { +pub unsafe extern "C" fn tc_replica_new_in_memory() -> *mut TCReplica { let storage = StorageConfig::InMemory .into_storage() .expect("in-memory always succeeds"); @@ -102,7 +102,7 @@ pub extern "C" fn tc_replica_new_in_memory() -> *mut TCReplica { /// Create a new TCReplica with an on-disk database having the given filename. On error, a string /// is written to the `error_out` parameter (if it is not NULL) and NULL is returned. #[no_mangle] -pub extern "C" fn tc_replica_new_on_disk<'a>( +pub unsafe extern "C" fn tc_replica_new_on_disk<'a>( path: *mut TCString, error_out: *mut *mut TCString, ) -> *mut TCReplica { @@ -138,7 +138,7 @@ pub extern "C" fn tc_replica_new_on_disk<'a>( /// Returns NULL when the task does not exist, and on error. Consult tc_replica_error /// to distinguish the two conditions. #[no_mangle] -pub extern "C" fn tc_replica_get_task(rep: *mut TCReplica, tcuuid: TCUuid) -> *mut TCTask { +pub unsafe extern "C" fn tc_replica_get_task(rep: *mut TCReplica, tcuuid: TCUuid) -> *mut TCTask { wrap( rep, |rep| { @@ -158,7 +158,7 @@ pub extern "C" fn tc_replica_get_task(rep: *mut TCReplica, tcuuid: TCUuid) -> *m /// /// Returns the task, or NULL on error. #[no_mangle] -pub extern "C" fn tc_replica_new_task( +pub unsafe extern "C" fn tc_replica_new_task( rep: *mut TCReplica, status: TCStatus, description: *mut TCString, @@ -179,7 +179,7 @@ pub extern "C" fn tc_replica_new_task( /// /// Returns the task, or NULL on error. #[no_mangle] -pub extern "C" fn tc_replica_import_task_with_uuid( +pub unsafe extern "C" fn tc_replica_import_task_with_uuid( rep: *mut TCReplica, tcuuid: TCUuid, ) -> *mut TCTask { @@ -202,7 +202,7 @@ pub extern "C" fn tc_replica_import_task_with_uuid( /// If undone_out is not NULL, then on success it is set to 1 if operations were undone, or 0 if /// there are no operations that can be done. #[no_mangle] -pub extern "C" fn tc_replica_undo<'a>(rep: *mut TCReplica, undone_out: *mut i32) -> TCResult { +pub unsafe extern "C" fn tc_replica_undo<'a>(rep: *mut TCReplica, undone_out: *mut i32) -> TCResult { wrap( rep, |rep| { @@ -223,7 +223,7 @@ pub extern "C" fn tc_replica_undo<'a>(rep: *mut TCReplica, undone_out: *mut i32) /// to this function will return NULL. The rep pointer must not be NULL. The caller must free the /// returned string. #[no_mangle] -pub extern "C" fn tc_replica_error<'a>(rep: *mut TCReplica) -> *mut TCString<'static> { +pub unsafe extern "C" fn tc_replica_error<'a>(rep: *mut TCReplica) -> *mut TCString<'static> { // SAFETY: see type docstring let rep: &'a mut TCReplica = unsafe { TCReplica::from_arg_ref_mut(rep) }; if let Some(tcstring) = rep.error.take() { @@ -237,7 +237,7 @@ pub extern "C" fn tc_replica_error<'a>(rep: *mut TCReplica) -> *mut TCString<'st /// Free a replica. The replica may not be used after this function returns and must not be freed /// more than once. #[no_mangle] -pub extern "C" fn tc_replica_free(rep: *mut TCReplica) { +pub unsafe extern "C" fn tc_replica_free(rep: *mut TCReplica) { // SAFETY: see type docstring let replica = unsafe { TCReplica::take_from_arg(rep) }; if replica.mut_borrowed { diff --git a/lib/src/string.rs b/lib/src/string.rs index 138ff31ea..b29258ae0 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -139,7 +139,7 @@ impl<'a> From<&str> for TCString<'static> { /// free(url); // string is no longer referenced and can be freed /// ``` #[no_mangle] -pub extern "C" fn tc_string_borrow(cstr: *const libc::c_char) -> *mut TCString<'static> { +pub unsafe extern "C" fn tc_string_borrow(cstr: *const libc::c_char) -> *mut TCString<'static> { debug_assert!(!cstr.is_null()); // SAFETY: // - cstr is not NULL (promised by caller, verified by assertion) @@ -154,7 +154,7 @@ pub extern "C" fn tc_string_borrow(cstr: *const libc::c_char) -> *mut TCString<' /// Create a new TCString by cloning the content of the given C string. The resulting TCString /// is independent of the given string, which can be freed or overwritten immediately. #[no_mangle] -pub extern "C" fn tc_string_clone(cstr: *const libc::c_char) -> *mut TCString<'static> { +pub unsafe extern "C" fn tc_string_clone(cstr: *const libc::c_char) -> *mut TCString<'static> { debug_assert!(!cstr.is_null()); // SAFETY: // - cstr is not NULL (promised by caller, verified by assertion) @@ -172,7 +172,7 @@ pub extern "C" fn tc_string_clone(cstr: *const libc::c_char) -> *mut TCString<'s /// /// The given length must be less than half the maximum value of usize. #[no_mangle] -pub extern "C" fn tc_string_clone_with_len( +pub unsafe extern "C" fn tc_string_clone_with_len( buf: *const libc::c_char, len: usize, ) -> *mut TCString<'static> { @@ -212,7 +212,7 @@ pub extern "C" fn tc_string_clone_with_len( /// /// This function does _not_ take ownership of the TCString. #[no_mangle] -pub extern "C" fn tc_string_content(tcstring: *mut TCString) -> *const libc::c_char { +pub unsafe extern "C" fn tc_string_content(tcstring: *mut TCString) -> *const libc::c_char { // SAFETY: // - tcstring is not NULL (promised by caller) // - lifetime of tcstring outlives the lifetime of this function @@ -239,7 +239,7 @@ pub extern "C" fn tc_string_content(tcstring: *mut TCString) -> *const libc::c_c /// /// This function does _not_ take ownership of the TCString. #[no_mangle] -pub extern "C" fn tc_string_content_with_len( +pub unsafe extern "C" fn tc_string_content_with_len( tcstring: *mut TCString, len_out: *mut usize, ) -> *const libc::c_char { @@ -262,7 +262,7 @@ pub extern "C" fn tc_string_content_with_len( /// Free a TCString. The given string must not be NULL. The string must not be used /// after this function returns, and must not be freed more than once. #[no_mangle] -pub extern "C" fn tc_string_free(tcstring: *mut TCString) { +pub unsafe extern "C" fn tc_string_free(tcstring: *mut TCString) { // SAFETY: // - tcstring is not NULL (promised by caller) // - caller is exclusive owner of tcstring (promised by caller) diff --git a/lib/src/strings.rs b/lib/src/strings.rs index 55572ec25..751a0aaed 100644 --- a/lib/src/strings.rs +++ b/lib/src/strings.rs @@ -40,7 +40,7 @@ impl PointerArray for TCStrings { /// /// When this call returns, the `items` pointer will be NULL, signalling an invalid TCStrings. #[no_mangle] -pub extern "C" fn tc_strings_free(tcstrings: *mut TCStrings) { +pub unsafe extern "C" fn tc_strings_free(tcstrings: *mut TCStrings) { debug_assert!(!tcstrings.is_null()); // SAFETY: // - *tcstrings is a valid TCStrings (caller promises to treat it as read-only) @@ -63,7 +63,8 @@ mod test { #[test] fn free_sets_null_pointer() { let mut tcstrings = TCStrings::return_val(Vec::new()); - tc_strings_free(&mut tcstrings); + // SAFETY: testing expected behavior + unsafe { tc_strings_free(&mut tcstrings) }; assert!(tcstrings.items.is_null()); assert_eq!(tcstrings.len, 0); assert_eq!(tcstrings._capacity, 0); diff --git a/lib/src/task.rs b/lib/src/task.rs index 823fbbffe..f37f08a09 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -218,7 +218,7 @@ fn to_datetime(time: libc::time_t) -> Option> { /// if (!success) { ... } /// ``` #[no_mangle] -pub extern "C" fn tc_task_to_mut<'a>(task: *mut TCTask, tcreplica: *mut TCReplica) { +pub unsafe extern "C" fn tc_task_to_mut<'a>(task: *mut TCTask, tcreplica: *mut TCReplica) { // SAFETY: // - task is not null (promised by caller) // - task outlives 'a (promised by caller) @@ -236,7 +236,7 @@ pub extern "C" fn tc_task_to_mut<'a>(task: *mut TCTask, tcreplica: *mut TCReplic /// /// The replica passed to `tc_task_to_mut` may be used freely after this call. #[no_mangle] -pub extern "C" fn tc_task_to_immut<'a>(task: *mut TCTask) { +pub unsafe extern "C" fn tc_task_to_immut<'a>(task: *mut TCTask) { // SAFETY: // - task is not null (promised by caller) // - task outlives 'a (promised by caller) @@ -246,13 +246,13 @@ pub extern "C" fn tc_task_to_immut<'a>(task: *mut TCTask) { /// Get a task's UUID. #[no_mangle] -pub extern "C" fn tc_task_get_uuid(task: *mut TCTask) -> TCUuid { +pub unsafe extern "C" fn tc_task_get_uuid(task: *mut TCTask) -> TCUuid { wrap(task, |task| TCUuid::return_val(task.get_uuid())) } /// Get a task's status. #[no_mangle] -pub extern "C" fn tc_task_get_status<'a>(task: *mut TCTask) -> TCStatus { +pub unsafe extern "C" fn tc_task_get_status<'a>(task: *mut TCTask) -> TCStatus { wrap(task, |task| task.get_status().into()) } @@ -261,7 +261,7 @@ pub extern "C" fn tc_task_get_status<'a>(task: *mut TCTask) -> TCStatus { /// Get a task's description, or NULL if the task cannot be represented as a C string (e.g., if it /// contains embedded NUL characters). #[no_mangle] -pub extern "C" fn tc_task_get_description<'a>(task: *mut TCTask) -> *mut TCString<'static> { +pub unsafe extern "C" fn tc_task_get_description<'a>(task: *mut TCTask) -> *mut TCString<'static> { wrap(task, |task| { let descr: TCString = task.get_description().into(); // SAFETY: see TCString docstring @@ -271,38 +271,38 @@ pub extern "C" fn tc_task_get_description<'a>(task: *mut TCTask) -> *mut TCStrin /// Get the entry timestamp for a task (when it was created), or 0 if not set. #[no_mangle] -pub extern "C" fn tc_task_get_entry<'a>(task: *mut TCTask) -> libc::time_t { +pub unsafe extern "C" fn tc_task_get_entry<'a>(task: *mut TCTask) -> libc::time_t { wrap(task, |task| to_time_t(task.get_entry())) } /// Get the wait timestamp for a task, or 0 if not set. #[no_mangle] -pub extern "C" fn tc_task_get_wait<'a>(task: *mut TCTask) -> libc::time_t { +pub unsafe extern "C" fn tc_task_get_wait<'a>(task: *mut TCTask) -> libc::time_t { wrap(task, |task| to_time_t(task.get_wait())) } /// Get the modified timestamp for a task, or 0 if not set. #[no_mangle] -pub extern "C" fn tc_task_get_modified<'a>(task: *mut TCTask) -> libc::time_t { +pub unsafe extern "C" fn tc_task_get_modified<'a>(task: *mut TCTask) -> libc::time_t { wrap(task, |task| to_time_t(task.get_modified())) } /// Check if a task is waiting. #[no_mangle] -pub extern "C" fn tc_task_is_waiting(task: *mut TCTask) -> bool { +pub unsafe extern "C" fn tc_task_is_waiting(task: *mut TCTask) -> bool { wrap(task, |task| task.is_waiting()) } /// Check if a task is active (started and not stopped). #[no_mangle] -pub extern "C" fn tc_task_is_active(task: *mut TCTask) -> bool { +pub unsafe extern "C" fn tc_task_is_active(task: *mut TCTask) -> bool { wrap(task, |task| task.is_active()) } /// Check if a task has the given tag. If the tag is invalid, this function will return false, as /// that (invalid) tag is not present. No error will be reported via `tc_task_error`. #[no_mangle] -pub extern "C" fn tc_task_has_tag<'a>(task: *mut TCTask, tag: *mut TCString) -> bool { +pub unsafe extern "C" fn tc_task_has_tag<'a>(task: *mut TCTask, tag: *mut TCString) -> bool { // SAFETY: see TCString docstring let tcstring = unsafe { TCString::take_from_arg(tag) }; wrap(task, |task| { @@ -319,7 +319,7 @@ pub extern "C" fn tc_task_has_tag<'a>(task: *mut TCTask, tag: *mut TCString) -> /// The caller must free the returned TCStrings instance. The TCStrings instance does not /// reference the task and the two may be freed in any order. #[no_mangle] -pub extern "C" fn tc_task_get_tags<'a>(task: *mut TCTask) -> TCStrings { +pub unsafe extern "C" fn tc_task_get_tags<'a>(task: *mut TCTask) -> TCStrings { wrap(task, |task| { let vec: Vec>> = task .get_tags() @@ -343,7 +343,7 @@ pub extern "C" fn tc_task_get_tags<'a>(task: *mut TCTask) -> TCStrings { /// Set a mutable task's status. #[no_mangle] -pub extern "C" fn tc_task_set_status<'a>(task: *mut TCTask, status: TCStatus) -> TCResult { +pub unsafe extern "C" fn tc_task_set_status<'a>(task: *mut TCTask, status: TCStatus) -> TCResult { wrap_mut( task, |task| { @@ -356,7 +356,7 @@ pub extern "C" fn tc_task_set_status<'a>(task: *mut TCTask, status: TCStatus) -> /// Set a mutable task's description. #[no_mangle] -pub extern "C" fn tc_task_set_description<'a>( +pub unsafe extern "C" fn tc_task_set_description<'a>( task: *mut TCTask, description: *mut TCString, ) -> TCResult { @@ -375,7 +375,7 @@ pub extern "C" fn tc_task_set_description<'a>( /// Set a mutable task's entry (creation time). Pass entry=0 to unset /// the entry field. #[no_mangle] -pub extern "C" fn tc_task_set_entry(task: *mut TCTask, entry: libc::time_t) -> TCResult { +pub unsafe extern "C" fn tc_task_set_entry(task: *mut TCTask, entry: libc::time_t) -> TCResult { wrap_mut( task, |task| { @@ -388,7 +388,7 @@ pub extern "C" fn tc_task_set_entry(task: *mut TCTask, entry: libc::time_t) -> T /// Set a mutable task's wait timestamp. Pass wait=0 to unset the wait field. #[no_mangle] -pub extern "C" fn tc_task_set_wait(task: *mut TCTask, wait: libc::time_t) -> TCResult { +pub unsafe extern "C" fn tc_task_set_wait(task: *mut TCTask, wait: libc::time_t) -> TCResult { wrap_mut( task, |task| { @@ -401,7 +401,7 @@ pub extern "C" fn tc_task_set_wait(task: *mut TCTask, wait: libc::time_t) -> TCR /// Set a mutable task's modified timestamp. The value cannot be zero. #[no_mangle] -pub extern "C" fn tc_task_set_modified(task: *mut TCTask, modified: libc::time_t) -> TCResult { +pub unsafe extern "C" fn tc_task_set_modified(task: *mut TCTask, modified: libc::time_t) -> TCResult { wrap_mut( task, |task| { @@ -416,7 +416,7 @@ pub extern "C" fn tc_task_set_modified(task: *mut TCTask, modified: libc::time_t /// Start a task. #[no_mangle] -pub extern "C" fn tc_task_start(task: *mut TCTask) -> TCResult { +pub unsafe extern "C" fn tc_task_start(task: *mut TCTask) -> TCResult { wrap_mut( task, |task| { @@ -429,7 +429,7 @@ pub extern "C" fn tc_task_start(task: *mut TCTask) -> TCResult { /// Stop a task. #[no_mangle] -pub extern "C" fn tc_task_stop(task: *mut TCTask) -> TCResult { +pub unsafe extern "C" fn tc_task_stop(task: *mut TCTask) -> TCResult { wrap_mut( task, |task| { @@ -442,7 +442,7 @@ pub extern "C" fn tc_task_stop(task: *mut TCTask) -> TCResult { /// Mark a task as done. #[no_mangle] -pub extern "C" fn tc_task_done(task: *mut TCTask) -> TCResult { +pub unsafe extern "C" fn tc_task_done(task: *mut TCTask) -> TCResult { wrap_mut( task, |task| { @@ -455,7 +455,7 @@ pub extern "C" fn tc_task_done(task: *mut TCTask) -> TCResult { /// Mark a task as deleted. #[no_mangle] -pub extern "C" fn tc_task_delete(task: *mut TCTask) -> TCResult { +pub unsafe extern "C" fn tc_task_delete(task: *mut TCTask) -> TCResult { wrap_mut( task, |task| { @@ -468,7 +468,7 @@ pub extern "C" fn tc_task_delete(task: *mut TCTask) -> TCResult { /// Add a tag to a mutable task. #[no_mangle] -pub extern "C" fn tc_task_add_tag(task: *mut TCTask, tag: *mut TCString) -> TCResult { +pub unsafe extern "C" fn tc_task_add_tag(task: *mut TCTask, tag: *mut TCString) -> TCResult { // SAFETY: see TCString docstring let tcstring = unsafe { TCString::take_from_arg(tag) }; wrap_mut( @@ -484,7 +484,7 @@ pub extern "C" fn tc_task_add_tag(task: *mut TCTask, tag: *mut TCString) -> TCRe /// Remove a tag from a mutable task. #[no_mangle] -pub extern "C" fn tc_task_remove_tag(task: *mut TCTask, tag: *mut TCString) -> TCResult { +pub unsafe extern "C" fn tc_task_remove_tag(task: *mut TCTask, tag: *mut TCString) -> TCResult { // SAFETY: see TCString docstring let tcstring = unsafe { TCString::take_from_arg(tag) }; wrap_mut( @@ -509,7 +509,7 @@ pub extern "C" fn tc_task_remove_tag(task: *mut TCTask, tag: *mut TCString) -> T /// to this function will return NULL. The task pointer must not be NULL. The caller must free the /// returned string. #[no_mangle] -pub extern "C" fn tc_task_error<'a>(task: *mut TCTask) -> *mut TCString<'static> { +pub unsafe extern "C" fn tc_task_error<'a>(task: *mut TCTask) -> *mut TCString<'static> { // SAFETY: // - task is not null (promised by caller) // - task outlives 'a (promised by caller) @@ -526,7 +526,7 @@ pub extern "C" fn tc_task_error<'a>(task: *mut TCTask) -> *mut TCString<'static> /// /// If the task is currently mutable, it will first be made immutable. #[no_mangle] -pub extern "C" fn tc_task_free<'a>(task: *mut TCTask) { +pub unsafe extern "C" fn tc_task_free<'a>(task: *mut TCTask) { // SAFETY: // - rep is not NULL (promised by caller) // - caller will not use the TCTask after this (promised by caller) diff --git a/lib/src/uuid.rs b/lib/src/uuid.rs index 3c7751747..cef445e40 100644 --- a/lib/src/uuid.rs +++ b/lib/src/uuid.rs @@ -26,13 +26,13 @@ impl PassByValue for TCUuid { /// Create a new, randomly-generated UUID. #[no_mangle] -pub extern "C" fn tc_uuid_new_v4() -> TCUuid { +pub unsafe extern "C" fn tc_uuid_new_v4() -> TCUuid { TCUuid::return_val(Uuid::new_v4()) } /// Create a new UUID with the nil value. #[no_mangle] -pub extern "C" fn tc_uuid_nil() -> TCUuid { +pub unsafe extern "C" fn tc_uuid_nil() -> TCUuid { TCUuid::return_val(Uuid::nil()) } @@ -43,7 +43,7 @@ pub const TC_UUID_STRING_BYTES: usize = 36; /// Write the string representation of a TCUuid into the given buffer, which must be /// at least TC_UUID_STRING_BYTES long. No NUL terminator is added. #[no_mangle] -pub extern "C" fn tc_uuid_to_buf<'a>(tcuuid: TCUuid, buf: *mut libc::c_char) { +pub unsafe extern "C" fn tc_uuid_to_buf<'a>(tcuuid: TCUuid, buf: *mut libc::c_char) { debug_assert!(!buf.is_null()); // SAFETY: // - buf is valid for len bytes (by C convention) @@ -63,7 +63,7 @@ pub extern "C" fn tc_uuid_to_buf<'a>(tcuuid: TCUuid, buf: *mut libc::c_char) { /// Write the string representation of a TCUuid into the given buffer, which must be /// at least TC_UUID_STRING_BYTES long. No NUL terminator is added. #[no_mangle] -pub extern "C" fn tc_uuid_to_str(tcuuid: TCUuid) -> *mut TCString<'static> { +pub unsafe extern "C" fn tc_uuid_to_str(tcuuid: TCUuid) -> *mut TCString<'static> { // SAFETY: // - tcuuid is a valid TCUuid (all byte patterns are valid) let uuid: Uuid = unsafe { TCUuid::from_arg(tcuuid) }; @@ -74,7 +74,7 @@ pub extern "C" fn tc_uuid_to_str(tcuuid: TCUuid) -> *mut TCString<'static> { /// Parse the given string as a UUID. Returns false on failure. #[no_mangle] -pub extern "C" fn tc_uuid_from_str<'a>(s: *mut TCString, uuid_out: *mut TCUuid) -> bool { +pub unsafe extern "C" fn tc_uuid_from_str<'a>(s: *mut TCString, uuid_out: *mut TCUuid) -> bool { // TODO: TCResult instead debug_assert!(!s.is_null()); debug_assert!(!uuid_out.is_null());