Hacker News new | ask | show | jobs
by benreesman 1421 days ago
Yeah, this just isn't how it is anymore. The last time I was up shit creek because 50k boxes were crash looping and GDB couldn't get me a stack trace was in 2014. The last time I spent more than 30 minutes chasing a memory corruption issue was in like, 2018. And it was because some wise ass had decided to roll his own fibers by stomping on `rip`, `rbp`, and `rsp`.

These days you use `std::unique_ptr`, build with clang-tidy, CI under ASAN, and it's never an issue in practice. Once in a blue moon the CI chirps an ASAN failure that gives you the entire history of the memory address with line numbers and you fix the typo.

The safety that Rust gives me is that it's more expressive type system and modern affordances for things like exhaustive pattern matching lets me avoid logic errors, which are every bit as deadly as buffer overruns and much harder to mechanically identify.

It is usually easier to write correct code in Rust than in C++ because it's much more modern and frankly kind of an everyman's Haskell (which I mean as a compliment). But it's intellectually dishonest to say that this doesn't come at a cost: when you wander out of the borrow checker's sweet spot it can become kind of a Tetris puzzle even when you know all the rules on paper.

The same pattern matching that lets people see a borrow checker puzzle and immediately say "right, we need to do X" is the pattern matching that let's a C++ hacker see a failed template instantiation and immediately know what got misspelled.

2 comments

A sibling comment suggests this has more to do with where you work than how modern your C++ is, which rings true to me. Different kinds of programs need different kinds of memory management patterns, and some are more error-prone than others.

In my experience there also tends to be a long tail of memory corruption bugs. After flushing out those that are easy to run into or that have a major impact, everything seems fine and you can go years without really spending time on them, but they're still lurking at the edges of automated crash reports and mysterious bug reports you never quite manage to reproduce yourself. And when I do manage to track one down, it's as likely as not to be in, around, or even caused by modern C++ features.

Tetris puzzle or not, it's really quite nice to systematically rule out those kinds of issues. In some domains it may not be worth it, but in others they can hide major security issues or similar. And either way it sure beats periodically digging through crash dumps trying to piece together something that looks impossible from the surrounding source code.

If you need extreme robustness you have to have coverage and fuzzing and canaries and stuff for logic bugs as well as memory bugs. If you’ve got a long tail of non-exercised code paths, a “<“ flipped with a “>” will fuck up your day just as bad as a use-after-free.

If your code is covered, ASAN will red-zone the memory bug. It checks every address.

People are welcome to their subjective opinions about the “easiest” way to get truly correct software (which almost no one needs), but the oft-repeated assertion/implication that the tools don’t exist to do it outside of Rust/Go is wrong. Not a subjective opinion, demonstrably incorrect.

And when enough truly important shit is written in Rust, which will be soon, there will be CVEs. Many of them.

Well, yeah, if you're reaching for that level of robustness you want every tool you can get. If you can get rid of a whole category of bugs with one tool, that only makes the other tools more effective for the rest!

(There are also cases where that extra robustness is more of a "nice to have," so if you can get a side effect of your approach to something more important, that changes the calculus too.)

> a “<“ flipped with a “>” will fuck up your day just as bad as a use-after-free

One is categorically worse than the other though.

I deal with issues like this about once a month. It may not happen where you work, but it definitely still happens.

If it really never happens where you work, consider yourself lucky.

Is every diff thoroughly reviewed? Is everything built with `-Wall -Wpedantic -Werror`, `clang-tidy` with most checks on, ASAN/TSAN/MSAN/UBSAN on every commit in the CI, and aggressively canaried against replay data (or whatever is appropriate to the domain to exercise all the paths)? Is all the code run through `clang-format` in a pre-commit hook to lower the cognitive overhead of spotting bugs?

I completely understand that when you turn all the checks up to maximum (which, in fairness, `rustc` does by default) you start with as many errors as you have files if you're lucky, and probably 10x that. I've had to take codebases from working by accident on every 10th line to passing all the static analysis cheaper than PVS-Studio, and it's a bear no doubt. But codebases that are `clang -Werror` clean, `clang-tidy` and `cppcheck` clean, ASAN/MSAN/UBSAN clean, and have all this enforced by CI?

I haven't seen those codebases thrash the core dump where GDB prints out a bunch of "????????????" instead of addresses with any frequency.

Someone should do a 2022-edition "Joel Test" (https://www.joelonsoftware.com/2000/08/09/the-joel-test-12-s...) because I think we're all using revision control now, times change, but until someone does, I'm happy to trade war stories about getting messy code bases / development workflows into fighting form.

We still develop on and support platforms where we use the vendor compilers (which don't have many of those modern features).
That definitely ups the stakes on the "modern" approach a lot (like, in the limit case `#ifdef`-hell to get part way there).

There are firms that will sell you a suite of frontends supporting every C compiler back to the early 80s and integrate them into a modern toolchain, I used to work with an alum of such a firm and I gather it's great stuff. I also gather it costs whatever you can afford, so there's that. I forget the name of the company but I could ping my friend if that's interesting to you.

Worst case, you could not have to chase memory corruptions on the subset of your target platforms that LLVM targets.