diff --git a/Cargo.lock b/Cargo.lock index 3bdb7dc0f..256a68b3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3037,7 +3037,9 @@ dependencies = [ "anyhow", "chrono", "libc", + "pretty_assertions", "taskchampion", + "uuid", ] [[package]] diff --git a/lib/header-intro.h b/lib/header-intro.h new file mode 100644 index 000000000..c0dd01153 --- /dev/null +++ b/lib/header-intro.h @@ -0,0 +1,76 @@ +/** + * TaskChampion + * + * This file defines the C interface to libtaskchampion. This is a thin + * wrapper around the Rust `taskchampion` crate. Refer to the documentation + * for that crate at https://docs.rs/taskchampion/latest/taskchampion/ for API + * details. The comments in this file focus mostly on the low-level details of + * passing values to and from TaskChampion. + * + * # Overview + * + * This library defines two major types used to interact with the API, that map directly + * to Rust types. + * + * * TCReplica - see https://docs.rs/taskchampion/latest/taskchampion/struct.Replica.html + * * TCTask - see https://docs.rs/taskchampion/latest/taskchampion/struct.Task.html + * * TCServer - see https://docs.rs/taskchampion/latest/taskchampion/trait.Server.html + * * TCWorkingSet - see https://docs.rs/taskchampion/latest/taskchampion/struct.WorkingSet.html + * + * It also defines a few utility types: + * + * * TCString - a wrapper around both C (NUL-terminated) and Rust (always utf-8) strings. + * * TC…List - a list of objects represented as a C array + * * see below for the remainder + * + * # Safety + * + * Each type contains specific instructions to ensure memory safety. + * The general rules are as follows. + * + * No types in this library are threadsafe. All values should be used in only + * one thread for their entire lifetime. It is safe to use unrelated values in + * different threads (for example, different threads may use different + * TCReplica values concurrently). + * + * ## Pass by Pointer + * + * Several types such as TCReplica and TCString are "opaque" types and always + * handled as pointers in C. The bytes these pointers address are private to + * the Rust implemetation and must not be accessed from C. + * + * Pass-by-pointer values have exactly one owner, and that owner is responsible + * for freeing the value (using a `tc_…_free` function), or transferring + * ownership elsewhere. Except where documented otherwise, when a value is + * passed to C, ownership passes to C as well. When a value is passed to Rust, + * ownership stays with the C code. The exception is TCString, ownership of + * which passes to Rust when it is used as a function argument. + * + * The limited circumstances where one value must not outlive another, due to + * pointer references between them, are documented below. + * + * ## Pass by Value + * + * Types such as TCUuid and TC…List are passed by value, and contain fields + * that are accessible from C. C code is free to access the content of these + * types in a _read_only_ fashion. + * + * Pass-by-value values that contain pointers also have exactly one owner, + * responsible for freeing the value or transferring ownership. The tc_…_free + * functions for these types will replace the pointers with NULL to guard + * against use-after-free errors. The interior pointers in such values should + * never be freed directly (for example, `tc_string_free(tcuda.value)` is an + * error). + * + * TCUuid is a special case, because it does not contain pointers. It can be + * freely copied and need not be freed. + * + * ## Lists + * + * Lists are a special kind of pass-by-value type. Each contains `len` and + * `items`, where `items` is an array of length `len`. Lists, and the values + * in the `items` array, must be treated as read-only. On return from an API + * function, a list's ownership is with the C caller, which must eventually + * free the list. List data must be freed with the `tc_…_list_free` function. + * It is an error to free any value in the `items` array of a list. + */ diff --git a/lib/src/string.rs b/lib/src/string.rs index 3e0d4069e..70e4795ee 100644 --- a/lib/src/string.rs +++ b/lib/src/string.rs @@ -25,7 +25,7 @@ use std::str::Utf8Error; /// When a `*TCString` appears as a return value or output argument, ownership is passed to the /// caller. The caller must pass that ownership back to another function or free the string. /// -/// Any function taking a `*TCReplica` requires: +/// Any function taking a `*TCString` requires: /// - the pointer must not be NUL; /// - the pointer must be one previously returned from a tc_… function; and /// - the memory referenced by the pointer must never be modified by C code. diff --git a/lib/src/workingset.rs b/lib/src/workingset.rs index 6e1e15b0f..672194886 100644 --- a/lib/src/workingset.rs +++ b/lib/src/workingset.rs @@ -7,6 +7,22 @@ use taskchampion::{Uuid, WorkingSet}; /// be freed at any time. /// /// To iterate over a working set, search indexes 1 through largest_index. +/// +/// # Safety +/// +/// The `*TCWorkingSet` returned from `tc_replica_working_set` is owned by the caller and +/// must later be freed to avoid a memory leak. Its lifetime is independent of the replica +/// from which it was generated. +/// +/// Any function taking a `*TCWorkingSet` requires: +/// - the pointer must not be NUL; +/// - the pointer must be one previously returned from `tc_replica_working_set` +/// - the memory referenced by the pointer must never be accessed by C code; and +/// - except for `tc_replica_free`, ownership of a `*TCWorkingSet` remains with the caller. +/// +/// Once passed to `tc_replica_free`, a `*TCWorkingSet` becomes invalid and must not be used again. +/// +/// TCWorkingSet is not threadsafe. pub struct TCWorkingSet(WorkingSet); impl PassByPointer for TCWorkingSet {} diff --git a/lib/taskchampion.h b/lib/taskchampion.h index 0fd61ad9a..702e24446 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -1,3 +1,81 @@ +/** + * TaskChampion + * + * This file defines the C interface to libtaskchampion. This is a thin + * wrapper around the Rust `taskchampion` crate. Refer to the documentation + * for that crate at https://docs.rs/taskchampion/latest/taskchampion/ for API + * details. The comments in this file focus mostly on the low-level details of + * passing values to and from TaskChampion. + * + * # Overview + * + * This library defines two major types used to interact with the API, that map directly + * to Rust types. + * + * * TCReplica - see https://docs.rs/taskchampion/latest/taskchampion/struct.Replica.html + * * TCTask - see https://docs.rs/taskchampion/latest/taskchampion/struct.Task.html + * * TCServer - see https://docs.rs/taskchampion/latest/taskchampion/trait.Server.html + * * TCWorkingSet - see https://docs.rs/taskchampion/latest/taskchampion/struct.WorkingSet.html + * + * It also defines a few utility types: + * + * * TCString - a wrapper around both C (NUL-terminated) and Rust (always utf-8) strings. + * * TC…List - a list of objects represented as a C array + * * see below for the remainder + * + * # Safety + * + * Each type contains specific instructions to ensure memory safety. + * The general rules are as follows. + * + * No types in this library are threadsafe. All values should be used in only + * one thread for their entire lifetime. It is safe to use unrelated values in + * different threads (for example, different threads may use different + * TCReplica values concurrently). + * + * ## Pass by Pointer + * + * Several types such as TCReplica and TCString are "opaque" types and always + * handled as pointers in C. The bytes these pointers address are private to + * the Rust implemetation and must not be accessed from C. + * + * Pass-by-pointer values have exactly one owner, and that owner is responsible + * for freeing the value (using a `tc_…_free` function), or transferring + * ownership elsewhere. Except where documented otherwise, when a value is + * passed to C, ownership passes to C as well. When a value is passed to Rust, + * ownership stays with the C code. The exception is TCString, ownership of + * which passes to Rust when it is used as a function argument. + * + * The limited circumstances where one value must not outlive another, due to + * pointer references between them, are documented below. + * + * ## Pass by Value + * + * Types such as TCUuid and TC…List are passed by value, and contain fields + * that are accessible from C. C code is free to access the content of these + * types in a _read_only_ fashion. + * + * Pass-by-value values that contain pointers also have exactly one owner, + * responsible for freeing the value or transferring ownership. The tc_…_free + * functions for these types will replace the pointers with NULL to guard + * against use-after-free errors. The interior pointers in such values should + * never be freed directly (for example, `tc_string_free(tcuda.value)` is an + * error). + * + * TCUuid is a special case, because it does not contain pointers. It can be + * freely copied and need not be freed. + * + * ## Lists + * + * Lists are a special kind of pass-by-value type. Each contains `len` and + * `items`, where `items` is an array of length `len`. Lists, and the values + * in the `items` array, must be treated as read-only. On return from an API + * function, a list's ownership is with the C caller, which must eventually + * free the list. List data must be freed with the `tc_…_list_free` function. + * It is an error to free any value in the `items` array of a list. + */ + + #include #include #include @@ -94,7 +172,7 @@ typedef struct TCServer TCServer; * When a `*TCString` appears as a return value or output argument, ownership is passed to the * caller. The caller must pass that ownership back to another function or free the string. * - * Any function taking a `*TCReplica` requires: + * Any function taking a `*TCString` requires: * - the pointer must not be NUL; * - the pointer must be one previously returned from a tc_… function; and * - the memory referenced by the pointer must never be modified by C code. @@ -146,6 +224,22 @@ typedef struct TCTask TCTask; * be freed at any time. * * To iterate over a working set, search indexes 1 through largest_index. + * + * # Safety + * + * The `*TCWorkingSet` returned from `tc_replica_working_set` is owned by the caller and + * must later be freed to avoid a memory leak. Its lifetime is independent of the replica + * from which it was generated. + * + * Any function taking a `*TCWorkingSet` requires: + * - the pointer must not be NUL; + * - the pointer must be one previously returned from `tc_replica_working_set` + * - the memory referenced by the pointer must never be accessed by C code; and + * - except for `tc_replica_free`, ownership of a `*TCWorkingSet` remains with the caller. + * + * Once passed to `tc_replica_free`, a `*TCWorkingSet` becomes invalid and must not be used again. + * + * TCWorkingSet is not threadsafe. */ typedef struct TCWorkingSet TCWorkingSet; diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 036802e9a..3990a4f39 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -27,6 +27,7 @@ fn codegen() -> anyhow::Result<()> { Builder::new() .with_crate(&lib_crate_dir) .with_config(Config { + header: Some(include_str!("../../lib/header-intro.h").into()), language: Language::C, cpp_compat: true, sys_includes: vec!["stdbool.h".into(), "stdint.h".into(), "time.h".into()],