3 ms·
> If an invariant (something we believe absolutely must be true) is violated, the only sensible thing to do is abort the process. For example, if you set a poin
by lelanthran 2y ago
> If an invariant (something we believe absolutely must be true) is violated, the only sensible thing to do is abort the process. For example, if you set a pointer to NULL and then half an hour later it's time to store a value in that pointer but somehow it's not NULL anymore: clearly something has gone terribly wrong, either in your logic, or potentially in totally unrelated code in some other library which has scribbled on the heap.
Then you return an error and let the caller deal with it.
There is no justification, ever, for a library aborting the caller. Even in the scenario you present, it's still better to let the caller deal with the fallout.
> If execution is allowed to continue, the resultant behaviour is totally undefined: you might corrupt data, or you might allow a permissions check to pass that should have failed, etc.
So? that corrupted data or failed permissions check already happened before the library gets to abort anyway.
Let the caller do whatever they can before the process aborts; don't abort for them before the caller has more context than the library does. If you abort without returning an error to the caller, the caller cannot do things like log "Hey, that previous permission check we allowed might have been allowed by accident".
Your way provides absolutely no upside at all.
- burntsushi 2y ago> Then you return an error and let the caller deal with it. No, this is absurd. C libraries generally don't do this either. Instead, they might just have UB instead where as Rust tends to panic. So in practice, your suggestion ends up preferring vulnerabilities as a result of UB versus a DoS. See: https://news.ycombinator.com/item?id=43300120 https://news.ycombinator.com/item?id=43300120 Can you show me C libraries you've written that are used by others that follow your strategy of turning broken runtime invariants into error values?
- lelanthran 2y ago> No, this is absurd. Just to be clear, you are making the argument that when a library call detects an error of the form of unexpected NULL/not-NULL, that they abort immediately? To be even more clear, I'm not making the argument that the program should proceed as normal after detecting an error (regardless of the type of error). That is not the argument that I am making which is why I find your "That's absurd" condescension extremely confusing.
- burntsushi 2y agoMy position is articulated here, with real examples: https://burntsushi.net/unwrap/ https://burntsushi.net/unwrap/ > Just to be clear, you are making the argument that when a library call detects an error of the form of unexpected NULL/not-NULL, that they abort immediately? There's no blanket answer here because your scenario isn't specific enough. Is the pointer caller provided? Is the pointer entirely an internal detail whose invariant is managed internally? Is this pointer access important for perf? Is the pointer invariant encapsulated by something else? Instead, I suggest showing examples of C libraries following your philosophy. Then we'll have something concrete. In the comment I linked, you'll noticed that I actually looked at and reviewed real code examples. Maybe you could engage with those.
- vacuity 2y agoI happen to agree with @lelanthran's position, but aside from that I think their point is not that there are C libraries following their principle, but rather that libraries should follow it. This is akin to Rust's "get"/"try" style of fallible methods, avoiding both UB and exceptions/panics. It also seems moot to ask this of typical C libraries, as they wouldn't use exceptions either. (I've edited this multiple times by now, apologies if it's confusing. Only adding things to it, but it may read weirdly as I've reconsidered what I'm trying to say.) For something like array indexing in Rust, it's not bad to have a panicking operator by default because it's very upfront and largely desired. Similarly, a library may document when it panics just as it would document its error type if it returned error values. But something that I would consider very bad design is if I use a library that spawns another thread and does some file processing and can panic, without making this clear to me. I think one of your main points is, suppose a library theoretically could index an array OOB and panic; it is not formally verified not to and so the developer is just covering all bases conveniently. The normal alternative being UB is of course unacceptable. There is a crucial distinction to be made here. If the index is derived from the application, return an error value making this clear to the application. However, at some point the index may be considered only internally relevant. I agree this is fine. The thought is that this will never trigger and the application will be none the wiser. If it is ever triggered, the library should be patched quickly. I think is not all the panics that people in this thread have in mind, as otherwise panics should be seen basically never, just as a well-designed but otherwise normal C program would have risk of UB but should exhibit this basically never. There should be an effort to minimize panics to the ones that are just sanity checks and only there for completeness, rather than a convenient way to handle failure. With panics, either I just let them happen when they will or I have to defensively corral the library into working for me. With error values, the library has set out to state its terms and conditions and I can be happy that the burden is on me to use it properly. I have more control over the application's behavior from the start, and the extra work to surface errors to users properly is more or less equal between both approaches. Yes, panics can also be laid out in the API contract. But it's more enforceable with error values. If there was a good way to do error-values-as-exceptions (automating Result bubbling with ?) that just panics up until a good boundary and returns a Result, that's basically catch_unwind but cleaner. It's true that oftentimes aborting (perhaps after cleanup) is the best way to handle errors, but it shouldn't be a struggle to avoid that when I know better. Particularly with C's malloc(): maybe I do want to change my program behavior upon failure instead of stopping right then and there.
- kazinator 2y agoA Unix kernel will kill your process just because it wrote into a broken pipe.