If ((uint32_t)x But why can't it just produce something different on each system? Allow me to call it as I see it: the modern interpretation of undefined behaviour is bullshit. What compilers do today should be the recourse of absolute last resort, and the sort of thing that makes its authors feel bad. But it seems to be treated as a matter of course. I don't know what to say. Mandatory reading: http://robertoconcert…
It's a problem because that will lead to bugs. You write code on x86, it works for years and then you let it run on ARM and it will misbehave. If it weren't a problem, then the compiler doing whatever it wants also wouldn't be a problem. IMO the best thing would be if the compiler would insert code to print to screen a warning and exit the program, if it comes across undefined behaviour. (This is different from sanit…
The Problem with Friendly C
141–150 of 174 posts
Re: The Problem with Friendly C
#142Earlier quoted context omitted.
I'll take the bet that in 2035 it's still going to be C/C++ (or a C derivative like Boring C). Because rewriting all that code is an economic impossibility. There's a very long way to go before the rate of foundational Rust code written exceeds the rate of foundational C/C++ code written. And even if you manage to have 100% Rust and 0% C/C++ code being written, you still have a huge legacy to write, which literally c…
I haven't used it yet, but apparently Rust's FFI with C (in both directions) is quite good: https://doc.rust-lang.org/book/ffi.html
I thought Chromium had a library for this (for multiple C++ processes), but I guess it is sort of ad hoc now?
https://www.chromium.org/developers/design-documents/inter-p...
https://www.chromium.org/Home/chromium-security/education/se...
Re: The Problem with Friendly C
#143Earlier quoted context omitted.
I'll take the bet that in 2035 it's still going to be C/C++ (or a C derivative like Boring C). Because rewriting all that code is an economic impossibility. There's a very long way to go before the rate of foundational Rust code written exceeds the rate of foundational C/C++ code written. And even if you manage to have 100% Rust and 0% C/C++ code being written, you still have a huge legacy to write, which literally c…
'Honestly I think your view of technological adoption is fairly naive -- I don't see much content here other than "everyone get behind Rust!".' I think you may not have seen much content because you weren't looking for it very hard; for one thing, you seem to have simply instantiated for me an instance of the fallacy I mentioned. People overestimate change in the short term, but underestimate it in the long term. "IM…
Application compartmentalization is certainly difficult in practice, and should be made easier, but it is deployed and it's a lot easier than rewriting all code in a different language.
ffmpeg is a great example. It is hideously broken, with thousands of likely security bugs [2]. Now let's compare these two strategies:
1) Rewrite EVERY video codec in Rust
2) Reuse existing code, but run it in a completely
untrusted process (no OS privileges), and only
communicate with IPC.
The claim is that #2 is much more achievable than #1. You could say that #1 takes orders of magnitude more work, which is true, but that doesn't even quite capture it -- it actually scales differently because for #2 you do constant work and get security for ALL video codecs. It's O(1) vs O(n).Why not get secure foundations by 2025 instead of 2035? What I'm arguing for is a hybrid, heterogeneous architecture -- instead of rewriting 100% of code in Rust, rewrite 5% in Rust and isolate the remaining 95%. That gets you a better result which much less effort.
These techniques predate modern browsers -- they are used in qmail and djbdns. [3] [4]
I agree with the idea that most people underestimate change in the long term, but that is orthogonal to my point -- we get get secure systems faster with a different technique, in a shorter time frame.
Also, if you look at the code we're using today, it is perfectly reasonable to conclude that in 2035 we will be using a lot of the same code. People are still using Linux (1991), GNU tools (started in the 80's), Python (1990), Perl (1985), PHP (90's), etc. In 2035 is undoubted we will be using code from 2015 -- in fact it is likely we will be using code from 1985 in 2035. Those are the economics of software. Stable code is extraordinarily valuable and long lasting.
But the good news it that we can be running C code from 1985 in 2035 and still have secure systems!
I apologize for saying your post was naive, but I can tell from your original post and response that you haven't worked with security or operating systems very much. I have some impatience because the "rewrite everything" response reminds me of people who think they can rewrite an old codebase do better, when in reality they would end up with something worse [5]. There is a lack of subtlety and lack of understanding implied when you want to start completely from scratch.
Browsers like Firefox and Chrome didn't even start from scratch. Firefox was derived from decades-old netscape code, and Chrome reused Webkit, which was derived from Konquerer. Google has infinite money, but didn't rewrite Android from scratch -- it is based on Linux and hundreds of other open source projects. Code lasts A LONG TIME -- simply suggesting that everyone rewrite it is not helpful. It's an economic impossibility.
[1] Deployed by code you are using every day: (Chrome, Firefox, IE, Safari, etc.)
https://en.wikipedia.org/wiki/Process_isolation
http://www.chromium.org/developers/design-documents/multi-pr... https://developer.mozilla.org/en-US/Firefox/Multiprocess_Fir...
[2] https://googleonlinesecurity.blogspot.com/2014/01/ffmpeg-and...
[3] http://cr.yp.to/cv/activities-20050107.pdf -- see "extreme sandboxing".
[4] http://cr.yp.to/qmail/qmailsec-20071101.pdf
[5] http://www.joelonsoftware.com/articles/fog0000000069.html
Re: The Problem with Friendly C
#144Earlier quoted context omitted.
What I want is for the semantics of the language to match the semantics of the physical machine I'm writing software for. If on x86 INT_MAX+1==INT_MIN, then that's what should happen on x86. If on ARM INT_MAX+1==INT_MAX, that's what should happen on ARM. No, I don't want portability. Portability means you're coding against the least common denominator. If I want it to be portable, I'll use a different compiler.
> If on x86 INT_MAX+1==INT_MIN [...] There is no such thing as "+ on x86". "+" is an operator of the C language. Modern x86 has about two dozen instructions that can be used to perform additions, each with slightly different behaviour, and with different performance characteristics. Someone has to define how C's "+" maps to those instructions. That is what the C standard does. The way it does that is by specifying pr…
Re: The Problem with Friendly C
#145Earlier quoted context omitted.
Huh? If you don't want any dynamic allocation (or any other features that require support from a runtime system), you can use `#![no_std]` and instead use `libcore` directly. This is how the standard library itself is implemented.
The problem, as I understand it, is that Rust's ownership semantics don't accommodate for the kinds of programming needed by 0 dynamic allocations. Lots of intrusive nodes pointing freely at one another, is an example. I've heard some Rust libraries approximate intrusive allocations almost perfectly, but not quite perfect.
What does require dynamic allocation is using boxed objects and dynamically sized collections (such as vectors and tree maps), which are provided by `libstd`, but not by `libcore`. Think of using `libcore` as using C or C++ in freestanding mode.
Re: The Problem with Friendly C
#146Earlier quoted context omitted.
It's a problem because that will lead to bugs. You write code on x86, it works for years and then you let it run on ARM and it will misbehave. If it weren't a problem, then the compiler doing whatever it wants also wouldn't be a problem. IMO the best thing would be if the compiler would insert code to print to screen a warning and exit the program, if it comes across undefined behaviour. (This is different from sanit…
The whole point of undefined behaviour is to allow for optimizations. Adding tons of runtime checks is the opposite of optimization. A lot of this is about being able to use a single instruction of the respective target architecture vs. adding another two, three instructions for the check, thus slowing things down to half the speed or less.
Re: The Problem with Friendly C
#147Earlier quoted context omitted.
If you have undefined behaviour after an infinite loop that breaks on some condition, then that undefined behaviour will not necessarily have been triggered. Thus, no warning.
Regehr himself "disproved" Fermat's Last Theorem by abusing a non-terminating loop and over-aggressive compilers. http://blog.regehr.org/archives/140
Re: The Problem with Friendly C
#148Earlier quoted context omitted.
No, rebutting that was exactly my point: programming is, in some ways, a friendlier version of working on mathematical proofs, in that the compiler will tell you when you "did something you were not supposed to do." Programmers expect to be able to hack away like monkeys on typewriters, and just run into a virtual wall whenever they misstep. Compilers will error out when a programmer explicitly types "0/0", and that'…
You are still missing the point. Yes, compilers obviously should not intentionally produce unnecessarily destructive behaviour. But that is not what is happening. What if the code contains "x/y"? Now, the compiler can try to prove that x and y can never be 0 at the same time. But what if that proof doesn't succeed? Maybe x is user input, so who knows what the user will enter at runtime? Now, the language spec could s…
See my sibling reply down-thread. You don't want the compiler to automatically insert the check; but neither do you want the compiler to assume you want to not insert the check. You want the compiler to error out until you tell it whether or not you want a check. Or rather, whether you want checked-division semantics—which will be free on some architectures and costly on others—or unchecked-division semantics—which will be dangerous on all architectures, but technically impossible on some (presumably, it would be approximated by using checked division and then throwing away the error.)
Either way, you probably don't want the default "checked division that could error out on the architectures where it's free, and unchecked division on the rest"; you then have to code for both the case where your function can now trap/throw an exception, and the case where control continues through your function using the invalid calculated intermediate as input for further operations. You've probably coded to handle one or the other—but it's very unlikely you've coded to handle both. And why would you want to have to?
Deciding on your code's semantics should a development-time decision. The compiler should prevent you from making the mistake of leaving your code's semantics to the whims of the target architecture your (presumably portable) code happens to get compiled on, at some other time by some other person, unless you explicitly opt in to having the semantics of "whatever the target architecture wants to give me", on a case-by-case basis. The compiler should ensure that your code has the semantics you want everywhere—whether those be "always do X" or "do whatever the target arch's closest approximation to X is."
What we call "undefined behavior" in C is actually implemented by C compilers as a very specific set of assumptions about what semantics developers prefer—and those assumptions may or may not be right for most code, but they can't be right for 100% of code. Code is made of decisions[1]; the whole of the task of programming is to explicate every decision about a business-process that was previously left implicit, so that the same predictable and deterministic thing will happen everywhere, every time. So why should compilers allow programmers to leave their desired semantics implicit?
Re: The Problem with Friendly C
#149Earlier quoted context omitted.
> If on x86 INT_MAX+1==INT_MIN [...] There is no such thing as "+ on x86". "+" is an operator of the C language. Modern x86 has about two dozen instructions that can be used to perform additions, each with slightly different behaviour, and with different performance characteristics. Someone has to define how C's "+" maps to those instructions. That is what the C standard does. The way it does that is by specifying pr…
There are integer additions of different size, but is there anything you could honestly call plain integer addition that has different behavior from wrapping?
But the C standard doesn't say "+ maps to what you could honestly call plain integer addition" anyway, so it's kindof pointless? And if you were to define your own language, you obviously could define "+" to have architecture-specific semantics, whithout appealing to any "plain integer addition".
Re: The Problem with Friendly C
#150Earlier quoted context omitted.
You are still missing the point. Yes, compilers obviously should not intentionally produce unnecessarily destructive behaviour. But that is not what is happening. What if the code contains "x/y"? Now, the compiler can try to prove that x and y can never be 0 at the same time. But what if that proof doesn't succeed? Maybe x is user input, so who knows what the user will enter at runtime? Now, the language spec could s…
> But if it did that, there might be target architectures where the code that would need to be generated to check for that case would have a 300% performance overhead See my sibling reply down-thread. You don't want the compiler to automatically insert the check; but neither do you want the compiler to assume you want to not insert the check. You want the compiler to error out until you tell it whether or not you wan…
I think I disagree. I don't want that to be a feature of the compiler, but of the language, in which case there is no way to "error out". That is to say: there should be operators for one kind of semantics and operators for another kind (it's a bad idea to overload the same operator on the same operands with different semantics). And if the language doesn't have that feature, the compiler shouldn't error out--unless it can prove that your code will unavoidably run into undefined behaviour, in which case, simply not producing a binary at all is perfectly within the range of acceptable results, and probably a good idea.
> [...] you then have to code for both the case where your function can now trap/throw an exception, and the case where control continues through your function using the invalid calculated intermediate as input for further operations. You've probably coded to handle one or the other—but it's very unlikely you've coded to handle both. And why would you want to have to?
If you think in terms of handling the results of undefined behavior, you are kindof doing it backwards. The language specifies certain preconditions that you have to meet in order for some operation to be defined. It's your job as a programmer to make sure that those preconditions are met, in which case it doesn't matter what some compiler does when they are not met.
> The compiler should ensure that your code has the semantics you want everywhere—whether those be "always do X" or "do whatever the target arch's closest approximation to X is."
Well, yes and no. Giving you the same semantics certainly is a good idea, where possible. But to "do whatever the target arch's closest approximation to X is." is a useless idea, as you can not write any reliable code on that basis. A language always has to specify certain properties that an operation needs to have, and every compiler needs to choose an implementation that fulfill those requirements, not just some vague "as close as possible"--that way, if you write code that only relies on those guaranteed properties, it will compile and run correctly on all targets. The question is only how many details are guaranteed properties and which parts are up to the implementation, aka "undefined behaviour", aka "stuff you must not rely on in your code". It's kindof impossible to really guarantee "the same semantics"--however, there are some guarantees that would be useful for a lot of code and that C, unfortunately, does not provide.
> What we call "undefined behavior" in C is actually implemented by C compilers as a very specific set of assumptions about what semantics developers prefer—and those assumptions may or may not be right for most code, but they can't be right for 100% of code.
That's actually not true. What we call "undefined behavior" in C is actually implemented by C compilers as "it doesn't matter what happens in this case". Sometimes, compiler writers will actually define some of the things that are undefined behavior in the standard, but more often than not, the compiler doesn't actually contain any explicit definition for what is supposed to happen in those cases. The compiler only worries about transforming your source code into machine code that does what the standard specifies where the standard does specify what is supposed to happen--where the standard doesn't specify anything, any resulting behavior is just a coincident byproduct of how the compiler works, which can change with any new release, and even depending on optimization options.
> So why should compilers allow programmers to leave their desired semantics implicit?
They actually don't. If the programmer wants to express "add x to y (two signed integers) with wrap around" and writes in a C program "x+y", the programmer has simply made a mistake. It may be that in some cases, if you write "x+y", your compiler will behave with those intended semantics. But that is a coincidence. There is nothing implicit there, "x+y" in C simply means something different than the programmer intended, and just by coincidence sometimes produces the desired result. It's not any more correct than writing "exit(0);" when you want to print "hello world"--the only difference is that the compiler is free to produce the intended result even though it's not required to, which confuses many people. But just because the compiler sometimes produces the intended result does not mean that what you wrote actually means what you intended.