4 ms·
May. If. If. If. In case. We are talking about an extremely simple straightforward API with an obvious contract. It's good enough for this function to reliably
by jstimpfle 4mo ago
May. If. If. If. In case.
We are talking about an extremely simple straightforward API with an obvious contract. It's good enough for this function to reliably surface almost all wrong uses with a segfault immediately. Wrong use will result in segfaults and otherwise bugs and crashes. The goal is not to work when used wrong but to work when used right. You cannot save the world from scratch in every little function. You still have a job to get done, and you have to move on.
- lmm 4mo ago> You cannot save the world from scratch in every little function. You still have a job to get done, and you have to move on. Or you can take all of 10 minutes to put sanity-check assertions at the start of all your public-facing API functions, eliminating a source of security bugs, get on with your life, and worry about the performance implications as and when it becomes a problem (hint: it's never going to become a problem).
- dwattttt 4mo agoIt takes a lot longer to figure out if it'll be a problem than to just add the check. And you don't have to ponder whether it's possible for a null to get there, because now it's fine if it does.
- jstimpfle 4mo agoAre you talking about extending the API contract to allow for NULL? That is often the path to madness, especially if it requires complicating the signature (return value etc). Better to just assert/crash.
- dwattttt 4mo agoNo. I'm talking about adding the check to reject NULL. Then you don't have to spend time justifying or figuring out why a NULL can't turn up here.
- jstimpfle 4mo agoSo reject as in assert? But how does that go together with what you said, "because now it's fine if it does"?
- dwattttt 4mo agoBecause no one is expecting it to work if a null is passed. Your total range of behaviours left are crashes, doesn't crash and is silently ok, or doesn't crash and causes something worse (data corruption, you get your product in a CVE, that area). My proposition is that "it's silently ok" isn't likely enough, which is in line with your position on "don't extend the contract to accept null". So what's left is crash, or something worse. So if those are your choices, don't waste time justifying that a null can't get there, just add a check to ensure you get the better behaviour. It takes seconds.
- jstimpfle 4mo agoIf you follow that line of reasoning, you will end up testing almost every pointer before accessing it. The reason is that you are extending your valid state space massively since you aren't able to specify "this subset of 7 trillion distinct states is invalid, if it was the case we would have failed before". You are requiring yourself to find a valid outcome for an input that doesn't make _any_ sense in the context of what your application is meant to achieve. How is that not a Sysiphean task?
- dwattttt 4mo agoYou're not "extending" the valid state space. That null value being passed to that function is already a potential state of your program. You're actually pruning the valid state space; before, when the null value is passed to the function, there are more operations performed that have uncertain consequences. If you assert-and-fail when you get the null input, you've pruned those states.
- jstimpfle 4mo ago
- jstimpfle 4mo agoYou can try and do this if it's a relatively narrow public facing API, but otherwise this is a theoretic ideal. In practice, if you add an assertion for every pointer argument to every little function, you'll go insane, and it is completely pointless, and the code will not be readable anymore. There are so many other interesting and relevant invariants that are usually in an API contract that are much harder or impossible to check upfront (let alone express formally in a type system), and even violations may be impossible to diagnose when they happen. People focus on NULL because that's the only way they can apply their silly limited type systems. But NULL checks give very little return for investment. In practice, you'll see templated Option<T> types and whatnot, and when I have to look at or even work with such code I want to kill myself because it's so painful.
- lmm 4mo agoNo, people focus on a handful of things like null, buffer overrun, and use-after-free because they still make up the majority of security vulnerabilities that we see exploited in the wild. You may imagine that subtle logic errors are more common, but the data doesn't bear that out; also FWIW I've never seen one of these detailed invariants be impossible to express in a type system if you spend 5 minutes actually trying.
- jstimpfle 4mo agoTypical invariant for me would look like: Given a, b, c input parameters to my func, it must hold that that a->m->t == b->t. c->mutex must be held, and c->cond is the condition variable that goes with c->mutex and will release any waiters on the buffer contained in a. Or: Integer x is representable using 12 bits only, Integer y should be a multiple of N and I have a integer s is used as a bit-shift that should be less than 8. Or: I need to guarantee that no locks have to be taken and no allocations have to be made on this complicated looking codepath. While holding a lock, we must not do any syscalls (syscall a, b, c are ok though), and surely not make any logging calls. I know only one system that can express this, it's called STRAIGHTFORWARD CODE, and it requires doing engineering and casual logic out-of-band, and yes it does include making mistakes and repairing them incrementally. I don't know a type system that would let me explain these things to me and tell me where I was wrong. But maybe you can show me, with 5 minutes of actually trying?