diff --git a/common/devdoc/unique_thandle_requirements.md b/common/devdoc/unique_thandle_requirements.md new file mode 100644 index 000000000..94f6deff5 --- /dev/null +++ b/common/devdoc/unique_thandle_requirements.md @@ -0,0 +1,302 @@ +# unique_thandle_requirements + +## Overview + +`unique_thandle` is a module that provides macros for handles that have a single owner. + +`UNIQUE_THANDLE(T)` is a handle to an object of type `T` that has exactly one owner at a time. A handle either holds an object or is `NULL`. + +The .c file that defines `T` creates the object with `UNIQUE_THANDLE_MALLOC` or `UNIQUE_THANDLE_MALLOC_FLEX` and puts it in a handle with `UNIQUE_THANDLE_ADOPT`: + +```c +/*item.h*/ +typedef struct ITEM_TAG ITEM; + +UNIQUE_THANDLE_TYPE_DECLARE(ITEM); + +int item_create(int value, UNIQUE_THANDLE(ITEM) item); + +/*item.c*/ +struct ITEM_TAG +{ + int value; +}; + +UNIQUE_THANDLE_TYPE_DEFINE(ITEM); + +int item_create(int value, UNIQUE_THANDLE(ITEM) item) +{ + int result; + ITEM* new_item = UNIQUE_THANDLE_MALLOC(ITEM)(NULL); + if (new_item == NULL) + { + result = MU_FAILURE; + } + else + { + new_item->value = value; + UNIQUE_THANDLE_ADOPT(ITEM)(item, new_item); + result = 0; + } + return result; +} +``` + +The object can be moved with `UNIQUE_THANDLE_INITIALIZE_MOVE` or disposed with `UNIQUE_THANDLE_DISPOSE`. + +```c +/*list.h*/ +typedef struct NODE_TAG +{ + struct NODE_TAG* next; + UNIQUE_THANDLE(ITEM) item; +} NODE; + +typedef struct LIST_TAG +{ + NODE head; /*holds no item*/ + NODE* end; /*the last node, &head when the list is empty*/ +} LIST; + +typedef LIST* LIST_HANDLE; + +int list_insert_num(LIST_HANDLE list, int n); +int list_insert_item(LIST_HANDLE list, UNIQUE_THANDLE(ITEM) item); + +/*list.c*/ +int list_insert_num(LIST_HANDLE list, int n) +{ + int result; + + UNIQUE_THANDLE(ITEM) item = UNIQUE_THANDLE_NULL; + if (item_create(n, item) != 0) + { + result = MU_FAILURE; + } + else + { + if (list_insert_item(list, item) != 0) + { + result = MU_FAILURE; + } + else + { + result = 0; + goto all_ok; + } + UNIQUE_THANDLE_DISPOSE(ITEM)(item); + } +all_ok: + return result; +} + +int list_insert_item(LIST_HANDLE list, UNIQUE_THANDLE(ITEM) item) +{ + int result; + if (list == NULL || item == NULL) + { + result = MU_FAILURE; + } + else + { + NODE* node = malloc(sizeof(NODE)); + if (node == NULL) + { + result = MU_FAILURE; + } + else + { + node->next = NULL; + UNIQUE_THANDLE_INITIALIZE_MOVE(ITEM)(node->item, item); + list->end->next = node; + list->end = node; + result = 0; + } + } + return result; +} +``` + +The compiler rejects the following misuses: + +```c +/*other.h*/ +typedef struct OTHER_TAG OTHER; + +UNIQUE_THANDLE_TYPE_DECLARE(OTHER); + +/*item.c*/ +void misuses(UNIQUE_THANDLE(ITEM) item, UNIQUE_THANDLE(OTHER) other) +{ + UNIQUE_THANDLE(ITEM) temp = UNIQUE_THANDLE_NULL; + ITEM* pointer = UNIQUE_THANDLE_GET_T(ITEM)(item); + + /*copies a handle: copy and item would both own the item*/ + UNIQUE_THANDLE(ITEM) copy = item; + + /*assigns a handle: temp and item would both own the item*/ + temp = item; + + /*writes the pointer in a handle: temp and item would both own the item*/ + temp[0].object = item[0].object; + + /*warnings as errors: makes a handle from a pointer to an item, which a handle may already own*/ + UNIQUE_THANDLE_INITIALIZE_MOVE(ITEM)(temp, &pointer); + + /*warnings as errors: moves a handle to another type, OTHER, into a handle to an ITEM*/ + UNIQUE_THANDLE_INITIALIZE_MOVE(ITEM)(temp, other); +} + +/*list.h*/ +/*returns a handle: the caller and the list would both own the item*/ +UNIQUE_THANDLE(ITEM) list_get(LIST_HANDLE list, uint32_t index); + +/*list.c*/ +/*node_get_value reads a node*/ +int node_get_value(const NODE* node) +{ + UNIQUE_THANDLE(ITEM) temp = UNIQUE_THANDLE_NULL; + + /*warnings as errors: moves the item, which the node owns, into temp*/ + UNIQUE_THANDLE_INITIALIZE_MOVE(ITEM)(temp, node->item); + ... +} +``` + +Read-only copies should be passed as `const`: + +```c +/*item_get_value uses the item during the call*/ +int item_get_value(const UNIQUE_THANDLE(ITEM) item) +{ + UNIQUE_THANDLE(ITEM) temp = UNIQUE_THANDLE_NULL; + + UNIQUE_THANDLE_INITIALIZE_MOVE(ITEM)(temp, item); /*warnings as errors: does not compile*/ + + UNIQUE_THANDLE_DISPOSE(ITEM)(item); /*warnings as errors: does not compile*/ + + ITEM* item_ptr = UNIQUE_THANDLE_GET_T(ITEM)(item); + return item_ptr->value; +} +``` + +If a read-only copy is passed as non-const and the copy is modified, the original is also changed: + +```c +int item_get_value(UNIQUE_THANDLE(ITEM) item) +{ + UNIQUE_THANDLE(ITEM) temp = UNIQUE_THANDLE_NULL; + + UNIQUE_THANDLE_INITIALIZE_MOVE(ITEM)(temp, item); + + ITEM* item_ptr = UNIQUE_THANDLE_GET_T(ITEM)(temp); + + return item_ptr->value; +} + +int caller(void) +{ + UNIQUE_THANDLE(ITEM) item = UNIQUE_THANDLE_NULL; + item_create(42, item); + + int val = item_get_value(item); + + /*item is now NULL: item_get_value moved the item into temp, which leaked it. Another call to item_get_value dereferences NULL and crashes*/ + int val2 = item_get_value(item); // will crash + + return val + val2; +} +``` + +Only one thread at a time may move or dispose a given `UNIQUE_THANDLE(T)` variable. + +## Exposed API + +```c +/*the type of a handle to a T*/ +#define UNIQUE_THANDLE(T) + +/*initializes a handle to NULL, as in UNIQUE_THANDLE(T) handle = UNIQUE_THANDLE_NULL;*/ +#define UNIQUE_THANDLE_NULL + +/*to be used in a header file*/ +#define UNIQUE_THANDLE_TYPE_DECLARE(T) + +/*to be used in a .c file*/ +#define UNIQUE_THANDLE_TYPE_DEFINE(T) +#define UNIQUE_THANDLE_TYPE_DEFINE_WITH_MALLOC_FUNCTIONS(T, malloc_function, malloc_flex_function, free_function) + +/*declared by UNIQUE_THANDLE_TYPE_DECLARE*/ +MOCKABLE_FUNCTION(, void, UNIQUE_THANDLE_INITIALIZE_MOVE(T), UNIQUE_THANDLE(T), t1, UNIQUE_THANDLE(T), t2); +MOCKABLE_FUNCTION(, void, UNIQUE_THANDLE_DISPOSE(T), UNIQUE_THANDLE(T), t); + +/*defined by UNIQUE_THANDLE_TYPE_DEFINE for use in the .c file that defines T*/ +/*allocates a T. dispose, if it is not NULL, is called when the object is disposed*/ +static T* UNIQUE_THANDLE_MALLOC(T)(void(*dispose)(T*)); +/*allocates a T with an additional nmemb * size bytes for its flexible array member*/ +static T* UNIQUE_THANDLE_MALLOC_FLEX(T)(void(*dispose)(T*), size_t nmemb, size_t size); +/*frees the memory of a T allocated by UNIQUE_THANDLE_MALLOC or UNIQUE_THANDLE_MALLOC_FLEX, for example when creating the object fails*/ +static void UNIQUE_THANDLE_FREE(T)(T* t); +/*puts a new object, which UNIQUE_THANDLE_MALLOC or UNIQUE_THANDLE_MALLOC_FLEX returned, in *t, which is NULL or not initialized*/ +static void UNIQUE_THANDLE_ADOPT(T)(UNIQUE_THANDLE(T) t, T* object); +/*returns a pointer to the T that *t holds, or NULL if t is NULL*/ +static T* UNIQUE_THANDLE_GET_T(T)(const UNIQUE_THANDLE(T) t); +``` + +`unique_thandle_ll.h` also provides `UNIQUE_THANDLE_MACRO(T)`, `UNIQUE_THANDLE_LL_TYPE_DECLARE(C, T)`, `UNIQUE_THANDLE_LL_TYPE_DEFINE(C, T)` and `UNIQUE_THANDLE_LL_TYPE_DEFINE_WITH_MALLOC_FUNCTIONS(C, T, malloc_function, malloc_flex_function, free_function)`. `UNIQUE_THANDLE_MACRO(T)` introduces `UNIQUE_THANDLE(T)`, once per translation unit. The other three name the functions after `C` and wrap the type `T`. `UNIQUE_THANDLE_TYPE_DECLARE(T)` is `UNIQUE_THANDLE_MACRO(T)` followed by `UNIQUE_THANDLE_LL_TYPE_DECLARE(T, T)`. + +## UNIQUE_THANDLE_TYPE_DEFINE(T) + +```c +#define UNIQUE_THANDLE_TYPE_DEFINE(T) +``` + +`UNIQUE_THANDLE_TYPE_DEFINE` defines the functions declared by `UNIQUE_THANDLE_TYPE_DECLARE`, the functions `UNIQUE_THANDLE_MALLOC(T)`, `UNIQUE_THANDLE_MALLOC_FLEX(T)`, `UNIQUE_THANDLE_FREE(T)`, `UNIQUE_THANDLE_ADOPT(T)` and `UNIQUE_THANDLE_GET_T(T)`, and `UNIQUE_THANDLE_INSPECT(T)`, which shows the object of a handle in a debugger. + +**SRS_UNIQUE_THANDLE_43_001: [** `UNIQUE_THANDLE_TYPE_DEFINE` shall use `malloc`, `malloc_flex` and `free` to allocate and free memory. **]** + +## UNIQUE_THANDLE_TYPE_DEFINE_WITH_MALLOC_FUNCTIONS(T, malloc_function, malloc_flex_function, free_function) + +```c +#define UNIQUE_THANDLE_TYPE_DEFINE_WITH_MALLOC_FUNCTIONS(T, malloc_function, malloc_flex_function, free_function) +``` + +`UNIQUE_THANDLE_TYPE_DEFINE_WITH_MALLOC_FUNCTIONS` behaves like `UNIQUE_THANDLE_TYPE_DEFINE`, with memory functions specific to `T`. + +**SRS_UNIQUE_THANDLE_43_002: [** `UNIQUE_THANDLE_TYPE_DEFINE_WITH_MALLOC_FUNCTIONS` shall use `malloc_function`, `malloc_flex_function` and `free_function` to allocate and free memory. **]** + +## UNIQUE_THANDLE_INITIALIZE_MOVE(T) + +```c +MOCKABLE_FUNCTION(, void, UNIQUE_THANDLE_INITIALIZE_MOVE(T), UNIQUE_THANDLE(T), t1, UNIQUE_THANDLE(T), t2); +``` + +`UNIQUE_THANDLE_INITIALIZE_MOVE` moves `*t2` into `*t1`, where `*t1` is either `NULL` or not initialized, for example a field of a structure that has just been allocated. + +To set a handle that is not initialized to `NULL`, move a `NULL` handle into it. + +**SRS_UNIQUE_THANDLE_43_008: [** If `t1` is `NULL` then `UNIQUE_THANDLE_INITIALIZE_MOVE` shall return. **]** + +**SRS_UNIQUE_THANDLE_43_009: [** If `t2` is `NULL` then `UNIQUE_THANDLE_INITIALIZE_MOVE` shall return. **]** + +**SRS_UNIQUE_THANDLE_43_010: [** If `t1` is equal to `t2` then `UNIQUE_THANDLE_INITIALIZE_MOVE` shall return. **]** + +**SRS_UNIQUE_THANDLE_43_011: [** `UNIQUE_THANDLE_INITIALIZE_MOVE` shall store `*t2` in `*t1` and set `*t2` to `NULL`. **]** + +## UNIQUE_THANDLE_DISPOSE(T) + +```c +MOCKABLE_FUNCTION(, void, UNIQUE_THANDLE_DISPOSE(T), UNIQUE_THANDLE(T), t); +``` + +`UNIQUE_THANDLE_DISPOSE` disposes the handle in `*t` and sets `*t` to `NULL`. + +**SRS_UNIQUE_THANDLE_43_012: [** If `t` is `NULL` then `UNIQUE_THANDLE_DISPOSE` shall return. **]** + +**SRS_UNIQUE_THANDLE_43_013: [** If `*t` is `NULL` then `UNIQUE_THANDLE_DISPOSE` shall return. **]** + +**SRS_UNIQUE_THANDLE_43_014: [** `UNIQUE_THANDLE_DISPOSE` shall set `*t` to `NULL`. **]** + +**SRS_UNIQUE_THANDLE_43_015: [** `UNIQUE_THANDLE_DISPOSE` shall then call the `dispose` function passed when the handle was created, if it is not `NULL`. **]** + +**SRS_UNIQUE_THANDLE_43_016: [** `UNIQUE_THANDLE_DISPOSE` shall free the memory of the handle. **]** diff --git a/common/inc/c_pal/unique_thandle.h b/common/inc/c_pal/unique_thandle.h new file mode 100644 index 000000000..23f5fcb43 --- /dev/null +++ b/common/inc/c_pal/unique_thandle.h @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#ifndef UNIQUE_THANDLE_H +#define UNIQUE_THANDLE_H + +#include "macro_utils/macro_utils.h" + +#include "c_pal/unique_thandle_ll.h" + +/*macro to be used in headers*/ +/*introduces UNIQUE_THANDLE(T) for a type T and declares the functions that move and dispose it*/ +#define UNIQUE_THANDLE_TYPE_DECLARE(T) \ + UNIQUE_THANDLE_MACRO(T) \ + UNIQUE_THANDLE_LL_TYPE_DECLARE(T, T) + +#endif /*UNIQUE_THANDLE_H*/ diff --git a/common/inc/c_pal/unique_thandle_ll.h b/common/inc/c_pal/unique_thandle_ll.h new file mode 100644 index 000000000..5df0684f4 --- /dev/null +++ b/common/inc/c_pal/unique_thandle_ll.h @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#ifndef UNIQUE_THANDLE_LL_H +#define UNIQUE_THANDLE_LL_H + +#include + +#include "macro_utils/macro_utils.h" + +#include "c_pal/thandle_ll.h" + +#include "umock_c/umock_c_prod.h" + +/*UNIQUE_THANDLE(T) is a handle to a T that has a single owner. +A handle is an array of one slot, like jmp_buf. The compiler rejects UNIQUE_THANDLE(T) copy = handle;, assignments of handles and functions that return a handle. +A UNIQUE_THANDLE(T) parameter points to the caller's handle, and a const UNIQUE_THANDLE(T) parameter only reads it. +The slot holds a const pointer to a struct that is only declared. UNIQUE_THANDLE_ADOPT, UNIQUE_THANDLE_INITIALIZE_MOVE and UNIQUE_THANDLE_DISPOSE write the slot, and UNIQUE_THANDLE_GET_T converts its pointer to a T*.*/ +#define UNIQUE_THANDLE(T) MU_C2(T, _UNIQUE_THANDLE) + +/*initializes a UNIQUE_THANDLE(T) to NULL, as in UNIQUE_THANDLE(T) handle = UNIQUE_THANDLE_NULL;*/ +#define UNIQUE_THANDLE_NULL { { NULL } } + +/*given a previous type T, these are the struct tags of the slot of UNIQUE_THANDLE(T) and of the object the slot points to. The object struct tag is only declared*/ +#define UNIQUE_THANDLE_SLOT_TAG(T) MU_C2(T, _UNIQUE_SLOT) +#define UNIQUE_THANDLE_OBJECT_TAG(T) MU_C2(T, _UNIQUE_OBJECT) + +/*introduces UNIQUE_THANDLE(T), once per type T. The names of the types are built from T, so UNIQUE_THANDLE_LL_TYPE_DECLARE(C, T) for any C uses the same UNIQUE_THANDLE(T)*/ +#define UNIQUE_THANDLE_MACRO(T) \ + typedef struct UNIQUE_THANDLE_SLOT_TAG(T) \ + { \ + const struct UNIQUE_THANDLE_OBJECT_TAG(T)* const volatile object; \ + } UNIQUE_THANDLE(T)[1]; + +/*given a previous type T, UNIQUE_THANDLE_INITIALIZE_MOVE introduces a new name for a function that moves a handle into a handle that is NULL or not initialized*/ +#define UNIQUE_THANDLE_INITIALIZE_MOVE(C) MU_C2(C, _UNIQUE_INITIALIZE_MOVE) + +/*given a previous type T, UNIQUE_THANDLE_DISPOSE introduces a new name for a function that sets a handle to NULL and disposes the object it held*/ +#define UNIQUE_THANDLE_DISPOSE(C) MU_C2(C, _UNIQUE_DISPOSE) + +/*given a previous type T, these are the names of the static functions that UNIQUE_THANDLE_LL_TYPE_DEFINE defines for the .c file that defines T*/ +#define UNIQUE_THANDLE_MALLOC(C) THANDLE_MALLOC(C) +#define UNIQUE_THANDLE_MALLOC_FLEX(C) THANDLE_MALLOC_FLEX(C) +#define UNIQUE_THANDLE_FREE(C) THANDLE_FREE(C) +#define UNIQUE_THANDLE_ADOPT(C) MU_C2(C, _UNIQUE_ADOPT) +#define UNIQUE_THANDLE_GET_T(C) MU_C2(C, _UNIQUE_GET_T) + +/*given a previous type T, UNIQUE_THANDLE_INSPECT introduces a new name for a function that returns the THANDLE_WRAPPER_TYPE_NAME(T) of the object a handle holds - useful in debugging, like THANDLE_INSPECT. The function has no side-effects*/ +#define UNIQUE_THANDLE_INSPECT(C) MU_C2(C, _UNIQUE_INSPECT) + +/*macro to be used in headers*/ +/*declares the functions that move and dispose a UNIQUE_THANDLE(T), named after C. UNIQUE_THANDLE_MACRO(T) introduces UNIQUE_THANDLE(T) before*/ +/*unlike THANDLE_LL_TYPE_DECLARE, it declares only functions: a translation unit defines the slot struct of T once, and REAL_UNIQUE_THANDLE_DECLARE declares the functions of REAL_T for the UNIQUE_THANDLE(T) that UNIQUE_THANDLE_TYPE_DECLARE(T) introduced*/ +#define UNIQUE_THANDLE_LL_TYPE_DECLARE(C, T) \ + MOCKABLE_FUNCTION(, void, UNIQUE_THANDLE_INITIALIZE_MOVE(C), UNIQUE_THANDLE(T), t1, UNIQUE_THANDLE(T), t2); \ + MOCKABLE_FUNCTION(, void, UNIQUE_THANDLE_DISPOSE(C), UNIQUE_THANDLE(T), t); + +#endif /*UNIQUE_THANDLE_LL_H*/ diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt index 9105f713f..d649cfeb4 100644 --- a/linux/CMakeLists.txt +++ b/linux/CMakeLists.txt @@ -15,6 +15,8 @@ set(pal_common_h_files ../common/inc/c_pal/thandle_log_context_handle.h ../common/inc/c_pal/tqueue.h ../common/inc/c_pal/tqueue_ll.h + ../common/inc/c_pal/unique_thandle.h + ../common/inc/c_pal/unique_thandle_ll.h ) set(pal_common_c_files diff --git a/win32/CMakeLists.txt b/win32/CMakeLists.txt index fc1ee57e9..90a4ff3c3 100644 --- a/win32/CMakeLists.txt +++ b/win32/CMakeLists.txt @@ -14,6 +14,8 @@ set(pal_common_h_files ../common/inc/c_pal/thandle_log_context_handle.h ../common/inc/c_pal/tqueue.h ../common/inc/c_pal/tqueue_ll.h + ../common/inc/c_pal/unique_thandle.h + ../common/inc/c_pal/unique_thandle_ll.h ) set(pal_common_c_files