From 7d98d60d24507eb8f51d02e3688c1bc8e314c518 Mon Sep 17 00:00:00 2001 From: thradams Date: Fri, 22 Mar 2024 20:12:22 -0300 Subject: [PATCH] docs --- .md | 436 --------------------- ownership.md | 855 +++++++++++++++-------------------------- src/lib.c | 37 +- src/web/ownership.html | 854 ++++++++++++++++------------------------ 4 files changed, 661 insertions(+), 1521 deletions(-) delete mode 100644 .md diff --git a/.md b/.md deleted file mode 100644 index 622f908c..00000000 --- a/.md +++ /dev/null @@ -1,436 +0,0 @@ - -# Ownership checks for C - -> This feature provides the experience of engaging in pair programming with an attentive developer who ensures program safety. - -## One minute tour - -In the context of this feature, a _destructor_ is a function called before an object's lifetime ends. The _lifetime_ of an object refers to the period during which it is accessible and valid. - -Introducing the new qualifier _owner_, which can be used to declare a type that requires the invocation of a _destructor_ before the end of its lifetime. - -For example, we can declare `malloc` as follows: - - -```c -void * owner malloc(size_t sz); -``` - -To assign ownership to the result of `malloc`, we need to assign it to an _owner_ variable: - -```c -int main() { - void * owner p = malloc(1); -} -``` - -The compiler will generate an error message stating that the object 'p' was not moved or destroyed. - -To address this, we define a destructor for the object by declaring `free` as follows: - -```c -void free(void * owner p); -``` - -```c -int main() { - void * owner p = malloc(1); - free(p); //p cannot be moved to free implicitly -} -``` - -The compiler informs us that 'p' cannot be moved to `free` implicitly and suggests using `move` before the argument. - -By explicitly using `move`, we indicate that the variable has been moved or destroyed at the caller's side. - -``` - free(move p); -``` - -The reason for that I want to make clear at the caller side that a variable has been moved/destroyed. - -However, for certain functions with obvious move semantics based on their names, we can use the `[[implicit]]` attribute to make the usage of `move` optional: - -```c -void free([[implicit]] void * owner p); -``` - - -I hope this brief tour provides you with a glimpse of what I aim to achieve. - -## Move assignment - -With the introduction of the _owner_ qualifier, certain changes in the type system are necessary. Similar to being cautious and explicit when moving a variable into a function, we should adopt the same approach for assignments. - -Consider this sample - -```c -int main() { - void * p1 = malloc(1); - void * p2 = malloc(1); - p1 = p2; - free(p1); - free(p2); -} -``` - -In this case, assigning `p2` into `p1` leads to a memory leak of the `p1` object and a double free of the `p2` object. - -To address this issue, the compiler will check the end of lifetime of `p1` before the assignment. - -The move assignment can be only used if both variables are owner. - -The syntax is: - -``` -p1 = move p2; -``` - -There is nothing especial on this assignment compared with the normal assignment. The only difference is that the intention is explicit. - -After any move (assignment or function argument), the object transitions into an uninitialized state. This state is only for static analysis purposes and has no runtime implications. - -Returning to our sample: - -```c -int main() { - void * owner p1 = malloc(1); - void * owner p2 = malloc(1); - - //error p1 was not moved or destroyed - p1 = move p2; - - free(p1); - free(p2); -} -``` - -The compiler will complain that `p1` was not moved or destroyed before the assignment. To resolve this, we modify the code as follows: - -```c -int main() { - void * owner p1 = malloc(1); - void * owner p2 = move p1; - free(p2); -} -``` - - -If you attempt to use `p1` after moving it, the compiler will issue a warning about using an uninitialized variable. - -In some cases, it may not be possible to determine if an object is initialized or not. For such scenarios, the compiler suggests using options like assertions, the `[[uninitialized]]` attribute, or destroying the object before assignment. - -For instance: - -```c -struct X { char * owner name; }; -void some_function(struct X * p) { - //error: unknown p->name state - p->name = strdup("new text"); -} -``` - -In this case, the compiler will display a message stating that the state of `p->name` cannot be determined and provides suggestions for handling this situation: - -This is how to fix it - -```c - free(p->name); - p->name = strdup("new text"); -``` -or -```c - assert(p->name == NULL); - p->name = strdup("new text"); -``` -or -```c - [[unitialized]] p->name = move strdup("new text"); -``` - - -We can assign a non owner to a owner. - -```c - a = b; /* b is owner*/ -``` - -In this situation the ownership is not transfered. The same think happens when we can function arguments that are not owners. - -```c - void F(T a) {} - owner T a; - F(a); /*ownership is not transfered*/ -``` - -## structs/union/enum - -We can apply the _owner_ qualifier to structs, unions, and enums as well: - -```c -int main() { - owner struct X x = {}; -} -``` - -This syntax works. However, if we forget to include the qualifier for an object that requires a destructor, we have a leak. - -To address this, an additional syntax is provided for tagged objects: - -```c -struct owner X { - ... -} -``` - -By using this syntax, the object is qualified as _owner_ by default: - -```c -int main() { - struct X x = {}; -} //**"object 'x' was not moved/destroyed"** -``` - - -## pointers - -So far, the pointer samples have used `void *`. Now, let's consider the following situation: - -```c -struct owner X { char * owner name; }; -int main() { - struct X * owner p = new_x(); -} -``` - -Here, `p` is an _owner_ pointer to an _owner_ object. In this case, the pointer is the owner of both the memory and the object. Moving the pointer will transfer both responsibilities, resulting in the destruction of the object and the memory. - -Consider this sample - -```c - -void x_delete([[implicit]] struct X * owner p) -{ - if (p){ - p->free(name); - free(p); - } -} -int main() { - struct X * owner p = new_x(); - x_delete(p); -} -``` - -This code demonstrates the same behavior as the following code: - - -```c -int main() { - struct X * owner p = new_x(); - if (p){ - p->free(name); - free(p); - } -} -``` - -Both scenarios require the same checks, indicating that there is nothing special about the destructors. - -The compiler needs to check each _owner_ member of the struct individually: - - -```c -int main() { - struct X * owner p = new_x(); - if (p){ - free(p->name); - free(p); - } -} -``` - -Having this logic in a specialized function makes the compiler's job easier, as the flow analysis becomes simpler. It's important to assist the compiler in order to leverage its capabilities. - -Another detail to note is that when we free an _owner_ pointer using `void *`, the compiler assumes that we are destroying the memory and not the pointed object. - -If the pointed object is also an _owner_, the compiler checks if the object is destroyed first. In the provided sample, `free(p->name);` was the only _owner_ member of the struct, so it was safe to call `free` on `p`. - - -## destroying structs\unions - -Consider: - -``` -struct owner X { char * owner name; }; -void x_destroy([[implicit]] struct X x) -{ - free(x.name); -} -int main() { - struct X x; - x_destroy(x); -} -``` - -This code is correct and works as expected. - -However, if we want to pass the struct using a pointer like: - - -``` -void x_destroy([[implicit]] struct X * owner p) { - free(p->name); -} -``` - -The problem arises when we pass an _owner_ pointer. The compiler assumes that we want to destroy both the object and the memory. However, in this case, the object is on the stack, and we only want to destroy the object, not to free the memory. - -To address this, a qualifier called `obj_owner` is introduced, which can only be used for pointers: - -``` -void x_destroy([[implicit]] struct X * obj_owner p) { - free(p->name); -} -``` - -This qualifier indicates that the pointer is the owner of the object but not the owner of the memory. - - -## Returning owner type - -Returning an _owner_ variable is the same as moving it. The design decision here was not require the `move` keyword. - - -```c -struct list make() -{ - struct list {...}; - return list; /*moved*/ -} -``` - -## Owner arrays -As expected arrays and pointer are related. - -The _owner_ qualifier can be placed inside the array together with the array size: - -```c -void array_destroy(int n, struct X a[owner n]) -{ -} - -int main() -{ - struct X a[owner 100]; - array_destroy(100, a); -} -``` - -We can also pass an _owner_ pointer: - -```c -void array_destroy(int n, struct X a[owner n]) -{ -} - -int main() -{ - struct X * owner p = calloc(100, sizeof(struct X)); - array_destroy(100, p); - free(p); -} -``` - -By convention, passing an _owner_ pointer to an array destructor will not transfer ownership of the memory, just of the pointed object. - -To destroy both the array and the memory, we can use: - -```c -void array_delete(int n, struct X * owner p) -{ -} - -int main() -{ - struct X * owner p = calloc(100, sizeof(struct X)); - array_delete(100, p); -} -``` - -## Reality check I - -Let's examine how these rules can help with `fopen` and `fclose`. - - -```c -FILE* owner fopen(char const* name,char const* mode); -int fclose([[implicit]] FILE* owner f); -``` - -```c -int main() { - FILE * owner p = fopen("text.txt", "r"); - if (p) { - fclose(p); - } -} -``` - -In this scenario, we encounter a problem because not all control paths call the destructor. The compiler would emit a warning in such cases. - -However, the code is correct because we don't need to and cannot call `fclose` on a null pointer. - -To address this, null checks need to be implemented in the static analyzer. The compiler will not emit warnings if it can prove that an _owner_ variable is empty or uninitialized at the end of its lifetime. - - -## Reality check II - -```c -int main() -{ - FILE * owner f = NULL; - if (fopen_s( &f,"f.txt", "r") == 0) { - fclose([[initialized]] f); - } - [[uninitialized]] f; -} -``` - -When initialization needs to be checked using a result code, we don't have semantics to provide the necessary information to the compiler. In this case, an annotation ` - -In this case, an annotation `[[initialized]]` is needed to inform the compiler that the variable is initialized, and an annotation `[[uninitialized]]` is needed to inform the compiler that the variable is uninitialized. - - -## What's next? -Implement this in cake. -http://thradams.com/cake/index.html -The hard part is the flow analysis. - -## Headers -move, owner, obj_owner and implicit can be defined as macros on . (Similar of C99 did with bool _Bool) - -```c -#define move _Move -#define owner _Owner -#define obj_owner _Obj_owner -#define implicit [[implicit]] -``` - -Defining this macros to empty makes to code compatible with any compiler. - -## Conclusion -This feature aims to provide ownership checks in C by introducing the _owner_ qualifier. It ensures that objects are properly destroyed or moved before their lifetime ends, preventing memory leaks and use-after-free errors. The compiler assists in detecting potential issues and suggests necessary changes to the code. By leveraging ownership checks, developers can write safer and more reliable code in C. - -## Motivation - -I have placed the motivation section at the end, considering that memory safety guarantees are already a widely discussed topic and the motivation becomes apparent as readers delve into the content. - -In the C programming language, manual management of resources such as memory is necessary. We rely on functions like `malloc` to allocate memory and store the resulting address in a variable. To properly deallocate memory when it is no longer needed, we must use the address returned by `malloc` and call the `free` function. - -Consequently, the variable holding the memory address is considered the owner of that memory. Discarding this address without calling `free` would result in a memory leak, which is an undesirable scenario. - -Resource leaks present a significant challenge because they often remain silent problems, initially having no immediate impact on a program's behavior or causing immediate issues. These leaks can easily go unnoticed during unit tests, creating a false sense of security. It is crucial to address and track these problems early on. By doing so, we can not only prevent potential complications but also save valuable time and resources in the long run. - -Moreover, these checks also help prevent occurrences of double free or use-after-free issues. While both problems typically lead to immediate failures at runtime, having preventive measures in place is highly advantageous. - diff --git a/ownership.md b/ownership.md index bb005978..872e4e1b 100644 --- a/ownership.md +++ b/ownership.md @@ -1,5 +1,5 @@ -Last Updated 27/02/2024 +Last Updated 22/03/2024 This is a work in progress, both design and implementation. Cake source itself is being used to validate the concepts. @@ -16,13 +16,15 @@ to code as they wish, they must either persuade the compiler or disable analysis A human factor must be considered to ensure that annotations do not make the work too boring with excessive details. In this regard, selecting defaults that cover the most common cases is crucial. -## Owner Objects +## Concepts + +### Owner Objects An **owner object** is an object referencing another object and managing its lifetime. -The most common type of owner objects are pointers, often referred as **owner pointers**. An owner pointer is created with the qualifier owner, as illustrated in Listing 1: +The most common type of owner objects are pointers, often referred as **owner pointers**. An owner pointer is created with the qualifier owner. -**Listing 1 - Owner Pointer to FILE** +**Sample - Owner Pointer to FILE** ```c #include @@ -36,23 +38,18 @@ int main() } ``` -> **owner** is actually a macro declared in ownership as **_Owner**. +> Note: **owner** is actually a macro declared in ownership as **_Owner**. The ownership mechanism has some rules that will be listed gradually throughout the text. -**Rule**: An **owner object** is always the unique owner of the referenced object. +**Rule:** An **owner object** is always the unique owner of the referenced object. -**Rule**: When owner objects are copied the ownership is transfered. +**Rule:** When owner objects are copied the ownership is transfered. **Rule:** Before the end of its lifetime, owner objects must move the ownership of the objects they own. -(Cake ownership model does not have the concept of destroyed/deleted, - instead everything is a transformation and ownership is moved during these - transformations.) - -For example, in Listing 2, the ownership of the owner pointer `f` is transferred to `f2`: -**Listing 2 - Assignment of Owner Objects is a Move** +Sample ```c #include @@ -67,19 +64,21 @@ int main() } ``` -Invoking a function `fclose` is analogous to assignment of the argument `f2`, resulting in the transfer of ownership of `f2` to the function parameter. Listing 3, shows the declaration of `fclose`. +Invoking a function `fclose` is analogous to assignment of the argument `f2`, resulting in the transfer of ownership of `f2` to the function parameter. -**Listing 3 - Declaration of close** +Sample - Declaration of fclose ```c void fclose(FILE *owner p); ``` +> Note: The cake ownership model does not include the concept of a destroyed or deleted object. Instead, everything is viewed as a transformation, where the object is broken into smaller parts and those parts are moved. + ### Non-pointer owner objects -We can have other types of **owner objects**. For instance, Berkeley sockets use an integer to identify the socket, as shown in listing 4: +We can have other types of **owner objects**. For instance, Berkeley sockets use an integer to identify the socket. -**Listing 4 - Non-Pointer owners objects** +Sample ```c owner int server_socket = @@ -88,19 +87,47 @@ We can have other types of **owner objects**. For instance, Berkeley sockets use close(server_socket); ``` -The location and usage of qualifier owner is similar of const qualifier. For pointers it goes after `*`, for this socket sample it can be before int. +> Note: The location and usage of the qualifier owner is similar to the const qualifier. For pointers, it goes after *, and for this socket sample, it can be before int. The owner qualifier belongs to the object (memory) that holds the reference. + +When a struct or union have at least one owner object it makes the struct a owner object too. + +**Rule:** Owner objects cannot be discarded. + +```c +#include +#include + +int main() { + fopen("file.txt", "r"); //warning +} +``` + +**Rule:** A non-owner object cannot be copied to a owner object. + +**Rule:** The null pointer constant can be used to initialize owner objects. (Even it its type is non-owner) + +**Sample** + +```c +FILE * f(); //returning non owner +int main() { + FILE * owner file = f(); //ERROR + FILE * owner file2 = 0; //OK +} +``` + ### View Objects A **view object** is an object referencing another object without managing its lifetime. -**Rule** The lifetime of the referenced object must be bigger than the lifetime of the view object. +**Rule:** The lifetime of the referenced object must be longer than the lifetime of the view object. The most common view objects are pointers called **view pointers**. -The view qualifier is not necessary for pointers, since it's the default behavior. When an owner object is copied to a view object, the ownership is not transferred, as shown in Listing 5: - -**Listing 5 - Calling Function with View Parameters** +The view qualifier is not necessary for pointers, since it's the default behavior. When an owner object is copied to a view object, the ownership is not transferred. + +**Sample** ```c #include @@ -117,11 +144,11 @@ int main() { } ``` -When a struct or union have at least one owner object it makes the struct a owner object too. -When a **view** qualifier is used in structs, it makes all members as view objects. Listing 6. + +When a **view** qualifier is used in structs, it makes all members as view objects. -**Listing 6 - A view parameter** +**Sample - A view parameter** ```c #include @@ -140,48 +167,41 @@ int main() { } ``` -It is interesting to compare against const qualifier. -While const adds a qualifier "const" "view" removes the qualifier "owner". +> Note: It is interesting to compare against const qualifier. While const adds a qualifier "const" "view" removes the qualifier "owner". -### Returning view pointers. -We cannot return local variables as view pointers. +### Returning a pointer to a view object +We can check the rule "The lifetime of the referenced object must be longer than the lifetime of the view object" with these constrains. + +We cannot return the address of local variables + ```c int * f() { int a = 1; - return &a; -} -int main() -{ - int * p = f(); - //warning: function returns address of - //local variable [-Wreturn-local-addr] + return &a; //ERROR } -``` - -But we can return static variables and function arguments. +``` + +We can return the address of global variables ```c static int a = 1; - int * f() { - return &a; -} -int * f2(int *p) { - return p; -} + return &a; // OK +} +``` + +And we can return parameters -int main() -{ - int * p = f(); - int b = 1; - p = f2(&b); +```c +int * f2(int *p) { + return p; //OK } ``` -Another sample: +Now consider: ```c #include @@ -201,8 +221,13 @@ int main(){ } ``` -Examining the implementation reveals that the returned view pointer's lifetime can be that of either 'a' or 'b'. Our goal is to set contracts at the declaration level. Following the concept of ensuring safety by default, we assume that the returned view pointers have the shortest scope, limited to the function call. -(Cake is not doing this check at this moment) +Examining the implementation reveals that the returned view pointer's lifetime can be that of either 'a' or 'b'. + +Our goal is to set contracts at the declaration level. + +Following the concept of ensuring safety by default, we assume that the returned view pointers have the shortest scope, limited to the function call. + +> Note : Currently, this check is missing at cake ### View pointer as struct members Consider this sample. @@ -216,14 +241,15 @@ struct Y { }; ``` -An object Y pointed by pY must live longer than object X. -This check must be done at instantiation. +The rule "The lifetime of the referenced object must be longer than the lifetime of the view object" needs to be checked at each instantiation. + +> Note : Currently, this check is missing at cake ### Deleting Owner Pointers -**Owner pointers** take on the responsibility of owning the pointed object and its associated memory, treating them as distinct entities. A common practice is to implement a delete function to release both resources, as illustrated in Listing 7: +**Owner pointers** take on the responsibility of owning the pointed object and its associated memory, treating them as distinct entities. A common practice is to implement a delete function to release both resources. -**Listing 7 - Implementing the delete function** +**Sample - Implementing the delete function** ```c #include @@ -244,7 +270,7 @@ void x_delete(struct X *owner p) { } int main() { - struct X * owner pX = calloc( 1, sizeof * pX); + struct X * owner pX = calloc(1, sizeof * pX); if (pX) { /*...*/; x_delete( pX); @@ -252,9 +278,9 @@ int main() { } ``` -When the object is created on the stack, we can implement a destructor, as shown in Listing 8: +When the object is created on the stack, we can implement a destructor. -**Listing 8 - Implementing a destructor** +**Sample - Implementing a destructor** ```c #include @@ -279,12 +305,11 @@ However in C, structs are typically passed by pointer rather than by value. To t A pointer qualified with **obj_owner** is the owner of the pointed object but not responsible for managing its memory. -Listing 9 illustrates how to implement a destructor using a obj_owner pointer parameter. +The next sample illustrates how to implement a destructor using a obj_owner pointer parameter. -**Listing 9 - Implementing a destructor using obj_owner** +**Sample - Implementing a destructor using obj_owner** ```c -struct X { #include #include @@ -304,9 +329,9 @@ int main() { } ``` -In order to prevent moving from a non owner object, only `address of expressions` to `obj_owner` are allowed. For instance, listing 10 shows we cannot move a view pointer. +In order to prevent moving from a non owner object, only _address of expressions_ to **obj_owner** are allowed. -**Listing 10 - Non address of expression or owner pointer.** +**Sample - Non address of expression or owner pointer.** ```c #include @@ -325,9 +350,9 @@ void f(struct X * x) { } ``` -We can copy an owner pointer to an **obj_owner** pointer. In this scenario, only the ownership of the pointed object is transferred, not the memory ownership. Listing 11 illustrates how we can use `x_destroy` in the implementation of `x_delete`. +We can copy an **owner** pointer to an **obj_owner** pointer. In this scenario, only the ownership of the pointed object is transferred, not the memory ownership. -**Listing 11 - Using `x_destroy` to implement `x_delete`** +**Sample - Using `x_destroy` to implement `x_delete`** ```c #include @@ -349,7 +374,7 @@ void x_delete(struct X *owner p) { } int main() { - struct X * owner pX = calloc( 1, sizeof * pX); + struct X * owner pX = calloc(1, sizeof * pX); if (pX) { /*...*/; x_delete( pX); @@ -371,7 +396,7 @@ void f(int a[owner]) But I think this is quite uncommon. -## Static analysis - Checking the rules at compile time +## Flow analysis Let's revisit our first example: @@ -383,13 +408,15 @@ int main() { FILE *owner f = fopen("file.txt", "r"); if (f) - fclose(f); + fclose(f); //f is moved } ``` -The flow analysis must ensure that when the owner pointer `f` goes out of scope, it does not own any objects. At the end of the scope, `f` can be either null or moved, and both states ensure that there are no remaining resources. +"Rule: Before the end of its lifetime, owner objects must move the ownership of the objects they own." -To check the ownership rules, the compiler uses six states: +At the end of the scope, `f` can be either null or moved, and both states ensure that the rule is followed. + +To check the ownership rules, the compiler need flow analysis and it uses six states: - uninitialized - moved @@ -398,15 +425,15 @@ To check the ownership rules, the compiler uses six states: - zero - not-zero -We can print these states using the **static_debug** declaration. We can also assert the variable is at a certain state using the **static_state** declaration. Listing 12 shows this usage: +We can print these states using the **static_debug** declaration. We can also assert the variable is at a certain state using the **static_state** declaration. -**Listing 12 - Usage of static\_state and static\_debug** +**Sample - Usage of static\_state and static\_debug** ```c int main() { int a; - static_state(a, "uninitialized"); - static_debug(a); + static_state(a, "uninitialized"); //checks a state + static_debug(a); //prints 'a' state } ``` @@ -419,104 +446,64 @@ c:/main.c:4:2: note: static_debug a == "uninitialized" ``` +#### Uninitialized state -As we have just seen, the **uninitialized** state is the state of variables that are declared but not initialized. +The **uninitialized** state is the state of variables that are declared but not initialized. -The compiler ensures that we don't read uninitialized objects. +Flow analysis must ensure that we don't read uninitialized objects. ```c -void f1(int i); -int main() { +void f(int condition) { int i; - f1(i); //error: uninitialized object 'i' + if (condition) + i = 0; + printf("%d", i); //warning i may be uninitialized } ``` -The other situation were variables becomes **uninitialized** is when moving ownership to function parameters. This prevents bugs like double free or use after free. +The other situation were variables becomes **uninitialized** is when moving ownership to function parameters. -```c -#include -#include +This prevents bugs like double free or use after free. -struct X { - char * owner text; -}; - -void x_delete(struct X * owner p) -{ - if (p) { - free(p->text); - free(p); - } -} - -void f(struct X * p){} +```c +int * _Owner f(); +void free(void * _Owner _Opt p); int main() { - struct X * owner p = malloc(sizeof(struct X)); - p->text = malloc(10); - x_delete(p); - f(p); //uninitialized object 'p' + int * _Owner p = f(); + free(p); + free(p); //warning p is uninitialized } ``` -When objects are moved within a local scope, the state is "moved" rather than "uninitialized." The "moved" state is similar to the "uninitialized" state. For instance, it's not possible to move an object that has already been moved. - -```c -#include -#include - -struct X { - char * owner text; -}; - -void x_delete(struct X * owner p) -{ - if (p) - { - free(p->text); - free(p); - } -} +#### Moved state -void f(struct X * p){} - -int main() { - struct X * owner p = malloc(sizeof(struct X)); - p->text = malloc(10); - - struct X * owner p2 = 0; - p2 = p; //MOVED +The **moved** state is similar to the uninitialized state. The difference is that the moved state is used when moving local variables. This information could be useful in the future to be less restrictive than uninitialized. - f(p); //error: object 'p' was moved - x_delete(p2); -} - -``` - -The "moved" state was introduced instead of solely relying on the "uninitialized" state because static analysis benefits from having more information on local variables. "Moved" objects may, in some cases, serve as view objects. For example, in listing XX, the x object has been moved to x2, but it is safe to use x as "view" object even after the move. + **Sample - local scope moves** ```c -#include -#include - -struct X { - char * owner text; -}; +int * _Owner f(); +void free(void * _Owner _Opt p); int main() { - struct X x = {0}; - //... - struct X x2 = {0}; - x2 = x; //MOVED - free(x2.text); + int * _Owner p = f(); + int * _Owner p2 = 0; + p2 = p; // p moved to p2 + + //compiler knows that *p still valid + + free(p); //warning p was moved + free(p2); //ok } ``` + -> Note: The current implementation of cake does not handle all necessary -> states to ensure the safe usage of moved objects. +#### _Out qualifier -A common scenario where uninitialized objects are utilized is when a pointer to an uninitialized object is passed to an "init" function. This situation is addressed by the qualifier **out**. +A common scenario where uninitialized objects are utilized is when a pointer to an uninitialized object is passed to an "init" function. + +This situation is addressed by the qualifier **_Out/out**. ```c #include @@ -527,27 +514,31 @@ struct X { char * owner text; }; -int init(out struct X *p, const char * text) +int init(out struct X *p) { - //SAFE - p->text = strdup(text); + p->text = strdup("a"); //safe } int main() { struct X x; - init(&x, "text"); + init(&x); free(x.text); -} - +} ``` -The "out" qualifier is necessary at the caller side and at the implementation. -The caller is informed that the argument must be uninitialized, and the implementation is aware that it can safely override the contents of the object `p->text = strdup(text);` without causing a memory leak. +With the out qualifier, caller is informed that the argument must be uninitialized. + +The implementation is aware that it can safely override the contents of the object `p->text` without causing a memory leak. -There is no explicit "initialized" state. When referring to initialized objects, it means the state is neither "moved" nor "uninitialized." +> Note: There is no explicit "initialized" state. When referring to initialized objects, it means the state is neither "moved" nor "uninitialized.". + +**Rule:** All objects passed as arguments must be initialized and all objects reachable must be initialized. + -**Rule** By default, the parameters of a function are considered initialized. The exception is created with out qualifier. +**Rule:** By default, the parameters of a function are considered initialized. The exception is created with out qualifier. + +**Rule:** We cannot pass initialized objects, or reachable initialized objects to **out** qualified object. For instance, at set implementation we need free text before assignment. @@ -562,13 +553,12 @@ struct X { int init(out struct X *p, const char * text) { - //SAFE - p->text = strdup(text); + p->text = strdup(text); //safe } int set(struct X *p, const char * text) { - free(p->text); //NECESSARY + free(p->text); //necessary p->text = strdup(text); } @@ -580,13 +570,80 @@ int main() { } ``` -**Rule** All objects passed as arguments must be initialized. The exception is when object is out qualified. +**Rule:** Function never returns uninitialized objects or reachable uninitialized objects. + +TODO void objects. -**Rule**: We cannot pass initialized objects to **out** qualified object. +**Rule:** Non owner objects accessible with parameters cannot leave scope with uninitialized/moved objects. -The **null** state means that owner objects are initialized and not referencing any object. Listing 13 shows a sample using owner pointers: +```c +#include +#include +#include -**Listing 13 - Null state** +struct X { + char * owner name; +}; + +void x_destroy(struct X * obj_owner p) { + free(p->name); +} + +struct Y { + struct X x; +} + +void f(struct Y * p) { + x_destroy(&p->x); //breaking the rule +} + +int main() { + struct Y y = {}; + y.x.name = strdup("a"); + f(&y); + free(y.x.name); +} + +``` + +Sample of swap if fine because at end of scopes objects are not uninitialized/moved. + +```c +#include +#include + +struct X +{ + char * owner name; +}; + +void x_destroy(struct X * obj_owner p) +{ + free(p->name); +} + +void x_swap(struct X * a, struct X * b) { + struct X temp = *a; + *a = *b; + *b = temp; +} //ok + +int main() { + struct X x1 = {}; + struct X x2 = {}; + + x_swap(&x1, &x2); + + x_destroy(&x1); + x_destroy(&x2); +} +``` + +#### Null and Not-Null state + +The **null** state means that pointers/objects are initialized and not referencing any object. + +**Sample - Null state** ```c #include @@ -597,11 +654,26 @@ int main() { } ``` -The **not-null** state indicates that the object is referencing some object. -The state can be a combination of possibilities like **null** and **not-null**. We can check possible combination using "or". -This particular combination **null or not-null** has a alias **maybe-null** as shown in listing 14. +**Rule:** Before assignment, owner objects, must not be holding objects. The state must be null or uninitialized/moved. + +Sample + +```c +#include +#include +int main() { + FILE * owner file = fopen("file.txt", "r"); + file = fopen("file.txt", "r"); //warning +} +``` + +The **not-null** state indicates that the object is initialized and not referencing any object. + +The final state is combination of possibilities like **null** and **not-null**. We can check possible combination using "or" at `static_state`. + +The combination **null or not-null** has a alias **maybe-null**. -**Listing 14 - not-null and maybe-null state** +**Sample - not-null and maybe-null** ```c #include @@ -619,15 +691,71 @@ int main() } ``` -The **zero** state is used for non-owner objects to complement and support uninitialized checks. -**Rule** Pointer parameters are consider not-null by default. The exception is created using the qualifier **_Opt**. +**Rule:** Pointer parameters are consider not-null by default. The exception is created using the qualifier **_Opt**. + +The **_Opt**. qualifier is used to tell the caller that the pointer can be at null state and tells the implementation that it is necessary to check the pointer for null before usage. + +**Rule:** Pointers that can be null cannot be passed to functions that don't declare the pointer as opt. + +```c +#include +#include + +struct X {int i;}; + +void f(struct X * p){ +} + +int main() +{ + struct X * owner p = calloc(1, sizeof * p); + f(p); //warning pointer can be null + free(p); +} +``` -To tell the compiler that the pointer can be null, we use the qualifier **_Opt**. +> Note: To enable null checks in cake use -nullchecks + +This sample can be fixed in two ways. -(Currently Cake is only doing null-checks if the -nullchecks option is passed to the compiler, the cake source itself has to move to nullchecks) +```c +/*...*/ +void f(struct X * p){} +int main() +{ + struct X * owner p = calloc(1, sizeof * p); + if (p) + f(p); //ok + free(p); +} +``` -**Listing 15 - The zero state** +or + +```c +/*...*/ +void f(struct X * opt p){ } +int main() +{ + struct X * owner p = calloc(1, sizeof * p); + f(p); //ok + free(p); +} +``` + +Considering these semantics, the correct declaration of free is: + +```c +void free(void * _Owner _Opt p); //p can be null +``` + + +#### Zero and Not-Zero state + +The **zero** state is used for non-owner objects to complement and support uninitialized checks. + +**Sample - The zero state** ```c int main() @@ -637,11 +765,13 @@ int main() } ``` -**Zero** and **null** are different states. This difference is necessary because, for non-pointers like the socket sample, 0 does not necessarily means null. The compiler does not know the semantics for types that are not pointers. However, you can use **static_set** to override states. In Listing 16, we annotate that server_socket is null, which doesn't mean it is zero but indicates that it is not holding any resources and is safe to return without calling close. +**Zero** and **null** are different states. This difference is necessary because, for non-pointers like the socket sample, 0 does not necessarily means null. The compiler does not know the semantics for types that are not pointers. +#### static_set +We can use **static_set** to override states. In the next sample, we annotate that server_socket is null, which doesn't mean it is zero but indicates that it is not holding any resources and is safe to return without calling close. -**Listing 16 - Usage of static_set** +**Sample - Usage of static_set** ```c owner int server_socket = @@ -656,8 +786,7 @@ int main() The **not-zero** state is used for non-owner objects to indicate the value if not zero. - -Similarly of **maybe-null**, **any** is a alias for **zero or not-zero** state. +**any** is a alias for **zero or not-zero** state. ```c int f(); @@ -666,24 +795,18 @@ int main() { int i = f(); static_state(i, "any"); } - ``` -By the way, the result of functions are never **uninitialized** by convention. -By default, pointers have the state **maybe-null** and other types have the state **any**. - -**Rule**: Function never returns uninitialized objects. - Now let's consider `realloc` function. ```c void * owner realloc( void *ptr, size_t new_size ); ``` -In the declaration of `realloc`, we are not moving the ptr. The reason for that is because the `ptr` may or may not be moved. If the function returns NULL, `ptr` was not moved. Listing 17 shows how **static_set** can be used. +In the declaration of `realloc`, we are not moving the ptr. The reason for that is because the `ptr` may or may not be moved. If the function returns NULL, `ptr` was not moved. -**Listing 17 - Using static_set with realloc** +**Sample - Using static_set with realloc** ```c #include @@ -702,131 +825,10 @@ int main() } ``` -Without the `static_set` we have this error - -``` -error: memory pointed by 'p' was not released before assignment. - 11 | p = p2; -``` - -The state of an object is a combination of all possible states. For instance, let's print and check the state of `f` at listing 17. - -**Listing 17 - Flow analysis** - -```c -#include -#include -#include - -int main() { - FILE *owner f = fopen("file.txt", "r"); - if (f) - fclose(f); - static_state(f, "uninitialized or null"); -} -``` - -When objects are moved to functions, the state is `uninitialized` that is the worst scenario of what can happens inside the function. When objects are moved inside the same source the state is `moved`. - -```c -#include -#include -#include - -int main() { - FILE *owner f = fopen("file.txt", "r"); - FILE *owner f2 = f; - static_state(f, "moved"); - fclose(f2); -} -``` - - -**Rule:** We cannot discard owner objects as showed in listing 18. - -**Listing 18 - owner objects cannot be discarded.** - -```c -#include -#include - -int main() { - //error: ignoring the result of owner type - fopen("file.txt", "r"); -} -``` - -**Rule:** Before the assignment of owner objects, the compiler checks if the owner object is not holding any resource, as shown in Listing 19: - -**Listing 19 - Check before assignment** - -```c -#include -#include - -int main() { - FILE * owner file = fopen("file.txt", "r"); - - //error: memory pointed by 'file' was not - //released before assignment. - file = fopen("file.txt", "r"); -} -``` - -### Function Parameters -For function parameters, the state of the object is initialized by default. - -**Listing 20 - States of function parameters** - -```c -#include -#include - -struct X { - char *owner text; -}; - -void set(struct X * x, const char * name) { - free(x->text); - x->text = strdup(name); -} - -``` - -When the argument can be uninitialized we use the qualifier _Out. -```c -#include -#include +### assert is a statement -struct X { - char *owner text; -}; - -void init(out struct X * x) { - x->text = strdup("a"); -} - -void set(struct X * x, const char * name) { - free(x->text); - x->text = strdup(name); -} - -``` - - -After calling init the object may or not be initialized. - -At listing 21, we show how **static_set** can be used to set states. - -**Listing 21 - Using static_set** - -```c -TODO -``` - - -But when possible we can use assert that works both as static information and runtime check in debug. +When possible we can use assert that works both as static information and runtime check in debug. Consider the following sample where we have a linked list. Each node has owner pointer to next. The next pointer of the tail of the list is always pointing to null, unless we have a bug. But the compiler does not know `list->tail->next` is null. Using assert we give this inform to the compiler and we also have a runtime check for possible logic bugs. @@ -861,237 +863,6 @@ void list_append(struct list* list, struct node* owner node) } ``` -**Rule:** A non-owner object cannot be copied to a owner object. - -But, the null pointer constant is converted to a null owner pointer. Se listing 23. - -**Listing 23 - non owner cannot be copied to owner** - -```c -FILE * f(); -int main() { - FILE * owner file = f(); //ERROR - FILE * owner file2 = 0; //OK -} -``` - -**Rule:** A view pointer parameter cannot leave the scope with moved/uninitialized objects. Listing 24 - -**Listing 24 - Messing with view parameters** - -```c - -#include -#include -#include - -struct X -{ - char * owner name; -}; - -void x_destroy(struct X * obj_owner p) -{ - free(p->name); -} - -struct Y -{ - struct X x; -} - -void f(struct Y * p) { - // parameter 'p' is leaving scoped with a uninitialized - // object 'p.x.name' - x_destroy(&p->x); -} - -int main() { - struct Y y = {}; - y.x.name = strdup("a"); - f(&y); - free(y.x.name); -} -``` - -However, listing 25 is correct, because before the end of scope states of the parameters are restored. - -**Listing 25 - swap function** - -```c -#include -#include - -struct X -{ - char * owner name; -}; - -void x_destroy(struct X * obj_owner p) -{ - free(p->name); -} - -void x_swap(struct X * a, struct X * b) { - struct X temp = *a; - *a = *b; - *b = temp; -} - -int main() { - struct X x1 = {}; - struct X x2 = {}; - - x_swap(&x1, &x2); - - x_destroy(&x1); - x_destroy(&x2); -} -``` - -**Rule:** When objects are moved to functions, they become uninitialized. This prevents bugs like double free. Listing 26. - -``` - int main() { - void * owner p = malloc(1); - free(p); - free(p); //ERROR p is uninitialized -} -``` - -**Rule:** moved objects cannot be moved. Listing 27. - -**Listing 27 - We cannot move a moved or uninitialized object** - -```c -int * owner p1 = ...; -int * owner p2 = p1; -int * owner p3 = p1; //ERROR p1 was moved -``` - -```c -int * owner f(int * owner p1) { - int * owner p2 = p1; - return p1; //ERROR p1 was moved -} -``` - -**Rule:** When coping a owner object to to a view object the compiler must check the lifetime. Listing 28 - -**Listing 28 - Lifetime check** - -```c -void using_file(FILE * f); - -struct X { - char *owner text; -}; - -struct X * owner make_owner(); - -int main() { - struct X *p = 0; - { - struct X * owner p2 = make_owner(); - p = p2; //error p lives longer than p2 - free(p2); - } - - using_file(fopen("file.txt", "r")); //ERROR -} -``` - - -**Rule:** Returned objects must be valid. - - -**Listing 34 - Assuming returned objects are valid** - -```c -void* owner malloc(unsigned long size); -void free(void* owner ptr); - -struct X { - char *owner text; -}; - -struct X * owner f(); - -int main() -{ - struct X * owner pX = f(); - if (pX) { - static_state(pX->text, "maybe-null"); - } -} -``` - - -**Rule:** Arguments must be valid. - -Exception - -`malloc` and `calloc` have built in semantics. - - -**Listing 35 - Function Arguments cannot be in a moved or uninitialized state** - -```c -#include -#include -struct X { - char *owner text; -}; - -void f(struct X * p); - -int main() { - struct X x; - f(&x); //ERROR - - struct X x1 = {0}; - struct X x2 = x1; //MOVED - f(&x1); //ERROR -} -``` - -For this reason, an qualifier `out` can be added to allow passing unitalicized objects. - -```c -void free(void* _Owner p); -char* _Owner strdup(const char* s); - -struct X { - char* _Owner s; -}; - -void init(_Out struct X * px) -{ - static_state(px, "maybe-null"); - static_state(px->s, "uninitialized"); - px->s = strdup("a"); -} - -void set(struct X* px, const char* text) -{ - static_state(px, "maybe-null"); - static_state(px->s, "maybe-null"); - free(px->s); - px->s = strdup(text); -} - -int main() { - struct X x; - init(&x); - set(&x, "b"); - free(x.s); -} -``` - - - - - ## Ownership Feature Strategy (Inspired by stdbool.h) @@ -1117,4 +888,6 @@ When we parsing malloc from MSVC/GCC we ignore the diferences, but at the curren `ownership.h` header must be included before to take efect. +The standard also could make out, opt, view, owner, obj_owner as reserved keyword for the future. +_ \ No newline at end of file diff --git a/src/lib.c b/src/lib.c index d6d2a47b..d1a00f02 100644 --- a/src/lib.c +++ b/src/lib.c @@ -31206,7 +31206,7 @@ static void del(struct token* from, struct token* to) { struct token* p = from; while (p) - { + { p->flags |= TK_C_BACKEND_FLAG_HIDE; p = p->next; @@ -31274,7 +31274,7 @@ void convert_if_statement(struct visit_ctx* ctx, struct selection_statement* p_s { /* OBS: - To debug this code use + To debug this code use print_code_as_we_see(&ctx->ast.token_list, false); before and after each transformation */ @@ -31286,9 +31286,9 @@ void convert_if_statement(struct visit_ctx* ctx, struct selection_statement* p_s return; } - + token_list_paste_string_before(&ctx->ast.token_list, p_selection_statement->first_token, "{"); - + struct token_list init_tokens_cut = { 0 }; if (p_selection_statement->p_init_statement && @@ -31303,31 +31303,31 @@ void convert_if_statement(struct visit_ctx* ctx, struct selection_statement* p_s init_tokens_cut = cut(p_selection_statement->p_init_statement->p_simple_declaration->first_token, p_selection_statement->p_init_statement->p_simple_declaration->last_token); } - + token_list_insert_before(&ctx->ast.token_list, p_selection_statement->first_token, &init_tokens_cut); - + struct token_list condition_tokens_cut = { 0 }; if (p_selection_statement->condition && p_selection_statement->condition->expression) { - /*leave it */ + /*leave it */ } else if (p_selection_statement->condition && p_selection_statement->condition->p_declaration_specifiers) { condition_tokens_cut = cut(p_selection_statement->condition->first_token, p_selection_statement->condition->last_token); - + token_list_insert_before(&ctx->ast.token_list, p_selection_statement->first_token, &condition_tokens_cut); token_list_paste_string_before(&ctx->ast.token_list, p_selection_statement->first_token, ";"); - + token_list_paste_string_before(&ctx->ast.token_list, p_selection_statement->close_parentesis_token, p_selection_statement->condition->p_init_declarator->p_declarator->name->lexeme ); - + } - token_list_paste_string_after(&ctx->ast.token_list, p_selection_statement->last_token, "}"); + token_list_paste_string_after(&ctx->ast.token_list, p_selection_statement->last_token, "}"); token_list_destroy(&condition_tokens_cut); token_list_destroy(&init_tokens_cut); } @@ -31708,14 +31708,17 @@ static void visit_declarator(struct visit_ctx* ctx, struct declarator* p_declara static void visit_init_declarator(struct visit_ctx* ctx, struct init_declarator* p_init_declarator) { visit_declarator(ctx, p_init_declarator->p_declarator); - visit_initializer(ctx, p_init_declarator->initializer); + visit_initializer(ctx, p_init_declarator->initializer); } static void visit_condition(struct visit_ctx* ctx, struct condition* p_condition) { if (p_condition->p_declaration_specifiers) - visit_declaration_specifiers(ctx, p_condition->p_declaration_specifiers, NULL); + { + visit_declaration_specifiers(ctx, + p_condition->p_declaration_specifiers, + &p_condition->p_init_declarator->p_declarator->type); + } - if (p_condition->p_init_declarator) visit_init_declarator(ctx, p_condition->p_init_declarator); @@ -31817,8 +31820,7 @@ static void visit_bracket_initializer_list(struct visit_ctx* ctx, struct braced_ } static void visit_designation(struct visit_ctx* ctx, struct designation* p_designation) -{ -} +{} static void visit_initializer(struct visit_ctx* ctx, struct initializer* p_initializer) { @@ -33138,8 +33140,7 @@ static void visit_enum_specifier(struct visit_ctx* ctx, struct enum_specifier* p } static void visit_typeof_specifier(struct visit_ctx* ctx, struct typeof_specifier* p_typeof_specifier) -{ -} +{} static void visit_type_specifier(struct visit_ctx* ctx, struct type_specifier* p_type_specifier) { diff --git a/src/web/ownership.html b/src/web/ownership.html index 3701bc1e..2ecdf1f7 100644 --- a/src/web/ownership.html +++ b/src/web/ownership.html @@ -19,38 +19,41 @@

Cake - C23 and Beyond

Abstract
  • -Owner Objects +Concepts
  • -Static analysis - Checking the rules at compile time +Flow analysis
  • -Ownership Feature Strategy (Inspired by stdbool.h) +Ownership Feature Strategy (Inspired by stdbool.h)
  • -

    Last Updated 27/02/2024

    +

    Last Updated 22/03/2024

    This is a work in progress, both design and implementation. Cake source itself is being used to validate the concepts.

    @@ -68,13 +71,15 @@

    Abstract

    A human factor must be considered to ensure that annotations do not make the work too boring with excessive details. In this regard, selecting defaults that cover the most common cases is crucial.

    -

    Owner Objects

    +

    Concepts

    + +

    Owner Objects

    An owner object is an object referencing another object and managing its lifetime.

    -

    The most common type of owner objects are pointers, often referred as owner pointers. An owner pointer is created with the qualifier owner, as illustrated in Listing 1:

    +

    The most common type of owner objects are pointers, often referred as owner pointers. An owner pointer is created with the qualifier owner.

    -

    Listing 1 - Owner Pointer to FILE

    +

    Sample - Owner Pointer to FILE

    #include <ownership.h>
     #include <stdio.h>
    @@ -88,23 +93,18 @@ 

    Owner Objects

    -

    owner is actually a macro declared in ownership as _Owner.

    +

    Note: owner is actually a macro declared in ownership as _Owner.

    The ownership mechanism has some rules that will be listed gradually throughout the text.

    -

    Rule: An owner object is always the unique owner of the referenced object.

    - -

    Rule: When owner objects are copied the ownership is transfered.

    +

    Rule: An owner object is always the unique owner of the referenced object.

    -

    Rule: Before the end of its lifetime, owner objects must move the ownership of the objects they own. -(Cake ownership model does not have the concept of destroyed/deleted, - instead everything is a transformation and ownership is moved during these - transformations.)

    +

    Rule: When owner objects are copied the ownership is transfered.

    -

    For example, in Listing 2, the ownership of the owner pointer f is transferred to f2:

    +

    Rule: Before the end of its lifetime, owner objects must move the ownership of the objects they own.

    -

    Listing 2 - Assignment of Owner Objects is a Move

    +

    Sample

    #include <ownership.h>
     #include <stdio.h>
    @@ -118,18 +118,22 @@ 

    Owner Objects

    }
    -

    Invoking a function fclose is analogous to assignment of the argument f2, resulting in the transfer of ownership of f2 to the function parameter. Listing 3, shows the declaration of fclose.

    +

    Invoking a function fclose is analogous to assignment of the argument f2, resulting in the transfer of ownership of f2 to the function parameter.

    -

    Listing 3 - Declaration of close

    +

    Sample - Declaration of fclose

    void fclose(FILE *owner p);
     
    -

    Non-pointer owner objects

    +
    +

    Note: The cake ownership model does not include the concept of a destroyed or deleted object. Instead, everything is viewed as a transformation, where the object is broken into smaller parts and those parts are moved.

    +
    + +

    Non-pointer owner objects

    -

    We can have other types of owner objects. For instance, Berkeley sockets use an integer to identify the socket, as shown in listing 4:

    +

    We can have other types of owner objects. For instance, Berkeley sockets use an integer to identify the socket.

    -

    Listing 4 - Non-Pointer owners objects

    +

    Sample

     owner int server_socket =
          socket(AF_INET, SOCK_STREAM, 0);
    @@ -137,19 +141,46 @@ 

    Non-pointer owner objects

    close(server_socket);
    -

    The location and usage of qualifier owner is similar of const qualifier. For pointers it goes after *, for this socket sample it can be before int.

    +
    +

    Note: The location and usage of the qualifier owner is similar to the const qualifier. For pointers, it goes after *, and for this socket sample, it can be before int. The owner qualifier belongs to the object (memory) that holds the reference.

    +
    + +

    When a struct or union have at least one owner object it makes the struct a owner object too.

    + +

    Rule: Owner objects cannot be discarded.

    + +
    #include <ownership.h> 
    +#include <stdio.h>
    +
    +int main() {  
    +  fopen("file.txt", "r"); //warning   
    +}
    +
    + +

    Rule: A non-owner object cannot be copied to a owner object.

    + +

    Rule: The null pointer constant can be used to initialize owner objects. (Even it its type is non-owner)

    + +

    Sample

    + +
    FILE * f(); //returning non owner
    +int main() {  
    +   FILE * owner file = f(); //ERROR   
    +   FILE * owner file2 = 0;  //OK
    +}
    +
    -

    View Objects

    +

    View Objects

    A view object is an object referencing another object without managing its lifetime.

    -

    Rule The lifetime of the referenced object must be bigger than the lifetime of the view object.

    +

    Rule: The lifetime of the referenced object must be longer than the lifetime of the view object.

    The most common view objects are pointers called view pointers.

    -

    The view qualifier is not necessary for pointers, since it's the default behavior. When an owner object is copied to a view object, the ownership is not transferred, as shown in Listing 5:

    +

    The view qualifier is not necessary for pointers, since it's the default behavior. When an owner object is copied to a view object, the ownership is not transferred.

    -

    Listing 5 - Calling Function with View Parameters

    +

    Sample

    #include <ownership.h>
     #include <stdio.h>
    @@ -165,11 +196,9 @@ 

    View Objects

    }
    -

    When a struct or union have at least one owner object it makes the struct a owner object too.

    - -

    When a view qualifier is used in structs, it makes all members as view objects. Listing 6.

    +

    When a view qualifier is used in structs, it makes all members as view objects.

    -

    Listing 6 - A view parameter

    +

    Sample - A view parameter

    #include <ownership.h>
     #include <stdlib.h>
    @@ -187,47 +216,40 @@ 

    View Objects

    }
    -

    It is interesting to compare against const qualifier. -While const adds a qualifier "const" "view" removes the qualifier "owner".

    +
    +

    Note: It is interesting to compare against const qualifier. While const adds a qualifier "const" "view" removes the qualifier "owner".

    +
    -

    Returning view pointers.

    +

    Returning a pointer to a view object

    -

    We cannot return local variables as view pointers.

    +

    We can check the rule "The lifetime of the referenced object must be longer than the lifetime of the view object" with these constrains.

    + +

    We cannot return the address of local variables

    int * f()
     {
        int a = 1;
    -   return &a;
    -}
    -int main()
    -{
    -  int * p = f();   
    -  //warning: function returns address of 
    -  //local variable [-Wreturn-local-addr]
    +   return &a; //ERROR
     }
     
    -

    But we can return static variables and function arguments.

    +

    We can return the address of global variables

    static int a = 1;
    -
     int * f()
     {   
    -   return &a;
    -}
    -int * f2(int *p) {
    -   return p;
    -}
    +   return &a; // OK
    +}  
    +
    -int main() -{ - int * p = f(); - int b = 1; - p = f2(&b); +

    And we can return parameters

    + +
    int * f2(int *p) {
    +   return p; //OK
     }
     
    -

    Another sample:

    +

    Now consider:

    #include <stdio.h>
     
    @@ -246,10 +268,17 @@ 

    Returning view pointers.

    }
    -

    Examining the implementation reveals that the returned view pointer's lifetime can be that of either 'a' or 'b'. Our goal is to set contracts at the declaration level. Following the concept of ensuring safety by default, we assume that the returned view pointers have the shortest scope, limited to the function call.
    -(Cake is not doing this check at this moment)

    +

    Examining the implementation reveals that the returned view pointer's lifetime can be that of either 'a' or 'b'.

    + +

    Our goal is to set contracts at the declaration level.

    + +

    Following the concept of ensuring safety by default, we assume that the returned view pointers have the shortest scope, limited to the function call.

    + +
    +

    Note : Currently, this check is missing at cake

    +
    -

    View pointer as struct members

    +

    View pointer as struct members

    Consider this sample.

    @@ -261,14 +290,17 @@

    View pointer as struct members

    }; -

    An object Y pointed by pY must live longer than object X.
    -This check must be done at instantiation.

    +

    The rule "The lifetime of the referenced object must be longer than the lifetime of the view object" needs to be checked at each instantiation.

    -

    Deleting Owner Pointers

    +
    +

    Note : Currently, this check is missing at cake

    +
    + +

    Deleting Owner Pointers

    -

    Owner pointers take on the responsibility of owning the pointed object and its associated memory, treating them as distinct entities. A common practice is to implement a delete function to release both resources, as illustrated in Listing 7:

    +

    Owner pointers take on the responsibility of owning the pointed object and its associated memory, treating them as distinct entities. A common practice is to implement a delete function to release both resources.

    -

    Listing 7 - Implementing the delete function

    +

    Sample - Implementing the delete function

    #include <ownership.h>
     #include <stdlib.h>
    @@ -288,7 +320,7 @@ 

    Deleting Owner Pointers

    } int main() { - struct X * owner pX = calloc( 1, sizeof * pX); + struct X * owner pX = calloc(1, sizeof * pX); if (pX) { /*...*/; x_delete( pX); @@ -296,9 +328,9 @@

    Deleting Owner Pointers

    }
    -

    When the object is created on the stack, we can implement a destructor, as shown in Listing 8:

    +

    When the object is created on the stack, we can implement a destructor.

    -

    Listing 8 - Implementing a destructor

    +

    Sample - Implementing a destructor

    #include <ownership.h>
     #include <stdlib.h>
    @@ -322,12 +354,11 @@ 

    Deleting Owner Pointers

    A pointer qualified with obj_owner is the owner of the pointed object but not responsible for managing its memory.

    -

    Listing 9 illustrates how to implement a destructor using a obj_owner pointer parameter.

    +

    The next sample illustrates how to implement a destructor using a obj_owner pointer parameter.

    -

    Listing 9 - Implementing a destructor using obj_owner

    +

    Sample - Implementing a destructor using obj_owner

    -
    struct X {
    -#include <ownership.h>
    +
    #include <ownership.h>
     #include <stdlib.h>
     
     struct X {
    @@ -346,9 +377,9 @@ 

    Deleting Owner Pointers

    }
    -

    In order to prevent moving from a non owner object, only address of expressions to obj_owner are allowed. For instance, listing 10 shows we cannot move a view pointer.

    +

    In order to prevent moving from a non owner object, only address of expressions to obj_owner are allowed.

    -

    Listing 10 - Non address of expression or owner pointer.

    +

    Sample - Non address of expression or owner pointer.

    #include <ownership.h>
     #include <stdlib.h>
    @@ -366,9 +397,9 @@ 

    Deleting Owner Pointers

    }
    -

    We can copy an owner pointer to an obj_owner pointer. In this scenario, only the ownership of the pointed object is transferred, not the memory ownership. Listing 11 illustrates how we can use x_destroy in the implementation of x_delete.

    +

    We can copy an owner pointer to an obj_owner pointer. In this scenario, only the ownership of the pointed object is transferred, not the memory ownership.

    -

    Listing 11 - Using x_destroy to implement x_delete

    +

    Sample - Using x_destroy to implement x_delete

    #include <ownership.h>
     #include <stdlib.h>
    @@ -389,7 +420,7 @@ 

    Deleting Owner Pointers

    } int main() { - struct X * owner pX = calloc( 1, sizeof * pX); + struct X * owner pX = calloc(1, sizeof * pX); if (pX) { /*...*/; x_delete( pX); @@ -409,7 +440,7 @@

    Deleting Owner Pointers

    But I think this is quite uncommon.

    -

    Static analysis - Checking the rules at compile time

    +

    Flow analysis

    Let's revisit our first example:

    @@ -420,13 +451,15 @@

    Static analysis - Checking the rules at compile time

    { FILE *owner f = fopen("file.txt", "r"); if (f) - fclose(f); + fclose(f); //f is moved }
    -

    The flow analysis must ensure that when the owner pointer f goes out of scope, it does not own any objects. At the end of the scope, f can be either null or moved, and both states ensure that there are no remaining resources.

    +

    "Rule: Before the end of its lifetime, owner objects must move the ownership of the objects they own."

    + +

    At the end of the scope, f can be either null or moved, and both states ensure that the rule is followed.

    -

    To check the ownership rules, the compiler uses six states:

    +

    To check the ownership rules, the compiler need flow analysis and it uses six states:

    • uninitialized
    • @@ -437,14 +470,14 @@

      Static analysis - Checking the rules at compile time

    • not-zero
    -

    We can print these states using the static_debug declaration. We can also assert the variable is at a certain state using the static_state declaration. Listing 12 shows this usage:

    +

    We can print these states using the static_debug declaration. We can also assert the variable is at a certain state using the static_state declaration.

    -

    Listing 12 - Usage of static_state and static_debug

    +

    Sample - Usage of static_state and static_debug

    int main() {
      int a;   
    - static_state(a, "uninitialized");  
    - static_debug(a);
    + static_state(a, "uninitialized"); //checks a state  
    + static_debug(a);                  //prints 'a' state 
     }
     
    @@ -456,101 +489,60 @@

    Static analysis - Checking the rules at compile time

    a == "uninitialized"
    -

    As we have just seen, the uninitialized state is the state of variables that are declared but not initialized.

    +

    Uninitialized state

    -

    The compiler ensures that we don't read uninitialized objects.

    +

    The uninitialized state is the state of variables that are declared but not initialized.

    -
    void f1(int i);
    -int main() {
    +

    Flow analysis must ensure that we don't read uninitialized objects.

    + +
    void f(int condition) {
        int i;
    -   f1(i); //error: uninitialized object 'i'
    +   if (condition) 
    +    i = 0;
    +   printf("%d", i); //warning i may be uninitialized
     }
     
    -

    The other situation were variables becomes uninitialized is when moving ownership to function parameters. This prevents bugs like double free or use after free.

    - -
    #include <ownership.h> 
    -#include <stdlib.h>
    -
    -struct X {
    -  char * owner text;
    -};
    +

    The other situation were variables becomes uninitialized is when moving ownership to function parameters.

    -void x_delete(struct X * owner p) -{ - if (p) { - free(p->text); - free(p); - } -} +

    This prevents bugs like double free or use after free.

    -void f(struct X * p){} +
    int * _Owner f();
    +void free(void * _Owner _Opt p);
     
     int main() {   
    -   struct X * owner p = malloc(sizeof(struct X));
    -   p->text = malloc(10);
    -   x_delete(p);
    -   f(p); //uninitialized object 'p'
    +   int * _Owner p = f();
    +   free(p);
    +   free(p); //warning p is uninitialized
     }
     
    -

    When objects are moved within a local scope, the state is "moved" rather than "uninitialized." The "moved" state is similar to the "uninitialized" state. For instance, it's not possible to move an object that has already been moved.

    +

    Moved state

    -
    #include <ownership.h> 
    -#include <stdlib.h>
    +

    The moved state is similar to the uninitialized state. The difference is that the moved state is used when moving local variables. This information could be useful in the future to be less restrictive than uninitialized.

    -struct X { - char * owner text; -}; - -void x_delete(struct X * owner p) -{ - if (p) - { - free(p->text); - free(p); - } -} +

    Sample - local scope moves

    -void f(struct X * p){} +
    int * _Owner f();
    +void free(void * _Owner _Opt p);
     
     int main() {   
    -   struct X * owner p = malloc(sizeof(struct X));
    -   p->text = malloc(10);
    -  
    -   struct X * owner p2 = 0;
    -   p2 = p; //MOVED
    +   int * _Owner p = f();
    +   int * _Owner p2 = 0;
    +   p2 = p; // p moved to p2  
       
    -   f(p); //error: object 'p' was moved
    -   x_delete(p2);
    -}
    -
    -
    + //compiler knows that *p still valid -

    The "moved" state was introduced instead of solely relying on the "uninitialized" state because static analysis benefits from having more information on local variables. "Moved" objects may, in some cases, serve as view objects. For example, in listing XX, the x object has been moved to x2, but it is safe to use x as "view" object even after the move.

    - -
    #include <ownership.h> 
    -#include <stdlib.h>
    -
    -struct X {
    -  char * owner text;
    -};
    -
    -int main() {   
    -  struct X x = {0};
    -  //...
    -  struct X x2 = {0};  
    -  x2 = x; //MOVED
    -  free(x2.text);
    +   free(p); //warning p was moved
    +   free(p2); //ok
     }
     
    -
    -

    Note: The current implementation of cake does not handle all necessary -states to ensure the safe usage of moved objects.

    -
    +

    _Out qualifier

    -

    A common scenario where uninitialized objects are utilized is when a pointer to an uninitialized object is passed to an "init" function. This situation is addressed by the qualifier out.

    +

    A common scenario where uninitialized objects are utilized is when a pointer to an uninitialized object is passed to an "init" function.

    + +

    This situation is addressed by the qualifier _Out/out.

    #include <ownership.h> 
     #include <stdlib.h>
    @@ -560,27 +552,31 @@ 

    Static analysis - Checking the rules at compile time

    char * owner text; }; -int init(out struct X *p, const char * text) +int init(out struct X *p) { - //SAFE - p->text = strdup(text); + p->text = strdup("a"); //safe } int main() { struct X x; - init(&x, "text"); + init(&x); free(x.text); -} - +}
    -

    The "out" qualifier is necessary at the caller side and at the implementation.

    +

    With the out qualifier, caller is informed that the argument must be uninitialized.

    + +

    The implementation is aware that it can safely override the contents of the object p->text without causing a memory leak.

    + +
    +

    Note: There is no explicit "initialized" state. When referring to initialized objects, it means the state is neither "moved" nor "uninitialized.".

    +
    -

    The caller is informed that the argument must be uninitialized, and the implementation is aware that it can safely override the contents of the object p->text = strdup(text); without causing a memory leak.

    +

    Rule: All objects passed as arguments must be initialized and all objects reachable must be initialized.

    -

    There is no explicit "initialized" state. When referring to initialized objects, it means the state is neither "moved" nor "uninitialized."

    +

    Rule: By default, the parameters of a function are considered initialized. The exception is created with out qualifier.

    -

    Rule By default, the parameters of a function are considered initialized. The exception is created with out qualifier.

    +

    Rule: We cannot pass initialized objects, or reachable initialized objects to out qualified object.

    For instance, at set implementation we need free text before assignment.

    @@ -594,13 +590,12 @@

    Static analysis - Checking the rules at compile time

    int init(out struct X *p, const char * text) { - //SAFE - p->text = strdup(text); + p->text = strdup(text); //safe } int set(struct X *p, const char * text) { - free(p->text); //NECESSARY + free(p->text); //necessary p->text = strdup(text); } @@ -612,13 +607,78 @@

    Static analysis - Checking the rules at compile time

    }
    -

    Rule All objects passed as arguments must be initialized. The exception is when object is out qualified.

    +

    Rule: Function never returns uninitialized objects or reachable uninitialized objects.

    -

    Rule: We cannot pass initialized objects to out qualified object.

    +

    TODO void objects.

    -

    The null state means that owner objects are initialized and not referencing any object. Listing 13 shows a sample using owner pointers:

    +

    Rule: Non owner objects accessible with parameters cannot leave scope with uninitialized/moved objects.

    -

    Listing 13 - Null state

    +
    #include <ownership.h> 
    +#include <string.h>
    +#include <stdlib.h>
    +
    +struct X {
    +  char * owner name;
    +};
    +
    +void x_destroy(struct X * obj_owner p) {
    +  free(p->name); 
    +}
    +
    +struct Y {
    +   struct X x;
    +}
    +
    +void f(struct Y * p) {   
    +   x_destroy(&p->x); //breaking the rule
    +}
    +  
    +int main() {
    +   struct Y  y = {};
    +   y.x.name = strdup("a");
    +   f(&y);
    +   free(y.x.name);
    +}  
    +
    +
    + +

    Sample of swap if fine because at end of scopes objects are not uninitialized/moved.

    + +
    #include <ownership.h> 
    +#include <stdlib.h>
    +
    +struct X
    +{
    +  char * owner name;
    +};
    +
    +void x_destroy(struct X * obj_owner p)
    +{
    +  free(p->name); 
    +}
    +
    +void x_swap(struct X * a, struct X * b) {
    +  struct X temp = *a;
    +  *a = *b;
    +  *b = temp;
    +} //ok
    +  
    +int main() {
    +   struct X x1 = {};
    +   struct X x2 = {};
    +  
    +   x_swap(&x1, &x2);
    +  
    +   x_destroy(&x1);
    +   x_destroy(&x2);
    +}
    +
    + +

    Null and Not-Null state

    + +

    The null state means that pointers/objects are initialized and not referencing any object.

    + +

    Sample - Null state

    #include <ownership.h> 
     
    @@ -628,11 +688,25 @@ 

    Static analysis - Checking the rules at compile time

    }
    -

    The not-null state indicates that the object is referencing some object. -The state can be a combination of possibilities like null and not-null. We can check possible combination using "or". -This particular combination null or not-null has a alias maybe-null as shown in listing 14.

    +

    Rule: Before assignment, owner objects, must not be holding objects. The state must be null or uninitialized/moved.

    -

    Listing 14 - not-null and maybe-null state

    +

    Sample

    + +
    #include <ownership.h> 
    +#include <stdio.h>
    +int main() {
    +  FILE * owner file = fopen("file.txt", "r");
    +  file = fopen("file.txt", "r"); //warning
    +}
    +
    + +

    The not-null state indicates that the object is initialized and not referencing any object.

    + +

    The final state is combination of possibilities like null and not-null. We can check possible combination using "or" at static_state.

    + +

    The combination null or not-null has a alias maybe-null.

    + +

    Sample - not-null and maybe-null

    #include <ownership.h> 
     #include <stdlib.h>
    @@ -649,15 +723,67 @@ 

    Static analysis - Checking the rules at compile time

    }
    -

    The zero state is used for non-owner objects to complement and support uninitialized checks.

    +

    Rule: Pointer parameters are consider not-null by default. The exception is created using the qualifier _Opt.

    + +

    The _Opt. qualifier is used to tell the caller that the pointer can be at null state and tells the implementation that it is necessary to check the pointer for null before usage.

    + +

    Rule: Pointers that can be null cannot be passed to functions that don't declare the pointer as opt.

    + +
    #include <ownership.h> 
    +#include <stdlib.h>
    +
    +struct X {int i;};
    +
    +void f(struct X * p){
    +}
    +
    +int main()
    +{
    +   struct X * owner p = calloc(1, sizeof * p);
    +   f(p); //warning pointer can be null
    +   free(p);
    +}
    +
    + +
    +

    Note: To enable null checks in cake use -nullchecks

    +
    + +

    This sample can be fixed in two ways.

    -

    Rule Pointer parameters are consider not-null by default. The exception is created using the qualifier _Opt.

    +
    /*...*/
    +void f(struct X * p){}
    +int main()
    +{
    +   struct X * owner p = calloc(1, sizeof * p);
    +   if (p)   
    +     f(p); //ok
    +   free(p);
    +}
    +
    + +

    or

    -

    To tell the compiler that the pointer can be null, we use the qualifier _Opt.

    +
    /*...*/
    +void f(struct X * opt p){ }
    +int main()
    +{
    +   struct X * owner p = calloc(1, sizeof * p);
    +   f(p); //ok
    +   free(p);
    +}
    +
    + +

    Considering these semantics, the correct declaration of free is:

    -

    (Currently Cake is only doing null-checks if the -nullchecks option is passed to the compiler, the cake source itself has to move to nullchecks)

    +
    void free(void * _Owner _Opt p); //p can be null 
    +
    -

    Listing 15 - The zero state

    +

    Zero and Not-Zero state

    + +

    The zero state is used for non-owner objects to complement and support uninitialized checks.

    + +

    Sample - The zero state

    int main()
     {
    @@ -666,9 +792,13 @@ 

    Static analysis - Checking the rules at compile time

    }
    -

    Zero and null are different states. This difference is necessary because, for non-pointers like the socket sample, 0 does not necessarily means null. The compiler does not know the semantics for types that are not pointers. However, you can use static_set to override states. In Listing 16, we annotate that server_socket is null, which doesn't mean it is zero but indicates that it is not holding any resources and is safe to return without calling close.

    +

    Zero and null are different states. This difference is necessary because, for non-pointers like the socket sample, 0 does not necessarily means null. The compiler does not know the semantics for types that are not pointers.

    + +

    static_set

    -

    Listing 16 - Usage of static_set

    +

    We can use static_set to override states. In the next sample, we annotate that server_socket is null, which doesn't mean it is zero but indicates that it is not holding any resources and is safe to return without calling close.

    + +

    Sample - Usage of static_set

      owner int server_socket =
          socket(AF_INET, SOCK_STREAM, 0);
    @@ -682,7 +812,7 @@ 

    Static analysis - Checking the rules at compile time

    The not-zero state is used for non-owner objects to indicate the value if not zero.

    -

    Similarly of maybe-null, any is a alias for zero or not-zero state.

    +

    any is a alias for zero or not-zero state.

    int f();
     
    @@ -690,23 +820,16 @@ 

    Static analysis - Checking the rules at compile time

    int i = f(); static_state(i, "any"); } -
    -

    By the way, the result of functions are never uninitialized by convention.

    - -

    By default, pointers have the state maybe-null and other types have the state any.

    - -

    Rule: Function never returns uninitialized objects.

    -

    Now let's consider realloc function.

    void * owner realloc( void *ptr, size_t new_size ); 
     
    -

    In the declaration of realloc, we are not moving the ptr. The reason for that is because the ptr may or may not be moved. If the function returns NULL, ptr was not moved. Listing 17 shows how static_set can be used.

    +

    In the declaration of realloc, we are not moving the ptr. The reason for that is because the ptr may or may not be moved. If the function returns NULL, ptr was not moved.

    -

    Listing 17 - Using static_set with realloc

    +

    Sample - Using static_set with realloc

    #include <ownership.h> 
     #include <stdlib.h>
    @@ -724,121 +847,9 @@ 

    Static analysis - Checking the rules at compile time

    }
    -

    Without the static_set we have this error

    - -
    error: memory pointed by 'p' was not released before assignment.
    - 11 |     p = p2;
    -
    - -

    The state of an object is a combination of all possible states. For instance, let's print and check the state of f at listing 17.

    - -

    Listing 17 - Flow analysis

    - -
    #include <ownership.h> 
    -#include <stdlib.h>
    -#include <stdio.h>
    -
    -int main() {
    -  FILE *owner f = fopen("file.txt", "r");
    -  if (f)
    -    fclose(f);  
    -  static_state(f, "uninitialized or null");  
    -}
    -
    - -

    When objects are moved to functions, the state is uninitialized that is the worst scenario of what can happens inside the function. When objects are moved inside the same source the state is moved.

    - -
    #include <ownership.h> 
    -#include <stdlib.h>
    -#include <stdio.h>
    -
    -int main() {
    -  FILE *owner f = fopen("file.txt", "r");  
    -  FILE *owner f2 = f; 
    -  static_state(f, "moved");  
    -  fclose(f2);
    -}  
    -
    - -

    Rule: We cannot discard owner objects as showed in listing 18.

    - -

    Listing 18 - owner objects cannot be discarded.

    - -
    #include <ownership.h> 
    -#include <stdio.h>
    -
    -int main() {
    -  //error: ignoring the result of owner type
    -  fopen("file.txt", "r");   
    -}
    -
    - -

    Rule: Before the assignment of owner objects, the compiler checks if the owner object is not holding any resource, as shown in Listing 19:

    - -

    Listing 19 - Check before assignment

    - -
    #include <ownership.h> 
    -#include <stdio.h>
    -
    -int main() {
    -  FILE * owner file = fopen("file.txt", "r");
    -  
    -  //error: memory pointed by 'file' was not 
    -  //released before assignment.  
    -  file = fopen("file.txt", "r");
    -}
    -
    - -

    Function Parameters

    - -

    For function parameters, the state of the object is initialized by default.

    - -

    Listing 20 - States of function parameters

    - -
    #include <ownership.h> 
    -#include <string.h>
    -
    -struct X {
    -  char *owner text; 
    -};
    -
    -void set(struct  X * x, const char * name) {
    -  free(x->text);
    -  x->text = strdup(name);
    -}
    -
    -
    - -

    When the argument can be uninitialized we use the qualifier _Out.

    - -
    #include <ownership.h> 
    -#include <string.h>
    -
    -struct X {
    -  char *owner text; 
    -};
    -
    -void init(out struct X * x) {
    -  x->text = strdup("a");
    -}
    -
    -void set(struct  X * x, const char * name) {
    -  free(x->text);
    -  x->text = strdup(name);
    -}
    -
    -
    - -

    After calling init the object may or not be initialized.

    - -

    At listing 21, we show how static_set can be used to set states.

    - -

    Listing 21 - Using static_set

    +

    assert is a statement

    -
    TODO
    -
    - -

    But when possible we can use assert that works both as static information and runtime check in debug.

    +

    When possible we can use assert that works both as static information and runtime check in debug.

    Consider the following sample where we have a linked list. Each node has owner pointer to next. The next pointer of the tail of the list is always pointing to null, unless we have a bug. But the compiler does not know list->tail->next is null. Using assert we give this inform to the compiler and we also have a runtime check for possible logic bugs.

    @@ -872,220 +883,7 @@

    Function Parameters

    }
    -

    Rule: A non-owner object cannot be copied to a owner object.

    - -

    But, the null pointer constant is converted to a null owner pointer. Se listing 23.

    - -

    Listing 23 - non owner cannot be copied to owner

    - -
    FILE * f();
    -int main() {  
    -   FILE * owner file = f(); //ERROR   
    -   FILE * owner file2 = 0;  //OK
    -}
    -
    - -

    Rule: A view pointer parameter cannot leave the scope with moved/uninitialized objects. Listing 24

    - -

    Listing 24 - Messing with view parameters

    - -
    
    -#include <ownership.h> 
    -#include <string.h>
    -#include <stdlib.h>
    -
    -struct X
    -{
    -  char * owner name;
    -};
    -
    -void x_destroy(struct X * obj_owner p)
    -{
    -  free(p->name); 
    -}
    -
    -struct Y
    -{
    -   struct X x;
    -}
    -
    -void f(struct Y * p) {
    -   // parameter 'p' is leaving scoped with a uninitialized
    -   // object 'p.x.name'
    -   x_destroy(&p->x);
    -}
    -  
    -int main() {
    -   struct Y  y = {};
    -   y.x.name = strdup("a");
    -   f(&y);
    -   free(y.x.name);
    -}
    -
    - -

    However, listing 25 is correct, because before the end of scope states of the parameters are restored.

    - -

    Listing 25 - swap function

    - -
    #include <ownership.h> 
    -#include <stdlib.h>
    -
    -struct X
    -{
    -  char * owner name;
    -};
    -
    -void x_destroy(struct X * obj_owner p)
    -{
    -  free(p->name); 
    -}
    -
    -void x_swap(struct X * a, struct X * b) {
    -  struct X temp = *a;
    -  *a = *b;
    -  *b = temp;
    -}
    -  
    -int main() {
    -   struct X x1 = {};
    -   struct X x2 = {};
    -  
    -   x_swap(&x1, &x2);
    -  
    -   x_destroy(&x1);
    -   x_destroy(&x2);
    -}
    -
    - -

    Rule: When objects are moved to functions, they become uninitialized. This prevents bugs like double free. Listing 26.

    - -
     int main() {
    -  void * owner p = malloc(1);
    -  free(p);
    -  free(p); //ERROR p is uninitialized
    -}
    -
    - -

    Rule: moved objects cannot be moved. Listing 27.

    - -

    Listing 27 - We cannot move a moved or uninitialized object

    - -
    int * owner p1 = ...;
    -int * owner p2 = p1;
    -int * owner p3 = p1; //ERROR p1 was moved
    -
    - -
    int * owner f(int * owner p1) {
    - int * owner p2 = p1;
    - return p1; //ERROR p1 was moved
    -}
    -
    - -

    Rule: When coping a owner object to to a view object the compiler must check the lifetime. Listing 28

    - -

    Listing 28 - Lifetime check

    - -
    void using_file(FILE * f);
    -
    -struct X { 
    -  char *owner text; 
    -};  
    -  
    -struct X * owner make_owner();  
    -
    -int main() {
    -  struct X *p = 0;  
    -  {
    -    struct X * owner p2 = make_owner();  
    -    p = p2; //error p lives longer than p2  
    -    free(p2);
    -  }
    -
    -  using_file(fopen("file.txt", "r")); //ERROR
    -}
    -
    - -

    Rule: Returned objects must be valid.

    - -

    Listing 34 - Assuming returned objects are valid

    - -
    void* owner malloc(unsigned long size);
    -void free(void* owner ptr);
    -
    -struct X { 
    -  char *owner text; 
    -};
    -
    -struct X * owner f();
    -
    -int main()
    -{
    -   struct X * owner pX = f();
    -   if (pX) {
    -     static_state(pX->text, "maybe-null");
    -   }
    -}
    -
    - -

    Rule: Arguments must be valid.

    - -

    Exception

    - -

    malloc and calloc have built in semantics.

    - -

    Listing 35 - Function Arguments cannot be in a moved or uninitialized state

    - -
    #include <ownership.h> 
    -#include <stdlib.h>
    -struct X { 
    -  char *owner text; 
    -};
    -
    -void f(struct X * p);
    -
    -int main() {
    -   struct X x;
    -   f(&x); //ERROR
    -     
    -   struct X x1 = {0};
    -   struct X x2 = x1; //MOVED
    -   f(&x1); //ERROR
    -}
    -
    - -

    For this reason, an qualifier out can be added to allow passing unitalicized objects.

    - -
    void  free(void* _Owner p);
    -char* _Owner strdup(const char* s);
    -
    -struct X {
    -    char* _Owner s;
    -};
    -
    -void init(_Out struct X *  px)
    -{
    -    static_state(px, "maybe-null");
    -    static_state(px->s, "uninitialized");
    -    px->s = strdup("a");
    -}
    -
    -void set(struct X* px, const char* text)
    -{
    -    static_state(px, "maybe-null");
    -    static_state(px->s, "maybe-null");
    -    free(px->s);
    -    px->s = strdup(text);
    -}
    -
    -int main() {
    -    struct X x;
    -    init(&x);
    -    set(&x, "b");
    -    free(x.s);
    -}
    -
    - -

    Ownership Feature Strategy (Inspired by stdbool.h)

    +

    Ownership Feature Strategy (Inspired by stdbool.h)

    If the compiler supports ownership checks and qualifiers such as _Owner, _View, _Obj_view, etc., it must define __STDC_OWNERSHIP__.

    @@ -1108,4 +906,8 @@

    Ownership Feature Strategy (Inspired by stdbool.h)

    When we parsing malloc from MSVC/GCC we ignore the diferences, but at the current version ownership.h header must be included before to take efect.

    + +

    The standard also could make out, opt, view, owner, obj_owner as reserved keyword for the future.

    + +

    _

    \ No newline at end of file