diff --git a/lib/src/annotation.rs b/lib/src/annotation.rs index dede87431..7142eff62 100644 --- a/lib/src/annotation.rs +++ b/lib/src/annotation.rs @@ -3,6 +3,20 @@ use crate::types::*; use chrono::prelude::*; /// TCAnnotation contains the details of an annotation. +/// +/// # Safety +/// +/// An annotation must be initialized from a tc_.. function, and later freed +/// with `tc_annotation_free` or `tc_annotation_list_free`. +/// +/// Any function taking a `*TCAnnotation` requires: +/// - the pointer must not be NUL; +/// - the pointer must be one previously returned from a tc_… function; +/// - the memory referenced by the pointer must never be modified by C code; and +/// - ownership transfers to the called function, and the value must not be used +/// after the call returns. In fact, the value will be zeroed out to ensure this. +/// +/// TCAnnotations are not threadsafe. #[repr(C)] pub struct TCAnnotation { /// Time the annotation was made. Must be nonzero. diff --git a/lib/src/replica.rs b/lib/src/replica.rs index 423810645..a35260b51 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -23,7 +23,7 @@ use taskchampion::{Replica, StorageConfig}; /// - the memory referenced by the pointer must never be modified by C code; and /// - except for `tc_replica_free`, ownership of a `*TCReplica` remains with the caller. /// -/// Once passed to `tc_replica_free`, a `*TCReplica` becmes invalid and must not be used again. +/// Once passed to `tc_replica_free`, a `*TCReplica` becomes invalid and must not be used again. /// /// TCReplicas are not threadsafe. pub struct TCReplica { @@ -74,7 +74,12 @@ fn wrap(rep: *mut TCReplica, f: F, err_value: T) -> T where F: FnOnce(&mut Replica) -> anyhow::Result, { - // SAFETY: see type docstring + debug_assert!(!rep.is_null()); + // SAFETY: + // - rep is not NULL (promised by caller) + // - *rep is a valid TCReplica (promised by caller) + // - rep is valid for the duration of this function + // - rep is not modified by anything else (not threadsafe) let rep: &mut TCReplica = unsafe { TCReplica::from_ptr_arg_ref_mut(rep) }; if rep.mut_borrowed { panic!("replica is borrowed and cannot be used"); @@ -90,13 +95,14 @@ where } /// Create a new TCReplica with an in-memory database. The contents of the database will be -/// lost when it is freed. +/// lost when it is freed with tc_replica_free. #[no_mangle] pub unsafe extern "C" fn tc_replica_new_in_memory() -> *mut TCReplica { let storage = StorageConfig::InMemory .into_storage() .expect("in-memory always succeeds"); - // SAFETY: see type docstring + // SAFETY: + // - caller promises to free this value unsafe { TCReplica::from(Replica::new(storage)).return_ptr() } } @@ -108,7 +114,10 @@ pub unsafe extern "C" fn tc_replica_new_on_disk( path: *mut TCString, error_out: *mut *mut TCString, ) -> *mut TCReplica { - // SAFETY: see TCString docstring + // SAFETY: + // - path is not NULL (promised by caller) + // - path is return from a tc_string_.. so is valid + // - caller will not use path after this call (convention) let path = unsafe { TCString::take_from_ptr_arg(path) }; let storage_res = StorageConfig::OnDisk { taskdb_dir: path.to_path_buf(), @@ -120,6 +129,11 @@ pub unsafe extern "C" fn tc_replica_new_on_disk( Err(e) => { if !error_out.is_null() { unsafe { + // SAFETY: + // - return_ptr: caller promises to free this string + // - *error_out: + // - error_out is not NULL (checked) + // - error_out points to a valid place for a pointer (caller promises) *error_out = err_to_tcstring(e).return_ptr(); } } @@ -127,7 +141,8 @@ pub unsafe extern "C" fn tc_replica_new_on_disk( } }; - // SAFETY: see type docstring + // SAFETY: + // - caller promises to free this value unsafe { TCReplica::from(Replica::new(storage)).return_ptr() } } @@ -147,7 +162,8 @@ pub unsafe extern "C" fn tc_replica_all_tasks(rep: *mut TCReplica) -> TCTaskList .drain() .map(|(_uuid, t)| { NonNull::new( - // SAFETY: see TCTask docstring + // SAFETY: + // - caller promises to free this value (via freeing the list) unsafe { TCTask::from(t).return_ptr() }, ) .expect("TCTask::return_ptr returned NULL") @@ -178,7 +194,8 @@ pub unsafe extern "C" fn tc_replica_all_task_uuids(rep: *mut TCReplica) -> TCUui ) } -/// Get the current working set for this replica. +/// Get the current working set for this replica. The resulting value must be freed +/// with tc_working_set_free. /// /// Returns NULL on error. #[no_mangle] @@ -187,7 +204,8 @@ pub unsafe extern "C" fn tc_replica_working_set(rep: *mut TCReplica) -> *mut TCW rep, |rep| { let ws = rep.working_set()?; - // SAFETY: caller promises to free this task + // SAFETY: + // - caller promises to free this value Ok(unsafe { TCWorkingSet::return_ptr(ws.into()) }) }, std::ptr::null_mut(), @@ -203,10 +221,13 @@ pub unsafe extern "C" fn tc_replica_get_task(rep: *mut TCReplica, tcuuid: TCUuid wrap( rep, |rep| { - // SAFETY: see TCUuid docstring + // SAFETY: + // - tcuuid is a valid TCUuid (all bytes are valid) + // - tcuuid is Copy so ownership doesn't matter let uuid = unsafe { TCUuid::val_from_arg(tcuuid) }; if let Some(task) = rep.get_task(uuid)? { - // SAFETY: caller promises to free this task + // SAFETY: + // - caller promises to free this task Ok(unsafe { TCTask::from(task).return_ptr() }) } else { Ok(std::ptr::null_mut()) @@ -225,13 +246,17 @@ pub unsafe extern "C" fn tc_replica_new_task( status: TCStatus, description: *mut TCString, ) -> *mut TCTask { - // SAFETY: see TCString docstring + // SAFETY: + // - description is not NULL (promised by caller) + // - description is return from a tc_string_.. so is valid + // - caller will not use description after this call (convention) let description = unsafe { TCString::take_from_ptr_arg(description) }; wrap( rep, |rep| { let task = rep.new_task(status.into(), description.as_str()?.to_string())?; - // SAFETY: caller promises to free this task + // SAFETY: + // - caller promises to free this task Ok(unsafe { TCTask::from(task).return_ptr() }) }, std::ptr::null_mut(), @@ -249,10 +274,13 @@ pub unsafe extern "C" fn tc_replica_import_task_with_uuid( wrap( rep, |rep| { - // SAFETY: see TCUuid docstring + // SAFETY: + // - tcuuid is a valid TCUuid (all bytes are valid) + // - tcuuid is Copy so ownership doesn't matter let uuid = unsafe { TCUuid::val_from_arg(tcuuid) }; let task = rep.import_task_with_uuid(uuid)?; - // SAFETY: caller promises to free this task + // SAFETY: + // - caller promises to free this task Ok(unsafe { TCTask::from(task).return_ptr() }) }, std::ptr::null_mut(), @@ -346,10 +374,15 @@ pub unsafe extern "C" fn tc_replica_rebuild_working_set( /// returned string. #[no_mangle] pub unsafe extern "C" fn tc_replica_error(rep: *mut TCReplica) -> *mut TCString<'static> { - // SAFETY: see type docstring + // SAFETY: + // - rep is not NULL (promised by caller) + // - *rep is a valid TCReplica (promised by caller) + // - rep is valid for the duration of this function + // - rep is not modified by anything else (not threadsafe) let rep: &mut TCReplica = unsafe { TCReplica::from_ptr_arg_ref_mut(rep) }; if let Some(tcstring) = rep.error.take() { - // SAFETY: see TCString docstring + // SAFETY: + // - caller promises to free this string unsafe { tcstring.return_ptr() } } else { std::ptr::null_mut() @@ -360,7 +393,10 @@ pub unsafe extern "C" fn tc_replica_error(rep: *mut TCReplica) -> *mut TCString< /// more than once. #[no_mangle] pub unsafe extern "C" fn tc_replica_free(rep: *mut TCReplica) { - // SAFETY: see type docstring + // SAFETY: + // - replica is not NULL (promised by caller) + // - replica is valid (promised by caller) + // - caller will not use description after this call (promised by caller) let replica = unsafe { TCReplica::take_from_ptr_arg(rep) }; if replica.mut_borrowed { panic!("replica is borrowed and cannot be freed"); diff --git a/lib/src/server.rs b/lib/src/server.rs index d50650ec9..74feff34f 100644 --- a/lib/src/server.rs +++ b/lib/src/server.rs @@ -61,7 +61,10 @@ pub unsafe extern "C" fn tc_server_new_local( ) -> *mut TCServer { wrap( || { - // SAFETY: see TCString docstring + // SAFETY: + // - server_dir is not NULL (promised by caller) + // - server_dir is return from a tc_string_.. so is valid + // - caller will not use server_dir after this call (convention) let server_dir = unsafe { TCString::take_from_ptr_arg(server_dir) }; let server_config = ServerConfig::Local { server_dir: server_dir.to_path_buf(), diff --git a/lib/src/string.rs b/lib/src/string.rs index f21f05c95..3e0d4069e 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -23,7 +23,7 @@ use std::str::Utf8Error; /// # Safety /// /// When a `*TCString` appears as a return value or output argument, ownership is passed to the -/// caller. The caller must pass that ownerhsip back to another function or free the string. +/// caller. The caller must pass that ownership back to another function or free the string. /// /// Any function taking a `*TCReplica` requires: /// - the pointer must not be NUL; @@ -32,7 +32,7 @@ use std::str::Utf8Error; /// /// Unless specified otherwise, TaskChampion functions take ownership of a `*TCString` when it is /// given as a function argument, and the pointer is invalid when the function returns. Callers -/// must not use or free TCStringList after passing them to such API functions. +/// must not use or free TCStrings after passing them to such API functions. /// /// TCString is not threadsafe. #[derive(PartialEq, Debug)] @@ -193,7 +193,8 @@ pub unsafe extern "C" fn tc_string_borrow(cstr: *const libc::c_char) -> *mut TCS // - cstr contains a valid NUL terminator (promised by caller) // - cstr's content will not change before it is destroyed (promised by caller) let cstr: &CStr = unsafe { CStr::from_ptr(cstr) }; - // SAFETY: see docstring + // SAFETY: + // - caller promises to free this string unsafe { TCString::CStr(cstr).return_ptr() } } @@ -208,7 +209,8 @@ pub unsafe extern "C" fn tc_string_clone(cstr: *const libc::c_char) -> *mut TCSt // - cstr contains a valid NUL terminator (promised by caller) // - cstr's content will not change before it is destroyed (by C convention) let cstr: &CStr = unsafe { CStr::from_ptr(cstr) }; - // SAFETY: see docstring + // SAFETY: + // - caller promises to free this string unsafe { TCString::CString(cstr.into()).return_ptr() } } @@ -245,7 +247,8 @@ pub unsafe extern "C" fn tc_string_clone_with_len( } }; - // SAFETY: see docstring + // SAFETY: + // - caller promises to free this string unsafe { tcstring.return_ptr() } } diff --git a/lib/src/task.rs b/lib/src/task.rs index 47dbbf3d1..ccea9fa20 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -23,6 +23,19 @@ use taskchampion::{Annotation, Tag, Task, TaskMut}; /// When a `tc_task_..` function that returns a TCResult returns TC_RESULT_ERROR, then /// `tc_task_error` will return the error message. /// +/// # Safety +/// +/// A task is an owned object, and must be freed with tc_task_free (or, if part of a list, +/// with tc_task_list_free). +/// +/// Any function taking a `*TCTask` requires: +/// - the pointer must not be NUL; +/// - the pointer must be one previously returned from a tc_… function; +/// - the memory referenced by the pointer must never be modified by C code; and +/// - except for `tc_{task,task_list}_free`, ownership of a `*TCTask` remains with the caller. +/// +/// Once passed to tc_task_free, a `*TCTask` becomes invalid and must not be used again. +/// /// TCTasks are not threadsafe. pub struct TCTask { /// The wrapped Task or TaskMut @@ -269,7 +282,8 @@ pub unsafe extern "C" fn tc_task_get_taskmap(task: *mut TCTask) -> TCKVList { pub unsafe extern "C" fn tc_task_get_description(task: *mut TCTask) -> *mut TCString<'static> { wrap(task, |task| { let descr: TCString = task.get_description().into(); - // SAFETY: see TCString docstring + // SAFETY: + // - caller promises to free this string unsafe { descr.return_ptr() } }) } @@ -308,7 +322,10 @@ pub unsafe extern "C" fn tc_task_is_active(task: *mut TCTask) -> bool { /// that (invalid) tag is not present. No error will be reported via `tc_task_error`. #[no_mangle] pub unsafe extern "C" fn tc_task_has_tag(task: *mut TCTask, tag: *mut TCString) -> bool { - // SAFETY: see TCString docstring + // SAFETY: + // - tag is not NULL (promised by caller) + // - tag is return from a tc_string_.. so is valid + // - caller will not use tag after this call (convention) let tcstring = unsafe { TCString::take_from_ptr_arg(tag) }; wrap(task, |task| { if let Ok(tag) = Tag::try_from(tcstring) { @@ -330,7 +347,8 @@ pub unsafe extern "C" fn tc_task_get_tags(task: *mut TCTask) -> TCStringList { .get_tags() .map(|t| { NonNull::new( - // SAFETY: see TCString docstring + // SAFETY: + // - this TCString will be freed via tc_string_list_free. unsafe { TCString::from(t.as_ref()).return_ptr() }, ) .expect("TCString::return_ptr() returned NULL") @@ -368,7 +386,12 @@ pub unsafe extern "C" fn tc_task_get_uda<'a>( key: *mut TCString<'a>, ) -> *mut TCString<'static> { wrap(task, |task| { + // SAFETY: + // - ns is not NULL (promised by caller) + // - ns is return from a tc_string_.. so is valid + // - caller will not use ns after this call (convention) if let Ok(ns) = unsafe { TCString::take_from_ptr_arg(ns) }.as_str() { + // SAFETY: same if let Ok(key) = unsafe { TCString::take_from_ptr_arg(key) }.as_str() { if let Some(value) = task.get_uda(ns, key) { // SAFETY: @@ -390,6 +413,10 @@ pub unsafe extern "C" fn tc_task_get_legacy_uda<'a>( key: *mut TCString<'a>, ) -> *mut TCString<'static> { wrap(task, |task| { + // SAFETY: + // - key is not NULL (promised by caller) + // - key is return from a tc_string_.. so is valid + // - caller will not use key after this call (convention) if let Ok(key) = unsafe { TCString::take_from_ptr_arg(key) }.as_str() { if let Some(value) = task.get_legacy_uda(key) { // SAFETY: @@ -461,7 +488,10 @@ pub unsafe extern "C" fn tc_task_set_description( task: *mut TCTask, description: *mut TCString, ) -> TCResult { - // SAFETY: see TCString docstring + // SAFETY: + // - description is not NULL (promised by caller) + // - description is return from a tc_string_.. so is valid + // - caller will not use description after this call (convention) let description = unsafe { TCString::take_from_ptr_arg(description) }; wrap_mut( task, @@ -577,7 +607,10 @@ pub unsafe extern "C" fn tc_task_delete(task: *mut TCTask) -> TCResult { /// Add a tag to a mutable task. #[no_mangle] pub unsafe extern "C" fn tc_task_add_tag(task: *mut TCTask, tag: *mut TCString) -> TCResult { - // SAFETY: see TCString docstring + // SAFETY: + // - tag is not NULL (promised by caller) + // - tag is return from a tc_string_.. so is valid + // - caller will not use tag after this call (convention) let tcstring = unsafe { TCString::take_from_ptr_arg(tag) }; wrap_mut( task, @@ -593,7 +626,10 @@ pub unsafe extern "C" fn tc_task_add_tag(task: *mut TCTask, tag: *mut TCString) /// Remove a tag from a mutable task. #[no_mangle] pub unsafe extern "C" fn tc_task_remove_tag(task: *mut TCTask, tag: *mut TCString) -> TCResult { - // SAFETY: see TCString docstring + // SAFETY: + // - tag is not NULL (promised by caller) + // - tag is return from a tc_string_.. so is valid + // - caller will not use tag after this call (convention) let tcstring = unsafe { TCString::take_from_ptr_arg(tag) }; wrap_mut( task, @@ -606,13 +642,17 @@ pub unsafe extern "C" fn tc_task_remove_tag(task: *mut TCTask, tag: *mut TCStrin ) } -/// Add an annotation to a mutable task. +/// Add an annotation to a mutable task. This call takes ownership of the +/// passed annotation, which must not be used after the call returns. #[no_mangle] pub unsafe extern "C" fn tc_task_add_annotation( task: *mut TCTask, annotation: *mut TCAnnotation, ) -> TCResult { - // SAFETY: see TCAnnotation docstring + // SAFETY: + // - annotation is not NULL (promised by caller) + // - annotation is return from a tc_string_.. so is valid + // - caller will not use annotation after this call let (entry, description) = unsafe { TCAnnotation::take_val_from_arg(annotation, TCAnnotation::default()) }; wrap_mut( @@ -647,11 +687,14 @@ pub unsafe extern "C" fn tc_task_set_uda( key: *mut TCString, value: *mut TCString, ) -> TCResult { - // SAFETY: see TCString docstring + // safety: + // - ns is not null (promised by caller) + // - ns is return from a tc_string_.. so is valid + // - caller will not use ns after this call (convention) let ns = unsafe { TCString::take_from_ptr_arg(ns) }; - // SAFETY: see TCString docstring + // SAFETY: same let key = unsafe { TCString::take_from_ptr_arg(key) }; - // SAFETY: see TCString docstring + // SAFETY: same let value = unsafe { TCString::take_from_ptr_arg(value) }; wrap_mut( task, @@ -674,9 +717,12 @@ pub unsafe extern "C" fn tc_task_remove_uda( ns: *mut TCString, key: *mut TCString, ) -> TCResult { - // SAFETY: see TCString docstring + // safety: + // - ns is not null (promised by caller) + // - ns is return from a tc_string_.. so is valid + // - caller will not use ns after this call (convention) let ns = unsafe { TCString::take_from_ptr_arg(ns) }; - // SAFETY: see TCString docstring + // SAFETY: same let key = unsafe { TCString::take_from_ptr_arg(key) }; wrap_mut( task, @@ -695,9 +741,12 @@ pub unsafe extern "C" fn tc_task_set_legacy_uda( key: *mut TCString, value: *mut TCString, ) -> TCResult { - // SAFETY: see TCString docstring + // safety: + // - key is not null (promised by caller) + // - key is return from a tc_string_.. so is valid + // - caller will not use key after this call (convention) let key = unsafe { TCString::take_from_ptr_arg(key) }; - // SAFETY: see TCString docstring + // SAFETY: same let value = unsafe { TCString::take_from_ptr_arg(value) }; wrap_mut( task, @@ -715,7 +764,10 @@ pub unsafe extern "C" fn tc_task_remove_legacy_uda( task: *mut TCTask, key: *mut TCString, ) -> TCResult { - // SAFETY: see TCString docstring + // safety: + // - key is not null (promised by caller) + // - key is return from a tc_string_.. so is valid + // - caller will not use key after this call (convention) let key = unsafe { TCString::take_from_ptr_arg(key) }; wrap_mut( task, @@ -737,6 +789,8 @@ pub unsafe extern "C" fn tc_task_error(task: *mut TCTask) -> *mut TCString<'stat // - task outlives 'a (promised by caller) let task: &mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) }; if let Some(tcstring) = task.error.take() { + // SAFETY: + // - caller promises to free this value unsafe { tcstring.return_ptr() } } else { std::ptr::null_mut() @@ -750,7 +804,7 @@ pub unsafe extern "C" fn tc_task_error(task: *mut TCTask) -> *mut TCString<'stat #[no_mangle] pub unsafe extern "C" fn tc_task_free(task: *mut TCTask) { // SAFETY: - // - rep is not NULL (promised by caller) + // - task is not NULL (promised by caller) // - caller will not use the TCTask after this (promised by caller) let mut tctask = unsafe { TCTask::take_from_ptr_arg(task) }; diff --git a/lib/src/uuid.rs b/lib/src/uuid.rs index 9496e8b0b..1bc300cf1 100644 --- a/lib/src/uuid.rs +++ b/lib/src/uuid.rs @@ -92,15 +92,16 @@ pub unsafe extern "C" fn tc_uuid_to_buf(tcuuid: TCUuid, buf: *mut libc::c_char) uuid.to_hyphenated().encode_lower(buf); } -/// 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. +/// Return the hyphenated string representation of a TCUuid. The returned string +/// must be freed with tc_string_free. #[no_mangle] 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::val_from_arg(tcuuid) }; let s = uuid.to_string(); - // SAFETY: see TCString docstring + // SAFETY: + // - caller promises to free this value. unsafe { TCString::from(s).return_ptr() } } @@ -110,7 +111,10 @@ pub unsafe extern "C" fn tc_uuid_to_str(tcuuid: TCUuid) -> *mut TCString<'static pub unsafe extern "C" fn tc_uuid_from_str(s: *mut TCString, uuid_out: *mut TCUuid) -> TCResult { debug_assert!(!s.is_null()); debug_assert!(!uuid_out.is_null()); - // SAFETY: see TCString docstring + // SAFETY: + // - s is not NULL (promised by caller) + // - s is return from a tc_string_.. so is valid + // - caller will not use s after this call (convention) let s = unsafe { TCString::take_from_ptr_arg(s) }; if let Ok(s) = s.as_str() { if let Ok(u) = Uuid::parse_str(s) { diff --git a/lib/taskchampion.h b/lib/taskchampion.h index 41edeedff..0fd61ad9a 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -57,7 +57,7 @@ typedef enum TCStatus { * - the memory referenced by the pointer must never be modified by C code; and * - except for `tc_replica_free`, ownership of a `*TCReplica` remains with the caller. * - * Once passed to `tc_replica_free`, a `*TCReplica` becmes invalid and must not be used again. + * Once passed to `tc_replica_free`, a `*TCReplica` becomes invalid and must not be used again. * * TCReplicas are not threadsafe. */ @@ -92,7 +92,7 @@ typedef struct TCServer TCServer; * # Safety * * When a `*TCString` appears as a return value or output argument, ownership is passed to the - * caller. The caller must pass that ownerhsip back to another function or free the string. + * caller. The caller must pass that ownership back to another function or free the string. * * Any function taking a `*TCReplica` requires: * - the pointer must not be NUL; @@ -101,7 +101,7 @@ typedef struct TCServer TCServer; * * Unless specified otherwise, TaskChampion functions take ownership of a `*TCString` when it is * given as a function argument, and the pointer is invalid when the function returns. Callers - * must not use or free TCStringList after passing them to such API functions. + * must not use or free TCStrings after passing them to such API functions. * * TCString is not threadsafe. */ @@ -123,6 +123,19 @@ typedef struct TCString TCString; * When a `tc_task_..` function that returns a TCResult returns TC_RESULT_ERROR, then * `tc_task_error` will return the error message. * + * # Safety + * + * A task is an owned object, and must be freed with tc_task_free (or, if part of a list, + * with tc_task_list_free). + * + * Any function taking a `*TCTask` requires: + * - the pointer must not be NUL; + * - the pointer must be one previously returned from a tc_… function; + * - the memory referenced by the pointer must never be modified by C code; and + * - except for `tc_{task,task_list}_free`, ownership of a `*TCTask` remains with the caller. + * + * Once passed to tc_task_free, a `*TCTask` becomes invalid and must not be used again. + * * TCTasks are not threadsafe. */ typedef struct TCTask TCTask; @@ -138,6 +151,20 @@ typedef struct TCWorkingSet TCWorkingSet; /** * TCAnnotation contains the details of an annotation. + * + * # Safety + * + * An annotation must be initialized from a tc_.. function, and later freed + * with `tc_annotation_free` or `tc_annotation_list_free`. + * + * Any function taking a `*TCAnnotation` requires: + * - the pointer must not be NUL; + * - the pointer must be one previously returned from a tc_… function; + * - the memory referenced by the pointer must never be modified by C code; and + * - ownership transfers to the called function, and the value must not be used + * after the call returns. In fact, the value will be zeroed out to ensure this. + * + * TCAnnotations are not threadsafe. */ typedef struct TCAnnotation { /** @@ -344,7 +371,7 @@ void tc_kv_list_free(struct TCKVList *tckvs); /** * Create a new TCReplica with an in-memory database. The contents of the database will be - * lost when it is freed. + * lost when it is freed with tc_replica_free. */ struct TCReplica *tc_replica_new_in_memory(void); @@ -370,7 +397,8 @@ struct TCTaskList tc_replica_all_tasks(struct TCReplica *rep); struct TCUuidList tc_replica_all_task_uuids(struct TCReplica *rep); /** - * Get the current working set for this replica. + * Get the current working set for this replica. The resulting value must be freed + * with tc_working_set_free. * * Returns NULL on error. */ @@ -728,7 +756,8 @@ TCResult tc_task_add_tag(struct TCTask *task, struct TCString *tag); TCResult tc_task_remove_tag(struct TCTask *task, struct TCString *tag); /** - * Add an annotation to a mutable task. + * Add an annotation to a mutable task. This call takes ownership of the + * passed annotation, which must not be used after the call returns. */ TCResult tc_task_add_annotation(struct TCTask *task, struct TCAnnotation *annotation); @@ -814,8 +843,8 @@ struct TCUuid tc_uuid_nil(void); void tc_uuid_to_buf(struct TCUuid tcuuid, char *buf); /** - * 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. + * Return the hyphenated string representation of a TCUuid. The returned string + * must be freed with tc_string_free. */ struct TCString *tc_uuid_to_str(struct TCUuid tcuuid);