Next: C++/CLI limitations?
From: Gerhard Menzl on 25 Aug 2005 19:04 Bob Bell wrote: > It seems to me that the problem being discussed is largely one of > definition of terms. Yes, that's what it has turned into. > As I understand it, Dave is advocating a definition of precondition > that reads something like "a condition which must be true when a > function is called, or else we have undefined behavior." I agree with > this definition, and view it as synonymous with "a condition which > must be true when a function is called, or else the function cannot > continue." "The function cannot continue" would allow for throwing an exception or returning an error code, "we have undefined behaviour" would not, hence the two are not synonymous. > It sounds like you want a definition that reads something like "a > precondition is a condition which is tested when a function is called; > if true, the function runs as normal; if false, the function throws an > exception." No, I don't *want* a particular definition. What I was hoping to get is a clear definition that is consistent with the statement that throwing an exception upon detecting a precondition violation is almost always wrong. So far I haven't seen one, and my hopes have dwindled. As to the role of undefined behaviour, I have seen: - a violated precondition causes undefined behaviour - a violated precondition may be the result of undefined behaviour - once you detect a violated precondition, you already have undefined behaviour - continuing after detecting a violated precondition would cause undefined behaviour All these are different in subtle ways. I have also tried to understand whether the definition used by David is specific to C++ or consistent with the notion of precondition used by the programming community in general, and with the concept of Design by Contract in Eiffel in particular. > Suppose you have a function F() that is documented to throw X when > condition Y fails. Suppose you call F(), and Y legitimately fails. You > don't have undefined behavior, an X is thrown, and life goes on. > > If, however, F() is called and Y appears to fail because some > undefined behavior has occured (such as stack corruption or whatnot), > well now you're in undefined behavior land, and anything could happen. > The program could crash, wipe your hard disk, or throw an X. If it > manages to actually throw an X, it doesn't change the fact that your > program is now exhibiting undefined behavior. > > The key point is that your condition Y did not help you detect and > avoid undefined behavior. Doesn't the very definition of undefined behaviour render its detection once it has occurred impossible? I don't see any point in treating precondition violations separately on the grounds that they may be the *result* of undefined behaviour: that is true for any program state, and you cannot detect it anyway. Treating them separately because going on as if nothing had happened would *lead to* undefined behaviour is an altogether different story. To me, mixing these aspects is a major obstacle in this discussion. -- Gerhard Menzl #dogma int main () Humans may reply by replacing the thermal post part of my e-mail address with "kapsch" and the top level domain part with "net". [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ]
From: Joe on 25 Aug 2005 19:14 Interesting how phrasing can be very subtle. To me, preconditions are those conditions the caller must meet before my routine has well defined meaning. That is, I am willing to guarantee to the caller that my routine will work if these conditions are met and further I believe this is the original intent of DBC. Stack corruption is a different thing entirely and nothing useful can be done about it. While it is a violation of the contract, it is not a very interesting one. If the stack is corrupt, evaluating the precondition is already undefined behavior and there is no reason the believe that the precondition check itself will return anything meaningful. I am relatively neutral as to whether exceptions should be used to return a precondition failure, but I firmly believe that they have nothing to do with stack corruption detection and the point is really that the caller is invoking our routine correctly so that they can correct the offending code if the preconditions are not true. In other words, much like static type checking, preconditions are there to catch programming errors of a dynamic nature, such as accidentally passing a 5 where the on;y sensible values are between 100 and 500. Stack corruption is a much more insideous problem which we would be thrilled to catch with a precondition, but that would not be the reason I would put a precondition in the code. joe [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ]
From: Bob Bell on 26 Aug 2005 05:47 Gerhard Menzl wrote: > Bob Bell wrote: > > As I understand it, Dave is advocating a definition of precondition > > that reads something like "a condition which must be true when a > > function is called, or else we have undefined behavior." I agree with > > this definition, and view it as synonymous with "a condition which > > must be true when a function is called, or else the function cannot > > continue." > > "The function cannot continue" would allow for throwing an exception or > returning an error code, "we have undefined behaviour" would not, hence > the two are not synonymous. I should be more specific. I interpret "the function cannot continue" to mean that the function shouldn't be allowed to execute a single instruction more, not even to throw an exception. > > It sounds like you want a definition that reads something like "a > > precondition is a condition which is tested when a function is called; > > if true, the function runs as normal; if false, the function throws an > > exception." > > No, I don't *want* a particular definition. What I was hoping to get is > a clear definition that is consistent with the statement that throwing > an exception upon detecting a precondition violation is almost always > wrong. So far I haven't seen one, and my hopes have dwindled. Then how about a more pragmatic definition? When a precondition fails, it almost always indicates a programmer error (a bug). When a bug occurs, the last thing you want is to unwind the stack: -- unwinding the stack destroys state that could help you track down the bug -- unwinding the stack may do more damage -- throwing an exception allows the bug to go unnoticed if a caller catches and swallows it (e.g., catch (...)) -- throwing an exception gives a (possibly indirect) caller a chance to respond to the bug; typically, there isn't anything reasonable a caller can do to respond to a bug What you really want is to stop the program in a debugger, generate a core dump, or otherwise examine the state of the program at the instant the bug was detected. If you throw an exception, you're just allowing the program to continue running with a bug. > I have also tried to understand whether the definition used by David is > specific to C++ or consistent with the notion of precondition used by > the programming community in general, and with the concept of Design by > Contract in Eiffel in particular. I don't think it is; from what I understand about Eiffel (which is little) the aim is to keep the program running if a contract is broken. But I could be wrong about that. > > Suppose you have a function F() that is documented to throw X when > > condition Y fails. Suppose you call F(), and Y legitimately fails. You > > don't have undefined behavior, an X is thrown, and life goes on. > > > > If, however, F() is called and Y appears to fail because some > > undefined behavior has occured (such as stack corruption or whatnot), > > well now you're in undefined behavior land, and anything could happen. > > The program could crash, wipe your hard disk, or throw an X. If it > > manages to actually throw an X, it doesn't change the fact that your > > program is now exhibiting undefined behavior. > > > > The key point is that your condition Y did not help you detect and > > avoid undefined behavior. > > Doesn't the very definition of undefined behaviour render its detection > once it has occurred impossible? You're right, undefined behavior, as defined by the language standard, is undetectable once it's occurred. It's clear from you response that applying the term "undefined behavior" to preconditions has been misleading. In the interest of clarity, I'm going to switch to "undefined state". Example: F() is documented to specify that it is the responsibility of all callers to establish condition Y. Now suppose F() is called and Y is false. What does this mean? All you know is that some caller failed to establish Y. Assuming the contract was valid and reasonable, you have detected a bug. (Even if the contract was invalid, you've still detected a bug -- only the bug is that F() demands condition Y.) In practical terms, the program has entered an undefined state -- it's doing something you didn't think it could do. Whether it entered the undefined state before Y was tested, as a result of testing Y, etc., is not that important, as far as I'm concerned. What's important is what you do about it. If you throw an exception, you allow the program to continue running. But since it's entered an undefined state, you don't know what it will do. This is not exactly the same as "undefined behavior" as defined in the standard, but it shares a lot of similarities. "Undefined behavior" means the language standard has nothing to say about what the program will do. "Undefined state" means that you, the programmer, have nothing to say about what the program will do. Getting back to my definition from my previous message, a precondition is "a condition which must be true when a function is called, or else the program has entered an undefined state." If this happens, I believe that the right thing to do is stop the program, so I see this as synonymous with "a condition which must be true when a function is called, or else the function cannot continue." > I don't see any point in treating precondition violations separately on > the grounds that they may be the *result* of undefined behaviour: that > is true for any program state, and you cannot detect it anyway. Treating > them separately because going on as if nothing had happened would *lead > to* undefined behaviour is an altogether different story. To me, mixing > these aspects is a major obstacle in this discussion. I think the right way to think about preconditions is that they detect bugs. Bob [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ]
From: Dave Harris on 27 Aug 2005 15:39 gerhard.menzl(a)hotmail.com (Gerhard Menzl) wrote (abridged): > As the author of get_count(), I can tell that at present a violation of > the precondition will have no consequences, and that undefined > behaviour will be invoked as soon as the function is changed to > dereference p. That's what I call a clear idea. I don't think you /need/ a clear idea. I sometimes assert things I expect to be true without thinking at all about the consequences if they aren't true. > How does the Eiffel runtime handle violated preconditions? Display a > diagnostic message and abort? If checking is enable, it throws an exception. If not, then the program proceeds normally and has undefined behaviour. Programmers are encourage to write code as if checking is disabled. -- Dave Harris, Nottingham, UK. [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ]
From: David Abrahams on 27 Aug 2005 15:32
Gerhard Menzl <gerhard.menzl(a)hotmail.com> writes: >>>By the way, the term "precondition" seems to be used in meanings that >>>differ from the one you have sketched. In the Eiffel world (as far as >>>I can tell), a precondition is something that the caller must >>>guarantee before invoking a function. >> >> In my definition the caller must guarantee the precondition also, >> since the alternative is undefined behavior. I don't know what I've >> said that could make you think otherwise. > > For example: > >> Yes, any precondition violation could be the result of stack >> corruption. > > An intact stack is a global condition that cannot be guaranteed a single > caller. So? I didn't say that an intact stack is a precondition. I just said that a corrupted stack could easily result in any precondition being violated. > Stack corruption could be the result of a broken compiler or of > user code that invokes undefined behaviour somewhere else. Both events > lie outside the language. They cannot be detected reliably and portably > at this level, and they are not covered by the contract between the > caller and the called. From this perspective, they're more like Acts of > God, to extend the law metaphor. Yet you seem to regard such global > failures in the context of the contract. I find this confusing and at > variance with the idea of DbC as I understand it. Let me put it another way: if a single caller cannot guarantee an intact stack, it cannot guarantee anything. A corrupted stack throws everything into doubt. Even if the caller's author _thinks_ he is testing some condition and guaranteeing it to the callee, if the stack is corrupt, any otherwise-correct test could easily give false results. That's why, in general: Preconditions are not ensured by the caller testing for them and somehow deciding to avoid making the call. They are ensured by reading the guarantees made to the caller by the other functions it is calling, reading the requirements the caller makes on *its* callers, and combining that information using logic to form a little proof that the conditions hold. It's fine for the caller to use asserts or whatever to check his own logic and root out bugs from his code. However, it's impossible to write a program that meets any useful specification when at any moment the programmer's understanding of the program state could turn out to be wrong, so those tests can at best be a debugging tool -- you'd better write your program as though they're going to pass. You have to decide on a baseline context inside of which your guarantees live. "Obviously" if some code executed earlier induces undefined behavior (e.g. by corrupting the stack), your guarantees are just as meaningless as if radiation had inverted a few bits in memory. >>>This would rule out stack integrity or other global conditions which >>>the caller cannot guarantee. Once again, I never said that stack corruption was a precondition violation. You seem to be looking hard for ways to find that what I've said is somehow inconsistent or incoherent. Poking holes in arguments I never made seems sorta pointless. It doesn't sound to me as though you're trying to understand what I'm saying; rather, it seems much more as though you simply don't _like_ what I'm saying. If that's so, I'd like to stop trying to explain myself now. If not, I apologize in advance for even asking. >> If the caller is invoking the callee other than as some expression of >> undefined behavior, it can. I'm assuming that even in Eiffel, the >> moment stack integrity is violated you have undefined behavior. Once >> you enter undefined behavior land, all bets are off and all actions >> are part of that undefined behavior. Undefined behavior means "all >> bets are off" and no guarantees (not even the ones you /think/ the >> caller can ensure) are really valid. > > A violated precondition causes undefined behaviour. That doesn't > mean undefined behaviour caused at a different level (like a broken > compiler) can be regarded as a breach of a local contract. No, it simply makes the local contract somewhat meaningless, unless there is strong insulation between the levels (as with processes). A broken compiler affects everything at a deep level, so there's no insulation. >>>Another conflicting use I have found is from P. J. Plauger's Editor's >>>Forum in the June issue of C/C++ Users Journal where he writes about a >>>new C library called "Safer C": "But any implementation of the >>>function must test that its arguments meet all preconditions. The >>>runtime equivalent of a diagnostic is to call the diagnostic handler. >>>If the handler returns, the function then cauterizes any output >>>buffers and returns an error code." >> >> I'm not sure that's a conflict either. > > To me, it is in glaring conflict with > >> The moment you say say, "I can do something reliably here if I detect >> a violation," then the condition being violated is -- by definition -- >> no longer a precondition because you're defining what the behavior >> should be when it happens. If you are merely suggesting that Microsoft's "Safer C" specification uses a different concept of the term "precondition," which allows a documented response to violations, you'll get no argument from me on that point. I never claimed that everyone in the world has the same concept. Clearly you and I don't, and I daresay the fact that somebody at Microsoft disagreed with me certainly doesn't prove anything about the coherence of my arguments. I am making only the following claims: 1. That any concept of "precondition violation" that allows the callee to guarantee a particular response to a violation is very weak and close to useless. It's logically indistinguishable from any other documented behavior, aside from attaching some meaningless moral judgement to the behavior ("violated preconditions are 'bad'; other documented behaviors are 'good'"). A technical term is much more powerful and useful when it distinguishes one thing from another. Okay, you could use "precondition violation" as a shorthand for "produces the following category of guaranteed behavior," as the "safer C" spec appears to do. That brings me to... 2. The concept of precondition violation that allows the callee to guarantee a particular response to a violation is usually bad for callers. Because it's logically indistinguishable from any other documented behavior, the caller almost invariably treats those responses in the same way as "error" returns (e.g. resource exhaustion, file locked, etc.) that can actually happen in correct code. That results in either haphazard "recovery" code that doesn't actually work consistently, or a huge overhead in code that tries to accomodate these responses-to-one's-own-bugs correctly. Even if you do the latter you still end up with the former, most of the time. -- Dave Abrahams Boost Consulting www.boost-consulting.com [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ] |