Live data from Hacker News

How safe is Zig?

scattered-thoughts.net

21–30 of 259 posts

Re: How safe is Zig?

#21

I like zig but this is taking a page out of rust book and exaggerating C and C++ clang and gcc will both tell you at runtime if you go out of bounds, have an integer overflow, use after free etc. You need to turn on the sanitizer. You can't have them all on at the same time because code will be unnecessarily slow (ex: having thread sanitizer on in a single threaded app is pointless)

One interesting distinction is that it sounds as if - for Zig, this is a language feature and not a toolchain feature. Although if there's only one toolchain for zig maybe that's a distinction-without-a-difference. At least it's not opt-in, that's really nice. Believe it or not, there are lots of people who write and debug C/C++ code who don't know about sanitizers or they know about it and never decide to use them.

I think it would be interesting to see zig move towards annotation-based compile time lifetime checking plugin (ideally in-toolchain, but alternatively as a library). You could choose to turn it on selectively for security-critical pathways, turn it off for "trust me" functions, or, do it on "not every recompilation", as desired.

Re: How safe is Zig?

#22

I would like Zig to do more to protect users from dangling stack pointers somehow. I am almost entirely done writing such bugs, but I catch them in code review frequently, and I recently moved these lines out of main() into some subroutine: var fba = std.heap.FixedBufferAllocator.init(slice_for_fba); gpa = fba.allocator(); slice_for_fba is a heap-allocated byte slice. gpa is a global. fba was local to main(), which c…

Alternatively, use offset values instead of internal pointers. Now your structs are trivially relocatable, and you can use smaller integer types instead of pointers, which allows you to more easily catch overflow errors.

Re: How safe is Zig?

#23

This was a great read, with an important point: there's always a tradeoff to be made, and we can make it (e.g. never freeing memory to obtain temporal memory safety without static lifetime checking). One thought: > Never calling free (practical for many embedded programs, some command-line utilities, compilers etc) This works well for compilers and embedded systems, but please don't do it command-line tools that are…

There are some old-hand approaches to this which work out fine.

An example would be a generous rolling buffer, with enough room for the data you're working on. Most tools which are working on a stream of data don't require much memory, they're either doing a peephole transformation or building up data with filtration and aggregation, or some combination.

You can't have a use-after-free bug if you never call free, treating the OS as your garbage collector for memory (not other resources please) is fine.

Re: How safe is Zig?

#24
Do compilers really can never call `free()`?

Simple compiler probably can. Most complex probably cannot (I don't want to imagine a Rust compiler without freeing memory: it has 7 layers of lowering (source code->tokens->ast->HIR->THIR->MIR->monomorphized MIR, excluding the final LLVM IR) and also allocates a lot while type-checking or borrow-checking).

What is most interesting to me is the average compiler. Does somebody have statistics on the average amount compilers allocate and free?

Re: How safe is Zig?

#25

A lot of embedded devices and safety critical software sometimes don't even use a heap, and instead use pre-allocated chunks of memory whose size is calculated beforehand. It's memory safe, and has much more deterministic execution time. This is also a popular approach in games, especially ones with entity-component-system architectures. I'm excited about Zig for these use cases especially, it can be a much easier ap…

Rust's borrow checker would be much calmer in these scenarios too, wouldn't it? If there are no lifetimes, there are no lifetime errors

Re: How safe is Zig?

#26

Earlier quoted context omitted.

Neither Clang nor GCC has perfect bounds or lifetime analysis, since the language semantics forbid it: it's perfectly legal at compile time to address at some offset into a supplied pointer, because the compiler has no way of knowing that the memory there isn't owned and initialized. Sanitizers are great; I love sanitizers. But you can't run them in production without a significant performance hit, and that's where t…

State of the art sanitizing is pretty consistently in the Yes, the "just-enable-the-compiler-flags" approach can be expensive, but the tools exist to allow most people to be sanitizing most of the time. Devs simply don't know what's available to them.

I'd consider even 10% to be a significant performance hit. People scream bloody murder when CPU-level mitigations cause even 1-2% regressions. The marginal cost of mitigations when memory safe code can run without them is infinite.

But let's say, for the sake of argument, that I can tolerate programs that run twice as long in production. This doesn't improve much:

* I'm not going to be deploying SoTA sanitizers (SANRAZOR is currently a research artifact; it's not available in mainline LLVM as far as I can tell.)

* No sanitizer that I know of guarantees that execution corresponds to memory safety. ASan famously won't detect reads of uninitialized memory (MSan will, but you can't use both at the same time), and it similarly won't detect layout-adjacent overreads/writes.

That's a lot of words to say that I think sanitizers are great, but they're not a meaningful alternative to actual memory safety. Not when I can have my cake and eat it too.

Re: How safe is Zig?

#27

I would like Zig to do more to protect users from dangling stack pointers somehow. I am almost entirely done writing such bugs, but I catch them in code review frequently, and I recently moved these lines out of main() into some subroutine: var fba = std.heap.FixedBufferAllocator.init(slice_for_fba); gpa = fba.allocator(); slice_for_fba is a heap-allocated byte slice. gpa is a global. fba was local to main(), which c…

Alternatively, use offset values instead of internal pointers. Now your structs are trivially relocatable, and you can use smaller integer types instead of pointers, which allows you to more easily catch overflow errors.

This is a good idea, but native support for slices tempts one to stray from the path.

Re: How safe is Zig?

#29

Do compilers really can never call `free()`? Simple compiler probably can. Most complex probably cannot (I don't want to imagine a Rust compiler without freeing memory: it has 7 layers of lowering (source code->tokens->ast->HIR->THIR->MIR->monomorphized MIR, excluding the final LLVM IR) and also allocates a lot while type-checking or borrow-checking). What is most interesting to me is the average compiler. Does someb…

> Do compilers really can never call `free()`?

I worked on, one of the many, Microsoft compiler teams, though as a software engineer in test not directly on the compiler itself, and I believe the lead dev told me they don't free any memory, though I could be misremembering since it was my first job out of college.

Remember C compilers are often one file at a time (and a LOLWTF # of includes), and the majority of work goes into making a single output file, and then you are done. Freeing memory would just take time, better to just hand it all back to the OS.

Also compilers are obsessed with correctness, generating incorrect code is to be avoided at all costs. Dealing with memory management is just one more place where things can go wrong. So why bother?

I do remember running out of memory using link time code gen though, back when everything was 32bit.

Related, I miss the insane dedication to quality that team had. Every single bug had a regression test created for it. We had regression tests 10-15 years old that would find a bug that would have otherwise slipped through. It was a great way to start my career off, just sad I haven't seen testing done at that level since then!

Re: How safe is Zig?

#30
UBSan has a -fsanitize-minimal-runtime flag which is supposedly suitable for production:

https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html#...

So it seems that null-pointer dereferences and integer overflows can be checked at runtime in C. Besides, there should be production-ready C compilers that offer bounds checking.

Post reply on HN