Insulating layer?
Insulating layer?
Posted Oct 13, 2024 13:10 UTC (Sun) by MaZe (subscriber, #53908)In reply to: Insulating layer? by johill
Parent article: On Rust in enterprise kernels
Furthermore ={ 0 } and ={} aren't quite the same thing, but ={} isn't supported by older compilers...
= { 0 } used to mean zero init the whole thing though...
As for why it matters? Take a look at the bpf kernel system call interface.
The argument to the system call is a union of structs for the different system call subcases.
The kernel requires everything past a certain point to be zero-filled or it assumes the non-zero values have meaning (ie. future extensions) and it can't understand them and thus returns an error.
Another example is when a struct is used as a key to a (bpf) map.
Obviously the kernel doesn't know what portion of the key is relevant, so it hashes everything - padding included, so if padding isn't 0, stuff just doesn't work (lookups fail).
Yes, the obvious answer is to make *all* padding explicit.
Of course, this is also a good idea because userspace might be 32-bit and the BPF .o code is 64-bit, so you also don't have agreement on things like sizeof(long) or its alignment. You want (or rather need) asserts on all struct sizes.
Yes, once these gotchas get you, you know better, and there are workarounds...
But it's bloody annoying that you have to think about stuff like this.
There's ample other examples of C(++)-compiler-wants-to-get-you behaviours.
I just picked one single one...
[I guess some sort of 'sane C' dialect that actually says a byte is unsigned 8 bits, arithmetic is twos complement, bitfields work like this, shifts works like this, alignment/padding works like this, here's a native/little/big-endian 16-bit integer, basically get rid of a lot of the undefined behaviour, add some sort of 'defer' keyword, etc... would help a lot]
Posted Oct 13, 2024 18:00 UTC (Sun)
by marcH (subscriber, #57642)
[Link] (16 responses)
It's not just "bloody annoying": it's _buggy_ because there are and always will be many instances where the developer does not think about it.
Every single discussion about C++ safety always ends up in the very same "you're just holding it wrong" argument in one form or the other. Well, guess what: yes, there is _always_ someone somewhere "holding it wrong". That's because developers are humans. Even if some humans are perfect, many others are not. When humans don't want to make mistakes, they ask _computers_ to do the tedious bits and catch errors. You know, like... a Rust compiler.
And please don't get me started on Coverity and other external checkers: I've seen first hand these to be the pinnacle of "you're holding it wrong". Notably (but not just) because of the many false positives, which invariably lead some developers with an obvious "conflict of interest" to mark real issues in their own code as false positives while more experienced people have simply no time to look and must delegate blindly.
Advocates of C++ safety live in some sort of fantasy workplace / ivory tower where none of this happens and where they muse about technical and theoretical problems while being completely disconnected from the trenches where most coding happens. This is a constant failure to realize that usability comes first, technical details second. Same thing with security: the best design on paper is worth nothing at all if it's too complicated to use.
The only, last hope for C++ safety is for a large enough number of C++ experts to spend less time on benchmarks and agree on ONE[*] safe subset and implement the corresponding static analyzer and distribute it with LLVM by default. That's a very long shot and even then some users will still find ways to hold that static analysis "wrong" in order to make their deadline. A generous dose of "unsafe" keywords on legacy code will of course be the easiest way (cause some of these keywords will be actually required) but they will find many other ways.
I think it got a bit better now but until recently we were still hearing horrors stories based on _SQL injection_! If our industry can't get past a problem that dumb and so easy to spot, catch and kill, then who's going to bet on "safe C++"?
[*] ONE subset? No, let's have C++ "profileS", plural! More choice!
Posted Oct 13, 2024 19:48 UTC (Sun)
by mathstuf (subscriber, #69389)
[Link] (15 responses)
I sit in on SG23 (the "Safety and Security" study group for ISO C++) and participate in discussions, but have not contributed any papers (mostly I try to help provide accuracy improvements when statements about Rust come up as I have done a fair amount of Rust programming myself, something that is thankfully seeming to increase among other members as well). It is my understanding that multiple profiles is because:
- *some* progress has to be able to be made in a reasonable amount of time;
Though Sean Baxter (of Circle) et al. are working on the second item there, it is *far* more radical than things like "make sure all array/vector indexing goes through bounds-checked codepaths" that one has a hope of applying incrementally to existing codebases.
I believe the plan is to lay down guidelines for profiles so that existing code can be checked against them as well as other SGs coming to SG23 to ask "what can I do to help conform to profile X?" with their work before it is merged.
Posted Oct 14, 2024 10:19 UTC (Mon)
by farnz (subscriber, #17727)
[Link] (14 responses)
From the outside, though, what seems to be happening is not "there is a goal of a 'one true safe profile', such that all code can be split into an 'unsafe' part and a 'safe' part, but to make progress, we're coming up with ways for you to say 'I comply only with this subset of the safety profile'", but rather "let 1,000 profiles conflict with each other, such that you cannot combine code written under different profiles safely". This has the worrying corollary that you expect 'safe' C++ to be impossible to safely compose, since there's no guarantee that two libraries will choose the same safety profile.
If it was clear that the intent is that there will eventually be one true profile that everyone uses, and that the reason for a lot of profiles existing is that we want to make progress towards the one true profile in stages, then there would be less concern.
Posted Oct 14, 2024 14:30 UTC (Mon)
by mathstuf (subscriber, #69389)
[Link] (13 responses)
Posted Oct 14, 2024 16:34 UTC (Mon)
by farnz (subscriber, #17727)
[Link] (12 responses)
For all practical purposes, there's no difference between 2 conflicting profiles and 1,000 profiles. The key bit is that you're perceived (by people showing up to ISO) as adding more profiles to "resolve" conflicts between the ultimate goal, rather than working towards a single safe C++ profile, with multiple supersets of "allowed" C++ to let you get from today's "C++ is basically unsafe" to the future's "C++ is safe" in stages.
Ultimately, what's scary is that it looks like the idea is that I won't be allowed to write code in safe C++, and mix it with safe C++ from another team, because we might choose different profiles; I can only see two paths to that:
Otherwise, you end up with the language fragmenting; if you have 3 profiles that conflict, and I write in profile 1, while you write in profile 2, the combination of our code is no longer safe C++. The only way to avoid this is to mandate that we all use the same profile; but then you've spent all that effort writing your profiles, only to see it wasted because all but 1 profile is not used.
Posted Oct 14, 2024 17:34 UTC (Mon)
by mathstuf (subscriber, #69389)
[Link] (11 responses)
Where do you forsee any profiles conflicting in such an incompatible way? Sure, it could happen, but there are specific broad profiles proposed and I'm not aware of anything inherently being in conflict. I'd be surprised if anything like that were known during the design and discussion and it not be addressed.
Posted Oct 14, 2024 18:19 UTC (Mon)
by smurf (subscriber, #17840)
[Link]
What happens if I code to profile A and need to call some library function that changes the state of some variable to, let's say from shared to unshared (assuming that this is of concern for Profile A; could be anything else), but which isn't annotated with profile A's directives — because it's from library B which is coded according to profile C (or no profile at all) instead?
Answer: you spend half your productive time adding profile A's extensions to B's headers, and the other half arguing with your manager why you're even using B (and/or A) when you have to put so much nonproductive effort into it.
The result will be that you now have a fork of B. Alternately you spend even more time *really* fixing its profile-A-violations instead of just papering them over (assuming that you have its source code, which is not exactly guaranteed), sending its authors a pull request, and even more time convincing them that coding to Profile A is a good idea in the first place — which will be an uphill battle if the internals of B aren't easily convertible.
Contrast this kind of what-me-worry attitude with Rust's, where spending considerable effort to avoid "unsafe" (if at all possible) is something you do (and your manager expects you'll do, as otherwise you'd be coding in C++) because you fully expect that *every* nontrivial or not-carefully-reasoned-out use of it *will* bite you sooner or later.
Posted Oct 14, 2024 18:32 UTC (Mon)
by farnz (subscriber, #17727)
[Link] (9 responses)
The versions I've heard about from SG23 members who pay attention to these things are cases where the different profiles assume different properties of "safe C++", such that if I, in module C, import modules A and B that each uses a different profile of "safe C++", the interactions between modules A and B through their safe interfaces, as intermediated by C, result in the safety guarantees made by their respective safety profiles being broken.
To put it in slightly more formal terms, each profile needs to be a consistent axiomatic system, such that anything that cannot be automatically proven safe using the profile's axioms is unsafe, and the human is on the hook for ensuring that the resulting code is consistent with the profile's axioms. The problem that multiple profiles introduce is that all pairs of profiles need to be relatively consistent with each other, or any complete program where two modules use different profiles has to be deemed unsafe.
I think we agree that "two modules using different profiles means your entire program is unsafe" is a bad outcome. But I'm arguing that fixing the problem of "we can't agree on axioms and theorems in our safe subset" by having multiple sets of axioms and theorems that must be relatively consistent with each other is simply expanding the amount of work you have to do, for no net gain to C++.
Posted Oct 14, 2024 20:34 UTC (Mon)
by mathstuf (subscriber, #69389)
[Link] (8 responses)
Yes…they wouldn't be different if not. But I guess I'm having trouble seeing where these conflicts live given that no specific rules have been laid down to even compare yet. The boundaries of where guarantees happen is certainly something that needs to be considered. The situation is going to be messier than Rust, but it's a lot harder to get moving with billions of lines of code you can't rewrite.
I think it's more along the lines of "module A says it has bounds safety and module B says it has thread safety". Module C using them together doesn't mean B can't mess up bounds or A gets itself in a thread-related problem by the way they get used together, but the thread bugs shouldn't be in B and the bounds bugs shouldn't be in A.
Posted Oct 14, 2024 21:04 UTC (Mon)
by farnz (subscriber, #17727)
[Link] (7 responses)
Unless the profiles are subsets of a "final" target, it's very hard to avoid accidental conflicts based on assumptions; for example, module A says it has bounds safety, but the assumptions in the "bounds safety" profile happen to include "no mutation of data, including via calls into module A, from any module that does not itself have bounds safety", and module B does not have bounds safety, thus causing module A to not have bounds safety because module B calls a callback in module C that calls code in module A in a way that the bounds safety profile didn't expect to ever happen unless you have both the bounds safety and thread safety profiles on module A. As a result, module A claims to have bounds safety, but module A has messed up bounds because it was "bounds safety on the assumption of no threading from modules without bounds safety".
Now, if it looked like SG23 were doing this because they knew it would make things a lot harder, and probably put off safe C++ until C++40 or later, I'd not be so concerned; but the mathematical nature here (of axiomatic systems) means that by splitting safety into "profiles", you've got all the work you'd have to do for a single "safe C++", plus all the work involved in ensuring that all the profiles are consistent with each other in any combination - and as the number of combinations of profiles grows, that work grows, too. If you have a "bounds safety" profile and a "thread safety" profile, then you need to ensure that the combination of bounds safety and thread safety is consistent. But you also need to ensure that bounds safety plus not thread safety is consistent, and that thread safety plus not bounds safety is consistent, and so on. Add in a third profile, and now you have to ensure consistency for all three profiles at once, all three cases of 1 profile, and all 3 pairs of profiles, and it just gets worse as the profile count goes up.
Posted Oct 16, 2024 13:43 UTC (Wed)
by mathstuf (subscriber, #69389)
[Link] (6 responses)
I don't think any profile can guarantee that it can't be subverted by such situations (e.g., even Rust guarantees can be broken by FFI behaviors). The way I forsee it working is that profiles get enough traction to help avoid some kind of "no C++ anymore" regulations by instead allowing "C++ guarded by profiles X, Y, Z" with ratcheting requirements as time goes by. If you need bounds safety, you need bounds safety and the bug needs to be addressed in the right location.
Thanks for the discussion, by the way. I'll keep this in mind as I participate in SG23 to help keep an eye on it.
Posted Oct 16, 2024 14:50 UTC (Wed)
by Wol (subscriber, #4433)
[Link] (5 responses)
So it's opt-in (so it won't break existing programs), but would get rid of a huge swathe of programmer logic errors. And it might even help different profiles work together ...
Cheers,
Posted Oct 16, 2024 16:09 UTC (Wed)
by mathstuf (subscriber, #69389)
[Link] (3 responses)
Posted Oct 16, 2024 17:48 UTC (Wed)
by Wol (subscriber, #4433)
[Link] (2 responses)
Division by zero I think is simple - "if the hardware supports it, set the value to IEEE Infinity. Otherwise if the compiler detects it it's compiler error else it's a runtime error. Allow the user to override the hardware and crash".
I'd be quite happy with an answer of "The compiler can't detect it, so not our problem".
But I'm thinking of the easy wins like "signed integer overflow is UB". At present I gather most compilers optimise your naive programmers' test for it away on the grounds of "if it can't happen then the following code is dead code". Most compilers have a flag that defines it as 2s complement, I believe, though that's not the default.
So I'm guessing that I should have worded it better as "All UB that the compiler can detect and act upon needs to have that action documented, and options provided to configure sane behaviour (for the skilled-in-the-arts end-developer's definition of sane). And standard bundles of such options could/should be provided".
As they say, the whole is greater than the sum of its parts, and you will get weird interactions between optimisations. But if the "safe profiles" emphasise the end-developer's ability to know and control how the compiler is interpreting UB, we (a) might get more predictable behaviour, (b) we might get more sensible conversations about what is and is not possible, and (c) we might get fewer unpleasant surprises from those weird interactions, because end-developers will disable optimisations they consider not worth the candle.
Effectively what the linux kernel is already doing to some extent. But we want to get rid of the masses of options that you need a Ph.D. to understand, and just have a few "here this gives you a sane bundle of options". It's rather telling that most of the options linux specifies are --no-f-this, --no-f-that ... Actually, that "sane bundle of options" sounds like what -O1, 2, 3 should be doing, but I strongly get the impression it isn't.
Cheers,
Posted Oct 16, 2024 18:18 UTC (Wed)
by smurf (subscriber, #17840)
[Link]
This is an "easy win" in the sense that it's a solved problem: both gcc and clang have a '-fwrapv' option.
Most UBs are *not* "easy wins". The past discussion here should have told you that already.
Posted Oct 16, 2024 18:43 UTC (Wed)
by mathstuf (subscriber, #69389)
[Link]
And if thread 1 wins on the first byte and thread 2 on the second byte, which is "last"? Reading it gives a value that was never written. I believe the JVM handles this by saying that *a* value is returned, but it doesn't have to be a value that was ever written.
> Division by zero I think is simple - "if the hardware supports it, set the value to IEEE Infinity. Otherwise if the compiler detects it it's compiler error else it's a runtime error. Allow the user to override the hardware and crash".
Sure…assuming IEEE support and things like `-fno-fast-math`. What do you propose for integer division here?
> I'd be quite happy with an answer of "The compiler can't detect it, so not our problem".
So…can the compiler optimize based on any assumptions around this behavior? I mean, if the behavior is implementation-defined to be "I dunno", what kinds of as-if code transformations are allowed?
> Actually, that "sane bundle of options" sounds like what -O1, 2, 3 should be doing, but I strongly get the impression it isn't.
In general, the "optimization level" flags are a grabbag of heuristics and wildly varying behavior across implementations. For example, Intel's toolchains turn on (their equivalent of) `-ffast-math` on `-O1` and above.
Posted Oct 16, 2024 16:58 UTC (Wed)
by marcH (subscriber, #57642)
[Link]
https://blog.llvm.org/2011/05/what-every-c-programmer-sho...
Insulating layer?
Insulating layer?
- waiting until all of C++ is covered by a single "safe" marker is likely to take until something like C++32 (nevermind all of the things added between now and then); and
- even if a single "safe" marker were possible, deployment is nigh impossible if there's not some way to attack problems incrementally rather than "here are 3000 diagnostics for your TU, good luck".
Insulating layer?
Insulating layer?
Number of profiles is kinda irrelevant here
Number of profiles is kinda irrelevant here
Number of profiles is kinda irrelevant here
Number of profiles is kinda irrelevant here
Number of profiles is kinda irrelevant here
Number of profiles is kinda irrelevant here
Number of profiles is kinda irrelevant here
Number of profiles is kinda irrelevant here
Wol
Number of profiles is kinda irrelevant here
Number of profiles is kinda irrelevant here
Wol
Number of profiles is kinda irrelevant here
Number of profiles is kinda irrelevant here
Number of profiles is kinda irrelevant here