Live data from Hacker News

Cake – C23 and Beyond (2023)

thradams.com

71–80 of 128 posts

Re: Cake – C23 and Beyond (2023)

#71
post #47

Agreed this is awesome, obviously sanitizers fill some of this gap currently but they aren't great with things like reference counting that RAII makes a doddle. Fwiw, here is an implementation of a runtime RAII style checking on top of leak sanitizer: https://perf.wiki.kernel.org/index.php/Reference_Count_Check... There's an interesting overlap with the cleanup attribute that is now appearing in the Linux kernel (by…

Cake implements defer as an extension, where ownership and defer work together. The flow analysis must be prepared for defer.

    int * owner p = calloc(1, sizeof(int));
    defer free(p);

However, with ownership checks, the code is already safe. This may also change the programmer's style, as generally, C code avoids returns in the middle of the code.

In this scenario, defer makes the code more declarative and saves some lines of code. It can be particularly useful when the compiler supports defer but not ownership.

One difference between defer and ownership checks, in terms of safety, is that the compiler will not prompt you to create the defer. But, with ownership checks, the compiler will require an owner object to hold the result of malloc, for instance. It cannot be ignored.

The same happens with C++ RAII. If you forgot to free something at our destructor or forgot to create the destructor, the compiler will not complain.

In cake ownership this cannot be ignored.

    struct X {
      FILE * owner file;
    };

    int main(){
       struct X x = {};
       //....
       
    } //error x.file not freed

Re: Cake – C23 and Beyond (2023)

#72

Earlier quoted context omitted.

In cake object and memory are two resources. We can for instance, delete the object and reuse the same memory. For instance, this code is correct. #include #include struct X { char * owner text; }; void x_delete(struct X * owner p) { if (p) { free(p->text); free(p); } } int main() { struct X * owner p = malloc(sizeof(struct X)); p->text = malloc(10); free(p->text); //object text destroyed struct X x2 = {0}; *p = x2;…

let p: Box = Box::new(X { ... }); let x2 = X { ... }; // Moves x2 into the same memory as the first X. // The first X is automatically dropped as part of this assignment. // Also consumes x2 so x2 is not available any more. *p = x2; // Drops the X that was originally assigned to x2 and then moved into p. drop(p); // No need, nor is it possible, to destroy x2.

Thanks for the rust sample. It looks very similar. Can the allocator be customized?

As I said I am not Rust specialist.

Also, in my understanding is that in Rust, sometimes a dynamic state is created when the object may or may not be moved.

In cake ownership this needs to me explicit ( and the destructor is not generated)

I also had a look at Rust in lifetime annotations. This concept may be necessary but I am avoiding it.

Consider this sample.

   struct X {  
     struct Y * pY;  
   };  
   struct Y {  
     char * owner name;  
   };  
An object Y pointed by pY must live longer than object X. (Cake is not checking this scenario yet)

Also (classic Rust sample)

    int * max(int * p1, int * p2) {  
      return *p1 > *p2 ? p1 : p2;
    }

    int main(){  
       int * p = NULL;
       int a  = 1;
      {
         int b = 2;
         p = max(&a,  &b);
      }
      printf("%d", *p);
    }
This is not implemented yet but I want to make the lifetime of p be the smallest scope. (this is to avoid lifetime annotations)

   int * p = NULL;
   int a  = 1;
   {
      int b = 2;
      p = max(&a,  &b);
   } //p cannot be used beyond this point*

Re: Cake – C23 and Beyond (2023)

#73
post #66

Earlier quoted context omitted.

I think it's a common misconception that ownership is there to make you suffer compiler shenanigans. When in my experience it changes the way you model programs. Turns out, that structuring your program in a way were it's clear who owns what makes for easier to understand and debug programs. It's a bit analogous to static typing, saying I'll use a language like Python without type hints, because its gonna make me avo…

> I think it's a common misconception that ownership is there to make you suffer compiler shenanigans. I don't think it's a misconception. When I tried Rust I tried to implement a cyclic data structure but couldn't because there is no clear "owner" in a cyclic data structure. The "safe" solution recommended by the rustaceans was to use integer handles. So, instead of juggling pointers I was juggling integers which ma…

You seem to have a preset opinion, and I'm not sure you are interested in re-evaluating it. So this is not written to change your mind.

I've developed production code in C, C++, Rust, and several other languages. And while like pretty much everything, there are situations where it's not a good fit, I find that the solutions tend to be the most robust and require the least post release debugging in Rust. That's my personal experience. It's not hard data. And yes occasionally it's annoying to please the compiler, and if there were no trait constraints or borrow rules, those instances would be easier. But way more often in my experience the compiler complained because my initial solution had problems I didn't realize before. So for me, these situations have been about going from building it the way I wanted to -> compiler tells me I didn't consider an edge case -> changing the implementation and or design to account for that edge case. Also using one example, where Rust is notoriously hard and or un-ergonomic to use, and dismissing the entire language seems premature to me. For those that insist on learning Rust by implementing a linked list there is https://rust-unofficial.github.io/too-many-lists/.

Re: Cake – C23 and Beyond (2023)

#74

Earlier quoted context omitted.

let p: Box = Box::new(X { ... }); let x2 = X { ... }; // Moves x2 into the same memory as the first X. // The first X is automatically dropped as part of this assignment. // Also consumes x2 so x2 is not available any more. *p = x2; // Drops the X that was originally assigned to x2 and then moved into p. drop(p); // No need, nor is it possible, to destroy x2.

And as bonus there is no temporal hole where you could access a null or dangling text. Here it is as a runnable snippet: https://godbolt.org/z/fc4Gfxrfd

In cake there is no temporal hole, we cannot reuse the deleted object. This prevents double free and use after free.

    int main() {   
       struct X * owner p = malloc(sizeof(struct X));
        
       p->text = malloc(10);

       free(p->text); //object text destroyed

       //p->text is on uninitialized state. 
       //cannot be used (except assignment)

       struct X x2 = {0};

       *p = x2; //x2 MOVED TO *p

       x_delete(p);

Re: Cake – C23 and Beyond (2023)

#75
post #8

Do I need to use the Cake frontend to use the ownership library or is it actually macros (or an extension?) I could use in code compiled with gcc or clang?

The answer you can have the same source code and compile with gcc, but only cake is implementing the checks at this moment. The have the same source code compiling in any compiler a header ownership.h is used to define owner etc as empty macro. This strategy is used on cake source itself, that is checked with cake, but compiled with gcc msvc and clang.

Got it, thank you!

Re: Cake – C23 and Beyond (2023)

#76
post #58

Earlier quoted context omitted.

If you are talking about a very naive version of mempool, then you are correct, but thats why I said a good implementation. The whole point of a good mempool is that you malloc once, and only call free when you exit the program. The data structures for memory allocation will never get corrupted. And the memory pool will never release chunk twice cause it keeps tracks of allocated chunks. User after free is mitigated…

> The whole point of a good mempool is that you malloc once, and only call free when you exit the program So you're describing fork() and _exit(). That's my favorite memory manager. For example, chibicc never calls free() and instead just forks a process for each item of work in the compile pipeline. It makes the codebase infinitely simpler. Rui literally solved memory leaks! No idea what you're talking about.

One issue I see with this approach (compiler leaking memory) is, for instance, if the requirements change and you need to utilize the compiler as a lib or service. For example, if the Cake source is used within a web browser compiled with Emscripten, leaking memory with each compilation would lead to a continuous increase in memory usage.

Additionally, compilers often offer the option to compile multiple files. Therefore, we cannot afford to leak memory with each file compilation.

Initially I was planning a global allocator for cake source. It had a lot of memory leaks that would be solved in the future.

When ownership checks were added it was a perfect candidate for fixing leaks. (actually I also had this in mind)

Re: Cake – C23 and Beyond (2023)

#77

I think that ownership for C is gross. It's hard to convert code to something like this. But you could get most of the benefit by just isoheaping (strictly allocate different types in different heaps).

> I think that ownership for C is gross

CVEs are gross. How do you prove your code is free from use-after-free and so on?

Re: Cake – C23 and Beyond (2023)

#78

Earlier quoted context omitted.

>If it were that simple, someone would have had success at scale by now A lot of code in that article doesn't use mempools, and furthermore, just because a double free exists doesn't mean that its always exploitable. And if its exploitable, it doesn't mean that you can gain a shell or even exfil data, sometimes it means you can just crash the program. Fundamentally, if you write a wrapper around memory management tha…

> Fundamentally, if you write a wrapper around memory management that keeps track of allocated resources, much in the same way how rust includes some runtime code during compilation for memory safety, you gain the same functionality. Can you substantiate that? There are commonly employed tracking allocators, such as ASAN that can catch certain kinds of UB, and UBSAN other, and with special interpreters you can catch…

Rust needs to add some runtime checks when calling destructors in scenarios where some object may or may not be moved.

In C++ for instance, for smart pointers, the destructor will have a "if p!= NULL". Then if the smart pointer was moved, it makes the pointer null and the destructor checks at runtime for it.

Re: Cake – C23 and Beyond (2023)

#79

I might be missing something, but this seems to require ownership annotations on all functions, e.g. a compatible and correct prototype for `fclose` to correctly note that the owned `FILE *` is moved into the call. If that's correct, then this is somewhat practically limited: either pre-existing codebases will need to be retrofitted with an essentially bespoke set of macros, or the compiler will need to be "fail open…

Compilers cheat when working with known libraries - they don't need annotations on known functions, much like they don't need the specific implementation of them to know the semantics. This occasionally goes wrong when the compiler assumes any function with a given name must be that function from libc, e.g. the programmer writes a function called `sin`, there's a risk of it being mistaken for the libm function.

Re: Cake – C23 and Beyond (2023)

#80
post #67

This project is amazing because it also seems that has #embed included, IIRC no other compiler has it yet. Just for that #embed directive I would already use cake for the moment (although it seems like it is only doing the file->array conversion)

(by the way, embed is not working on web version because of include directory bug - it is an open issue and regression)
Post reply on HN