To be devil’s advocate and take the other side of this argument: I would rather the CPU I paid for spend its cycles performing actual application logic. Every cycle spent executing all this “safety code” overhead feels like me having to pay for a developer’s sloppiness.
I feel end users pay a high cost for all this runtime checking and all these frameworks and levels of abstraction.
And second, would you really rather deal with security holes on an on-going basis?
The problem with C is not that you can write unsafe code in it, it is that the design of the language -- and pointer aliasing in particular -- makes it impossible to perform even basic sanity checks at compile time [EDIT: or even at run-time for that matter]. For example:
int x[10];
...
int y = x[11];
In any sane language, that would be a trivially-checkable compile-time error. But in C it is not because there are all kinds of things that could legally happen where the ellipses are that would make this code not violate any of C's safety constraints. That is the problem with C, and it is a fundamental problem with C's design, that it conflates pointers and arrays. C was designed for a totally different world, and it is broken beyond repair.
[0] Ok, I guess you could #define x something, but that's not interesting from a static analysis perspective.
The issue is when you pass the array to another function, it can't track the size without changing the ABI.
Indeed the `-Werror` option is often a good thing to have (though I don't set it by default on my free software projects, because other people might use other compilers with different warnings, and I don't want to block them outright).
Rust's compiler will reject programs unless it can prove them to be valid. C compilers will accept programs unless they can prove them to be invalid. But then C warnings can lead to an indeterminate state: code that looks iffy may be rejected, but we've not necessarily proven that the code is wrong. We're still trusting the programmers' claim that code which may exhibit undefined behaviour with certain inputs won't ever receive those inputs.
I don’t really love forcing errors; when a program is “under construction” you should be able to act like it is and not have to clean up all the incomplete parts. It also annoys people testing new compilers against your code.
Another problem with C. There's way too much implementation-dependent behavior.
However I do get spurious warnings, such as mixing arithmetic and bitwise operations even in cases where that's intended. Pleasing every compiler is not trivial.
int z[20];
x = z;
because arrays were formally equivalent to int*'s.
But apparently things have changed since the last time I looked.
It is also worth considering that there is nothing preventing C compilers from inserting compile time checks to address examples such as yours. I just tried to compile a similar example in gcc and it does catch the unsafe code with warnings and the optimizer turned on.
Yes, that's true. So? The original claim is that it is self-evident that this tradeoff should be resolved in favor of performance in the design of the language, and it just isn't (self-evident). If anything, it is self-evident to me that the tradeoff should be resolved in favor of safety in today's world.
Especially when to get the compiler to do the right thing safely you need to add manual bounds checking with the expectation that the compiler will optimise it away, but without any mechanism to ensure that it actually happens.
It depends greatly on the problem at hand, but there are definitely cases where even with a dynamic array size we can unroll our loop such that we check bounds less than once per iteration.
There is an asymmetrical risk here. On one hand the compile might emit checks that aren't needed. On the other that the programmer will fail to insert a check that is needed.
The problem is that for CPU programming errors are application logic too and they are run, cf. protected memory. You don't run everything in ring0 do you?
If your transistor/cost curve has a doubling time of 3 years, 156 weeks. A 5% difference is approximately 11 weeks.
If your transistor/cost curve has a doubling time of 2 years, a 5% difference is approximately 8 weeks.
How fast do we have to get before safety is table stakes? Focusing on raw unsafe speed wouldn't be a normalized metric in any other industry. I'll spend 8-11 weeks of performance gains on correctness.
C was an inside job.
First it's up to the programmer to put them in. Then hopefully the compiler will optimize away the unneeded ones. If the programmer though biffs and forgets then oops. And of course classically we blame the programmer not the system he's been forced to work under.
That seems like a reasonable thing in 1982. Which is 40 years ago.
It would be better if the compiler implemented the checks automagically and removed ones it knows it doesn't need. And bonus, if the programmer puts one in, leave it alone.
Yes, they can defeat a few classes of exploits, but generally not the ones that lead to really bad outcomes.
> ~70% of the vulnerabilities Microsoft assigns a CVE each year continue to be memory safety issues
Memory safety is a leading source of serious bugs in a big group of operating systems, browsers, image and file parsers, and more.
It does seem likely we can stop this, and it doesn’t seem likely to me we can fix it in C. We have tried and failed for years at that.
Hardware memory tagging, the language itself is beyond hope.
How is this not a win? We only have so many decisions we can make per day.
Unlike general purpose languages WUFFS has a very specific purpose (Wrangling Untrusted File Formats Safely) so it doesn't need to worry that it can't solve some of your problems, which frees it to completely refuse to do stuff that's unsafe, while going real, real fast.
WUFFS gets to completely omit bounds checks, which you probably wouldn't have dared try in C because it's so obviously unsafe, but WUFFS already proved at compile time that it can't ever have bounds misses so it needn't do these checks. WUFFS gets to also omit integer overflow checks, because again it proved at compile time that your code is correct and cannot overflow for any input. And since WUFFS knows how big the data is at all times it gets to automatically generate loop unrolling and suchlike accelerations.
WUFFS isn't a general purpose language, it doesn't have strings, it doesn't have growable arrays (what C++ calls "vectors"), it doesn't have any dynamic memory allocation, but then we weren't talking about how much you love general purpose features, we were talking about safety preventing security bugs. Which is exactly what WUFFS does.