What you are describing is the desire to rely on your tooling and tests, and you should be able to. Unfortunately, in the 25 years I've known C++, I feel like I've been promised this and that and the other thing was making C++ safe, correct, reliable, etc. And yet this goal seems elusive. I'm glad that you don't have to think about most of the language these days, but there are too many trapdoors and landmines waiting out there, and in C++ they have serious consequences. There are bugs in the code--probably in your code--right now. Some might be CVEs because of how little guarantees you have when you stumble into UB. I hope we get past the "works for me" and realize the downstream implications of software being riddled with potentially serious bugs that result from a ton of complexity coupled with explosive consequences.
I guess you can still write valuable software, even more complex than most people can write in a lifetime, with simple (03) C++. Like Doom 3: https://github.com/id-Software/DOOM-3-BFG
> What you are describing
On the other hand, while I think you have the best intentions, I believe what you are describing is also the exact meaning of FUD. In that precise order: "in C++ they have serious consequences" is Fear, "There are bugs in the code--probably in your code--right now" is Uncertainty, and "Some might be CVEs" is Doubt.
I guess. The other day i created a PR to turn some macros into a variadic template and got a compilation error that can be resolved by moving some functions around. No one could figure out why so i just closed the PR.
There are so many moving parts in C++ there’s a reason why there are dialects. People are manually having to choose the smaller C++ that they can manage.
> I think you have the best intentions, I believe what you are describing is also the exact meaning of FUD.
FUD usually has a negative connotation as a disingenuous form of rhetoric. If your assume the poster is genuine, then you shouldn't in the next breath accuse them of spreading FUD. If one is genuinely fearful and uncertain and filled with doubt, it's okay to express that.
To the parent's point, C++ does allow programmers to easily write code that crashes spectacularly. Such bugs have been shown to cause catastrophic failure in critical systems, to the point where we decided to build languages and tools that mitigate those modes of failure. Those learnings have found their way back into C++, but the problem remains that writing modern C++" is an ever-moving target, and the "legacy C++" that should be avoided is still there in the name of backwards compatibility, so buggy code still being written. The solution is not "just write modern C++" because that doesn't work; witness the lamentations here about people who are still writing C++ like it's the 90s.
As for the uncertainty, the only problem with that statement is the "probably" because we all know the only bug-free code is trivial code (and even then...). But still, there's an important point here: shouldn't we be able to confidently make statements about our codebase like "there are no bugs of X type in here, because it's been checked by tools". For example, some languages are stricter with what they will allow past the compiler, and the level of strictness confers some guarantees about what kind of bugs have been checked. If we can't say "my language's tooling makes it so my program doesn't suffer from X bugs", then how much is it helping us really?
Bah, 2/3rds of CVEs are buffer overruns that cause RCE. That doesn't occur in memory safe languages. I mean, you can juggle double-ended chainsaws on fire as a party trick, but probably not recommended for daily activities.
> 2/3rds of CVEs are buffer overruns that cause RCE
Is this the "70 percent of all security bugs are memory safety issues" article people like to link every time?
If so, it's not 2/3, it's 70%. They are not buffer overruns, but memory issues, and not all can cause remote code execution.
There is no rule that says that fixing bugs is an itch and everybody has to disperately scratch it, and some people can sleep well at night even if they have a few bugs. The rest is FUD in favor of one or another language flavor.
Not all software has a remote endpoint, is connected to internet, has an UI, or process input, etc. C++ and "juggl(ing) double-ended chainsaws on fire" is not the same and is an unfair comparison.
One of the better known buffer overruns (Heartbleed) was build on top of what boiled down to buffer reuse, which is common even in memory safe languages.
There's no obvious way to write the Heartbleed mistake in Safe Rust.
In C it's really easy, a trivial mistake, you just don't check the payload length and copy blindly, but in Rust you can't write this mistake at all, so you'd need to re-architect the system to make it possible to leak this data or else (if you're not actively trying to leak data) you just don't do this and you can't be attacked this way.
The closest analogue, Rust's [T].copy_from_slice wants a slice of the same size, so if we try to make the mistake OpenSSL made where we just forget to check, that means the slices are the wrong size and we panic, it's a Denial of Service but nothing more.
If the slice is the right size that's a normal heartbeat, everything works as intended.
It is also really easy in C to have a safe abstraction for buffers that check payload length and does not copy blindly. Why so few people use such abstractions I do not know, but I guess once Rust is more widely deployed, we will also see a lot of unsafe Rust where people did not have enough time or energy to do things properly.
> I guess once Rust is more widely deployed, we will also see a lot of unsafe Rust where people did not have enough time or energy to do things properly.
This doesn't make a whole lot of sense. If you lack "time or energy" you're not going to put the extra work in to write unsafe code.
In C this bug was much easier to write than the correct thing, whereas I explained in Rust the bug is much harder to write than the correct thing. Humans are lazy so they're going to tend to do the thing that's easier, and here (and in many cases) that's the more correct thing in Rust but not in C.
This is just ergonomics. Notice how crash bars work on fire exits for example. Even people who are panicked and just running into the doors will trigger them to safely open outwards. You get incident reports where operators locked the fire doors, trapping people, or incidents where there are just too many people to evacuate despite the fire doors working for those who reached them, but you don't get incidents where people are like "Huh, I have no idea how to open this door, these crash bars are too difficult for me to understand".
You are right that one problem in C is that it is too inconvenient to do the right thing. A safe buffer abstraction should be included in the standard library. Still, I already saw bad Rust code using unsafe. I wonder how it came into existence when not by lazyness?
I am far from a rust expert, but from a short look at stackoverflow copying between two slices of different sizes seems to be a for loop away and I saw no mention of unsafe.
> so you'd need to re-architect the system to make it possible to leak this data or else (if you're not actively trying to leak data)
OpenSSL called, your standard library sucks and it is going to provide its own significantly worse replacement for everything you can think of and at least ten things more.
> you just don't do this and you can't be attacked this way.
> I am far from a rust expert, but from a short look at stackoverflow copying between two slices of different sizes seems to be a for loop away and I saw no mention of unsafe.
You can do this, but, what are you copying and why?
The C code is just trying to copy the expected amount of data from the receive buffer into the send buffer. Under attack the receive buffer is actually nowhere near big enough to do that, but C doesn't care, which is why Heartbleed exists.
You can't write that mistake in Rust, even if you insist on painstakingly writing it out as a for loop, if we have a 20 byte receive slice, and we ask for receive[1000] that'll panic
To leak the data in Rust, you need to re-architect the software, you need to consciously plan for leaking the data in your software. "This code is to help us leak important secrets, and then this structure here enables the leaked data to be fed into data sent to an attacker".
> Unfortunately, in the 25 years I've known C++, I feel like I've been promised this and that and the other thing was making C++ safe, correct, reliable, etc. And yet this goal seems elusive.
How hard have you tried?
The tooling is terrible to set up, and build system integration is extremely lacking. But once you have set up clang-tidy and clang-format, it just works, and it catches bugs and ensures a consistent style. And enabling clang's and GCC's static analysis is just a few compiler options away. Same with address sanitizer, leak sanitizer, UB sanitizer and thread sanitizer.
None of it gets used automatically, you have to understand the tools and how to integrate them into your build system. But they're not at all elusive.
> There are bugs in the code--probably in your code--right now. Some might be CVEs because of how little guarantees you have when you stumble into UB.
Pick any medium/large project written in any language that hasn't undergone extensive formal verification+testing and this statement will likely be true.
This won't quite be true for Safe Rust because it doesn't have Undefined Behaviour. So while it's possible there are bugs, and it's possible those are security bugs, they can't be caused by UB as they would often be in C++. Your program had defined behaviour - it's possible this behaviour wasn't what you intended, but it's what you wrote.
A lot of cases which might invoke UB in C++ will just be a Denial of Service in Rust, as you panic because an unhappy path wasn't catered for properly in your software and most likely your panic handler just terminates the program. That DoS might still be a CVE, but it's probably not the severity you'd have seen for the equivalent C++ bug.
I am the maintainer of a very large scale multi-threaded C++ application used by large airline/airport/rail companies around the world. I haven’t had a bug in production for 5+ years. Not one. And I routinely refactor/improve large parts of the code with zero problems.
The “secret” obviously is 9000+ system level tests.
And I will claim that you need that number of tests. Even if the application was written in (say) Rust. Rust won’t save you from subtle daylight savings errors, country specific governmental requirements, weird special case organisational policy rules etc. Only an executable spec (tests) will do that.