Jump to page: 1 2
Thread overview
Assert and undefined behavior
Oct 11, 2017
John Burton
Oct 11, 2017
rikki cattermole
Oct 11, 2017
user1234
Oct 11, 2017
Eduard Staniloiu
Oct 11, 2017
Jonathan M Davis
Oct 11, 2017
Ali Çehreli
Oct 12, 2017
Timon Gehr
Oct 12, 2017
John Burton
Oct 12, 2017
kdevel
Oct 12, 2017
Jonathan M Davis
Oct 12, 2017
kdevel
Oct 13, 2017
Jonathan M Davis
Oct 13, 2017
kdevel
Oct 13, 2017
Jonathan M Davis
Oct 14, 2017
Jesse Phillips
Oct 14, 2017
Jonathan M Davis
Oct 14, 2017
Timon Gehr
Oct 14, 2017
kdevel
Oct 15, 2017
Timon Gehr
Oct 17, 2017
Jesse Phillips
October 11, 2017
The spec says this :-

"As a contract, an assert represents a guarantee that the code must uphold. Any failure of this expression represents a logic error in the code that must be fixed in the source code. A program for which the assert contract is false is, by definition, invalid, and therefore has undefined behaviour."

Now I worry about the words "undefined behavior" because in C++ compiler writers seem to have decided that these words mean that it's ok for the compiler to generate code to do whatever it feels like even in unconnected code and even before the undefined behavior is invoked because some subsequent code has undefined behavior.

From my C++ experience this paragraph tells me that if I use "assert" to check my assumptions, and the assertion is false, then this could lead to my program failing in unpredictable ways unconnected with the actual assertion.

I therefore feel like I ought to not use assert and should instead validate my assumptions with an if statement and a throw or exit or something.

I feel like a failing assertion should not cause "undefined behavior" in the sense it is commonly used in C++ programming these days but should have exactly defined behavior that it will do nothing if the assert passes and throw the specified exception if it fails. Can I safely assume this despite the wording?

I know this might seem like a small or pedantic point, but C++ compilers can and do use invoking undefined behavior as an excuse to do all kinds of unexpected things in generated code these days and I want to write safe code :) I feel that if D is specified in the same way then assert is not safe for me to use in a real program.
October 11, 2017
On 11/10/2017 10:27 AM, John Burton wrote:
> The spec says this :-
> 
> "As a contract, an assert represents a guarantee that the code must uphold. Any failure of this expression represents a logic error in the code that must be fixed in the source code. A program for which the assert contract is false is, by definition, invalid, and therefore has undefined behaviour."
> 
> Now I worry about the words "undefined behavior" because in C++ compiler writers seem to have decided that these words mean that it's ok for the compiler to generate code to do whatever it feels like even in unconnected code and even before the undefined behavior is invoked because some subsequent code has undefined behavior.
> 
>  From my C++ experience this paragraph tells me that if I use "assert" to check my assumptions, and the assertion is false, then this could lead to my program failing in unpredictable ways unconnected with the actual assertion.
> 
> I therefore feel like I ought to not use assert and should instead validate my assumptions with an if statement and a throw or exit or something.
> 
> I feel like a failing assertion should not cause "undefined behavior" in the sense it is commonly used in C++ programming these days but should have exactly defined behavior that it will do nothing if the assert passes and throw the specified exception if it fails. Can I safely assume this despite the wording?
> 
> I know this might seem like a small or pedantic point, but C++ compilers can and do use invoking undefined behavior as an excuse to do all kinds of unexpected things in generated code these days and I want to write safe code :) I feel that if D is specified in the same way then assert is not safe for me to use in a real program.

You misinterpreted it.

The program /could/ be in an invalid state because an internal state check (assert) says that it isn't.

What the compiler generates for the assert, depends upon the platform and if its building with optimizations. But all of them will end in the process crashing unless you go out of your way to handle it.

By default it throws and Error in debug mode, which you shouldn't be catching since it is an Error and not an Exception anyway.
October 11, 2017
On Wednesday, 11 October 2017 at 09:27:49 UTC, John Burton wrote:
> [...]
> I therefore feel like I ought to not use assert and should instead validate my assumptions with an if statement and a throw or exit or something.

Yes, that's the way of doing. assert() are just used to test the program. the -release option in DMD disable all the assert() (excepted assert(0) which is a bit special), so that in a release version, only Throwable objects can be used after a failure detected.

October 11, 2017
On Wednesday, October 11, 2017 09:27:49 John Burton via Digitalmars-d-learn wrote:
> The spec says this :-
>
> "As a contract, an assert represents a guarantee that the code must uphold. Any failure of this expression represents a logic error in the code that must be fixed in the source code. A program for which the assert contract is false is, by definition, invalid, and therefore has undefined behaviour."
>
> Now I worry about the words "undefined behavior" because in C++ compiler writers seem to have decided that these words mean that it's ok for the compiler to generate code to do whatever it feels like even in unconnected code and even before the undefined behavior is invoked because some subsequent code has undefined behavior.
>
>  From my C++ experience this paragraph tells me that if I use
> "assert" to check my assumptions, and the assertion is false,
> then this could lead to my program failing in unpredictable ways
> unconnected with the actual assertion.
>
> I therefore feel like I ought to not use assert and should instead validate my assumptions with an if statement and a throw or exit or something.
>
> I feel like a failing assertion should not cause "undefined behavior" in the sense it is commonly used in C++ programming these days but should have exactly defined behavior that it will do nothing if the assert passes and throw the specified exception if it fails. Can I safely assume this despite the wording?
>
> I know this might seem like a small or pedantic point, but C++ compilers can and do use invoking undefined behavior as an excuse to do all kinds of unexpected things in generated code these days and I want to write safe code :) I feel that if D is specified in the same way then assert is not safe for me to use in a real program.

If your assertions are failing, you're screwed anyway. If an assertion fails, then by definition, your program is in an invalid state and who knows what is going to happen. The whole point of assertions is to catch problems during development so that you ensure that your code is correct. Not using them is just making things worse for yourself.

The compiler _may_ use an assertion to inform its code generation by assuming that the assertion is true (which certainly wouldn't cause you any problems when not compiling with -release, since a failed assertions would throw an AssertError and kill your program), and yes, in theory, if you compile with -release, and an assertion would have failed, and the compiler did something that assumed that the assertion passed, then maybe things would be worse, but you're already screwed anyway, because your program is in an invalid state, because the assertion wasn't true. In reality, I expect that the compiler does very little at this point to optimize based on assertions, but any time that it actually does will generally benefit you.

If you're concerned about compiling with -release and having an assertion that would have failed not result in your program dying like it normally would, then you can use assert(0), which will be translated to a HLT instruction with -release. e.g.

if(!cond)
 assert(0);

instead of

assert(cond);

Regardless, you obviously shouldn't be using assertions for anything that depends on user input. They're for detecting bugs in your program during development. If you're truly using them for that, then you'll be better off using them, and not using them is just begging for your program to have more bugs that you didn't catch.

And the reality of the matter is that if you're looking to avoid undefined behavior in general, you're screwed. Yes, D is much more likely to define what should be happening than C/C++ is and thus has less undefined behavior in general, but not ever allowing for undefined behavior would really harm performance overall, because it really can help the compiler to optimize code when it's not forced to do something less efficient just to make it so that everything is fully defined, and pretty much any language you use is going to have at least some undefined behavior.

Assertions are a great way to help ensure that you catch bugs in your program, and if they can help the compiler optimize your code upon occasion, then all the better (though honestly, I expect that they rarely have any effect on the optimizer at this point given how little of the work on dmd is geared towards better optimizing code; some work is done in that area to be sure, but it's rarely the focus because of how much else needs to be done).

- Jonathan M Davis

October 11, 2017
On 10/11/2017 02:27 AM, John Burton wrote:
> The spec says this :-
>
> "As a contract, an assert represents a guarantee that the code must
> uphold. Any failure of this expression represents a logic error in the
> code that must be fixed in the source code. A program for which the
> assert contract is false is, by definition, invalid, and therefore has
> undefined behaviour."

The important part is that the "program" has undefined behavior according to *your* definition because you're the one who asserted that something should never have happened:

struct Square {
    int side;
    int area;

    invariant() {
        assert(area == side * side); // Could be inside foo()
    }

    void foo() {
    }
}

void main() {
    auto s = Square(1, 10);
    s.foo();
}

So, you think you wrote your program to never break that assertion. So, regardless of the reason for the failure (design error, memory corruption, hardware error, etc.), the program is outside of its well-defined state (by you).

> I know this might seem like a small or pedantic point

Not only this specific point, but almost everything about assertion failures are very important and very interesting. For example, according to the text you quoted; the code injected by the compiler, the one that dumps a backtrace for an Error, should not be executed either. You have no guarantee that that code will really dump a backtrace, whether the output will be correct, etc. :/ (There has been many many long discussions about these topics on the D newsgroups.)

What gives me comfort is the fact that life is not perfect anyway.[1] Things somehow seem to work fine. :)

Ali

[1] Another example is mouse clicks (and screen taps). We have no guarantee that we are clicking what we wanted to. Sometimes a new window pops up and you click some random button but it works in general.

October 11, 2017
On Wednesday, 11 October 2017 at 09:39:04 UTC, user1234 wrote:
> On Wednesday, 11 October 2017 at 09:27:49 UTC, John Burton wrote:
>> [...]
>> I therefore feel like I ought to not use assert and should instead validate my assumptions with an if statement and a throw or exit or something.
>
> Yes, that's the way of doing. assert() are just used to test the program. the -release option in DMD disable all the assert() (excepted assert(0) which is a bit special), so that in a release version, only Throwable objects can be used after a failure detected.

A small addition to the answers already provided.

As user1234 has already said, asserts are removed in the -release build, so, if you have to validate some assumption (ex. the file opened) you should use enforce[0].

Cheers,
Eduard

[0] - https://dlang.org/library/std/exception/enforce.html
October 12, 2017
On 11.10.2017 11:27, John Burton wrote:
> The spec says this :-
> 
> "As a contract, an assert represents a guarantee that the code must uphold. Any failure of this expression represents a logic error in the code that must be fixed in the source code. A program for which the assert contract is false is, by definition, invalid, and therefore has undefined behaviour."
> 
> Now I worry about the words "undefined behavior" because in C++ compiler writers seem to have decided that these words mean that it's ok for the compiler to generate code to do whatever it feels like even in unconnected code and even before the undefined behavior is invoked because some subsequent code has undefined behavior.
> 
> From my C++ experience this paragraph tells me that if I use "assert" to check my assumptions, and the assertion is false, then this could lead to my program failing in unpredictable ways unconnected with the actual assertion.
> 

Yes, that's what it is saying. (The other answers, that say or try to imply that this is not true or true but not a bad thing, are wrong.)

To make this more obvious, see:

http://forum.dlang.org/post/lrbpvj$mih$1@digitalmars.com

Refer to point 2. The fix is to not use both assert and -release.

However, in practice, I think none of the current compiler implementations actually uses assert expressions for optimizations.
October 12, 2017
On Thursday, 12 October 2017 at 14:22:43 UTC, Timon Gehr wrote:
> On 11.10.2017 11:27, John Burton wrote:

> Yes, that's what it is saying. (The other answers, that say or try to imply that this is not true or true but not a bad thing, are wrong.)
>
> ...
> 
>
> However, in practice, I think none of the current compiler implementations actually uses assert expressions for optimizations.

This is an example of what I mean :-

import std.stdio;

extern void control_nuclear_reactor(int[] data);

void test(int[] data)
{
    if (data.length == 0) {
        writeln("Not enough data!");
    } else {
        control_nuclear_reactor(data);
    }

    assert(data.length > 0);
}

So according to the spec, if data is size zero then the assert fails and therefore the code has **undefined behavour**. What this means in practice is that the compiler decides that it doesn't matter what code is generated for that case as it undefined what it is meant to do anyway, so the compiler can "optimize" out the if condition as it only affects the case where the language doesn't define what it's supposed to do anyway, and compiles the code as if it was :-

void test(int[] data)
{
    control_nuclear_reactor();
}

Which obviously could have very bad results if the test mattered.

Yes my program is invalid because I violated it's assumptions but I find it very hard to argue that including the assert should "break" the code before it.

C++ compilers can and do perform such optimizations so I was wondering if assert in D could cause such behavior according to the spec.
October 12, 2017
On Thursday, 12 October 2017 at 15:37:23 UTC, John Burton wrote:
> C++ compilers can and do perform such optimizations so I was wondering if assert in D could cause such behavior according to the spec.

In the context of ISO-C++ it is meaningless to reason about the "actual behavior" of a non-conforming program ("start WW III" etc.). You may find details here: <http://en.cppreference.com/w/cpp/language/ub>

As standard oriented C++ (or C or FORTRAN) programmers we avoid undefined behavior not because we would want to prevent WW III, but because we want to write and reason about conforming code only.

IIRC C++'s assert is defined in the ISO-C standard. There we can read:

"The assert macro puts diagnostic tests into programs; it expands to a void expression. When it is executed, if expression (which shall have a scalar type) is false (that is, compares equal to 0), the assert macro writes information about the particular call that failed [...] on the standard error stream in an implementation-defined format). It then calls the abort function."

So in C/C++

---
int main ()
{
   assert (0);
   return 0;
}
---

is a perfectly valid (conforming) program.

D ist not standardized (yet) hence there is no such thing as a "standard conforming D implementation" or a "standard conforming D program". The D documentation is simply the manual of a set of programs (compiler, tools) which may or may not be correctly be described therin. According to <https://dlang.org/spec/contracts.html> the program

---
void main ()
{
   assert (false);
}
---

qualifies as "invalid, and therefore has undefined behaviour." A statement, which makes no sense to me. Either it is a "debugging aid", that implies defined behavior, or it is undefined behavior, then assert (false) cannot aid debugging.
October 12, 2017
On Thursday, October 12, 2017 20:15:41 kdevel via Digitalmars-d-learn wrote:
> On Thursday, 12 October 2017 at 15:37:23 UTC, John Burton wrote:
> > C++ compilers can and do perform such optimizations so I was wondering if assert in D could cause such behavior according to the spec.
>
> In the context of ISO-C++ it is meaningless to reason about the "actual behavior" of a non-conforming program ("start WW III" etc.). You may find details here: <http://en.cppreference.com/w/cpp/language/ub>
>
> As standard oriented C++ (or C or FORTRAN) programmers we avoid undefined behavior not because we would want to prevent WW III, but because we want to write and reason about conforming code only.
>
> IIRC C++'s assert is defined in the ISO-C standard. There we can read:
>
> "The assert macro puts diagnostic tests into programs; it expands to a void expression. When it is executed, if expression (which shall have a scalar type) is false (that is, compares equal to 0), the assert macro writes information about the particular call that failed [...] on the standard error stream in an implementation-defined format). It then calls the abort function."
>
> So in C/C++
>
> ---
> int main ()
> {
>     assert (0);
>     return 0;
> }
> ---
>
> is a perfectly valid (conforming) program.
>
> D ist not standardized (yet) hence there is no such thing as a "standard conforming D implementation" or a "standard conforming D program". The D documentation is simply the manual of a set of programs (compiler, tools) which may or may not be correctly be described therin. According to <https://dlang.org/spec/contracts.html> the program
>
> ---
> void main ()
> {
>     assert (false);
> }
> ---
>
> qualifies as "invalid, and therefore has undefined behaviour." A statement, which makes no sense to me. Either it is a "debugging aid", that implies defined behavior, or it is undefined behavior, then assert (false) cannot aid debugging.

assert(false) is a bit special in that it's never removed (it becomes a HLT instruction with -release), and the compiler recognizes that you're saying that that code is supposed to be unreachable (e.g. it then doesn't require a return statement after assert(0) if if the function is supposed to return). Obviously, asserting false in main is pointless, and you're dead wrong about the code being unreachable, but the fact that you're wrong is caught when the line is reached, and the program is killed. And that behavior is completely defined, because it's known at compile time that the condition being tested by the assertion is false.

assert(false) does aid in debugging in that you're indicating that a piece of code is supposed to be unreachable, and if you reach it, then you have a bug, and you catch it, but really, it's a special case indicating that a piece of code is supposed to be unreachable rather than an assertion to test that a particular condition is true at a particular point in the program, which is what assertions normally do.

- Jonathan M Davis

« First   ‹ Prev
1 2