3 ms·
That depends on what you mean by "just like." Zig does not make sound static guarantees about use-after-free, because those guarantees have other costs that Zig
by pron 5y ago
That depends on what you mean by "just like." Zig does not make sound static guarantees about use-after-free, because those guarantees have other costs that Zig would rather not pay. This means that detecting the problem might requires some dynamic analysis. But that doesn't mean that it's "just like" such analyses in C, because Zig does make guarantees that are fundamentally different from C's. In particular, unless you resort to syntactically delineated constructs -- analogous to unsafe in Rust -- Zig does not have pointer arithmetic or arrays of unknown size. Unlike in C, in (the safe subset of) Zig every pointer is known and every object is known. This means that it's easier to build more precise and more effective mechanisms that detect such errors in Zig than in C.
In other words, if you mean that use-after-free is handled in Zig in a manner that's very different from how it's handled in either Java or Rust and more similar to the general approach of how it's handled by various tools for C, then that's correct. If you mean that that general approach is just as (in)effective in Zig as it is in C, then that is incorrect. Because Zig is fundamentally more precise than C, analyses of Zig, even if they are conceptually similar to those done for C, can be more precise and effective.
- pcwalton 5y agoIt's hard to figure out exactly what you're saying, but I think you're trying to imply that bounds checks will allow for the development of some kind of novel use-after-free mitigation. Without a specific proposal I don't know why that would be the case. Zig is not meaningfully different from C++ when it comes to UAF.
- pron 5y ago> Zig is not meaningfully different from C++ when it comes to UAF. Without pointer arithmetic, unsafe casts (Zig is much easier to write without such casts than C, at least), unsafe unions, and unknown buffer sizes, the set of pointers in a Zig program can be well-defined, as pointers can come into being only in very specific ways (they have a simple provenance). Because the set of pointers is well defined, it can be precisely tracked and analysed. This means that 1. pointers can be traced even with arena allocators or perhaps even other kinds of pools (provided they cooperate with the tool) and 2. dangling pointers can be detected even without being dereferenced. This is simply not something that analysers for C or C++ can do (at least not nearly as easily). The way to think about it is that in Zig, when an object (including an allocator) is deallocated, you can invalidate the full set of pointers pointing to it, and that set it the only way of generating more pointers into it. That's not the case in C or C++. For one, pointers aren't well defined (unions); for another, a valid pointer could be used to create an invalid one.
- pcwalton 5y agoWhat you've described is a tracing garbage collector (to be pedantic, one where weak pointers are the norm, but the infrastructure and algorithm are essentially the same). In fact, I absolutely agree with you that Zig should adopt tracing garbage collection (a state-of-the-art generational concurrent one with bump allocation in the nursery), and doing so would eliminate most of my complaints about it. Unfortunately, it's unlikely that Zig will ever do this, given everything that I've seen about its design goals of being low-level with no runtime.
- pron 5y agoYou've misunderstood me. My point was that Zig's properties allow a kind of precise analysis that is not possible (or very hard) in C or C++, and so it is not true that it is "not meaningfully different re UAF". As a simple concrete example, I hinted at a hypothetical algorithm similar to that of a tracing GC, that could be used not to collect garbage (I specifically mentioned the use of arenas), but to promptly detect all dangling pointers during testing, including those that are not dereferenced, and those pointing at arenas. That alone is already more effective than tooling you could make for C or C++. But those guarantees allow for other kinds of analysis, perhaps static analysis, that will also be more effective than what's affordable in C or C++. So while the general approach -- of dynamic and static analysis, as opposed those of Java or Rust -- is in the same broad category of tools for C or C++, their effectiveness, due to Zig's properties, is significantly increased, and that their entire cost/benefit is different. I.e. it could find more bugs for a lower price, so much so that the approach, while underwhelming when applied to C or C++, could well compare favourably with others when applied to Zig. (A GC -- whether tracing or ref-counting -- might well be adopted for Zig's comptime, but that's a whole other matter)
- pcwalton 5y ago> As a simple concrete example, I hinted at a hypothetical algorithm similar to that of a tracing GC, that could be used not to collect garbage (I specifically mentioned the use of arenas), but to promptly detect all dangling pointers during testing, including those that are not dereferenced, and those pointing at arenas. I'm highly skeptical that there won't be too many false positives with such a tool. Systems programmers routinely create temporary dangling pointers and let the values go dead without dereferencing those pointers. It happens most every time you call free, in fact. I also see no reason why you couldn't create such a thing for C and C++. In fact, it exists: the Boehm GC can operate in such a checking mode. The fact that everyone uses ASan instead is a strong indicator that ASan is in fact a better approach. Finally, precise tracing GC stack/register maps are a lot of work, especially in LLVM which has poor support for them. (I heavily looked into this for Rust.) Without a serious effort (and it is a lot of work) to generate them for Zig I have to consider it vaporware.