www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Disallow side effects in assert

reply "bearophile" <bearophileHUGS lycos.com> writes:
A nice blog post about assertions, preconditions, invariants and 
their usage:

http://blog.regehr.org/archives/1091

It shows how D avoids some troubles, and offers most of what's 
needed.

 The checkRep() Idiom
It could go in the D invariant. I use it often.
 The second way to accidentally change the state of the
 program is like this:
 
 assert (treeDepth() == 7);

 but unfortunately treeDepth() changes the value in some
 variable or heap cell, perhaps via a longish call chain.
 
 In case it isn't totally clear, the problem with side-effects
 in assertions is that we'll test our program for a while, 
 decide it's good, and do a release build with assertions turned 
 off and of course suddenly it doesn't work. Or, it might be the 
 release version that works but our debug build is broken by a 
 side-effecting assertion. Dealing with these problems is highly 
 demoralizing since assertions are supposed
 to save time, not eat it up. I feel certain that there are 
 static analyzers that warn about this kind of thing. In fact,
 the original paper about the work that became Coverity's tool 
 mentions exactly this analysis in Section 4.1, and also gives 
 plenty of examples of this bug. This is an area where language 
 support for controlling side effects would be useful. Such 
 support is extremely primitive in C/C++.
On this topic I have recently added an Enhancement Request, to disallow side effects in assert(): https://d.puremagic.com/issues/show_bug.cgi?id=12028 Two simple examples of code that is meant to be forbidden: int foo(ref int y) pure nothrow { return y++; } void main() { int x; assert(++x); assert(foo(x)); } Bye, bearophile
Feb 03 2014
next sibling parent reply "Daniel Murphy" <yebbliesnospam gmail.com> writes:
"bearophile"  wrote in message news:yzopesnnlgidhelsrrci forum.dlang.org... 
     assert(foo(x));
Having the compiler see inside function bodies is problematic.
Feb 03 2014
parent reply "bearophile" <bearophileHUGS lycos.com> writes:
Daniel Murphy:

    assert(foo(x));
Having the compiler see inside function bodies is problematic.
Why do you need that? You can tell that foo() is not strongly pure from its signature. That's enough. Bye, bearophile
Feb 03 2014
parent reply "Daniel Murphy" <yebbliesnospam gmail.com> writes:
"bearophile"  wrote in message news:qfqkkpkdialejlknrpyx forum.dlang.org...

 Why do you need that? You can tell that foo() is not strongly pure from 
 its signature. That's enough.
Enough to tell it _might_ have a side effect, not enough to know it _will_. The number of false positives would make it near useless.
Feb 03 2014
parent reply "bearophile" <bearophileHUGS lycos.com> writes:
Daniel Murphy:

 Enough to tell it _might_ have a side effect, not enough to 
 know it _will_. The number of false positives would make it 
 near useless.
How many impure functions do you call in your asserts? In D we have a purity system, and now it works well enough. It's a good idea to actually start use it, for asserts, contracts, parallelism, etc. Bye, bearophile
Feb 03 2014
next sibling parent Iain Buclaw <ibuclaw gdcproject.org> writes:
On 3 February 2014 17:00, bearophile <bearophileHUGS lycos.com> wrote:
 Daniel Murphy:


 Enough to tell it _might_ have a side effect, not enough to know it
 _will_. The number of false positives would make it near useless.
How many impure functions do you call in your asserts?
Rephrase to 'How many *functions* do you call in your asserts?' If a function has a side effect (or returns), I'd have thought that typically people want to store that value first, then test it using assert(). Regards Iain.
Feb 03 2014
prev sibling parent reply "Daniel Murphy" <yebbliesnospam gmail.com> writes:
"bearophile"  wrote in message news:aifyouczfuqhdgubpfsw forum.dlang.org...

 How many impure functions do you call in your asserts?
 In D we have a purity system, and now it works well enough. It's a good 
 idea to actually start use it, for asserts, contracts, parallelism, etc.
Do you really want to force all codebases to 'pure'ify all their functions? A function call in an assert might be a red flag, but it certainly isn't a bug.
Feb 03 2014
parent reply "bearophile" <bearophileHUGS lycos.com> writes:
Daniel Murphy:

 Do you really want to force all codebases to 'pure'ify all 
 their functions?
What percentage of your functions are transitively called in your asserts? In my code it's a small percentage.
 A function call in an assert might be a red flag, but it 
 certainly isn't a bug.
I agree. I am suggesting to disallow just impure functions in asserts. We have an acceptably good type system, but are we afraid of using it? Bye, bearophile
Feb 03 2014
parent reply Brad Roberts <braddr puremagic.com> writes:
I have one semi-large code base where I validate the locking semantics 
by adding assert(!lock.try_acquire)'s.  That's clearly a side effect, 
but also clearly should never have one if the code is correct.

On 2/3/14, 10:20 AM, bearophile wrote:
 Daniel Murphy:

 Do you really want to force all codebases to 'pure'ify all their
 functions?
What percentage of your functions are transitively called in your asserts? In my code it's a small percentage.
 A function call in an assert might be a red flag, but it certainly
 isn't a bug.
I agree. I am suggesting to disallow just impure functions in asserts. We have an acceptably good type system, but are we afraid of using it? Bye, bearophile
Feb 03 2014
next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
Brad Roberts:

 I have one semi-large code base where I validate the locking 
 semantics by adding assert(!lock.try_acquire)'s.  That's 
 clearly a side effect, but also clearly should never have one 
 if the code is correct.
I see, and you can't you use a special library-defined assert for that purpose. Bye, bearophile
Feb 03 2014
prev sibling parent reply Marco Leise <Marco.Leise gmx.de> writes:
Am Mon, 03 Feb 2014 10:33:11 -0800
schrieb Brad Roberts <braddr puremagic.com>:

 I have one semi-large code base where I validate the locking semantics 
 by adding assert(!lock.try_acquire)'s.  That's clearly a side effect, 
 but also clearly should never have one if the code is correct.
Damn those gotchas. Basically lock.try_acquire is pure when it returns false, and if not, the program is terminated anyways so it doesn't make a difference. I also noticed that contracts don't work at all with shared objects. Where you synchronize around a mutex inside a method you have to move all checks into the critical section. Yeah, its obvious, but when you turn a non-thread-safe structure into a thread-safe one and have to delete all in-out-blocks it hurts a bit. :) -- Marco
Feb 03 2014
parent "bearophile" <bearophileHUGS lycos.com> writes:
Marco Leise:

 Damn those gotchas. Basically lock.try_acquire is pure when it
 returns false, and if not, the program is terminated anyways so
 it doesn't make a difference.
So an annotation like the proposed noreturn (similar to GCC function annotation, and useful for functions that do not return, so they can be used where you use assert(0) and the like) is not enough to solve this problem. Bye, bearophile
Feb 03 2014
prev sibling next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
If someone implements this test, we could see how many problems 
it causes in Phobos (and eventually in the D front-end) and 
perhaps even see if it finds some problem.

Bye,
bearophile
Feb 03 2014
prev sibling next sibling parent reply "Jesse Phillips" <Jesse.K.Phillips+D gmail.com> writes:
On Monday, 3 February 2014 at 14:57:19 UTC, bearophile wrote:
 Two simple examples of code that is meant to be forbidden:

 int foo(ref int y) pure nothrow {
     return y++;
 }
 void main() {
     int x;
     assert(++x);
     assert(foo(x));
 }

 Bye,
 bearophile
Aren't these things you might do inside a unittest?
Feb 03 2014
parent "bearophile" <bearophileHUGS lycos.com> writes:
Jesse Phillips:

 Aren't these things you might do inside a unittest?
Mutating state and calling impure functions is OK in an unittest. So this code is OK: int foo(ref int y) pure nothrow { return y++; } unittest { int x; ++x; assert(x); foo(x); assert(x); } Bye, bearophile
Feb 03 2014
prev sibling parent reply "bearophile" <bearophileHUGS lycos.com> writes:
An example of the problem?

http://forum.dlang.org/thread/52EFE127.8070504 yahoo.com?page=2#post-xodootdnxopfyeqmhnjb:40forum.dlang.org

Bye,
bearophile
Feb 04 2014
parent reply Marco Leise <Marco.Leise gmx.de> writes:
Am Tue, 04 Feb 2014 14:25:14 +0000
schrieb "bearophile" <bearophileHUGS lycos.com>:

 An example of the problem?
 
 http://forum.dlang.org/thread/52EFE127.8070504 yahoo.com?page=2#post-xodootdnxopfyeqmhnjb:40forum.dlang.org
 
 Bye,
 bearophile
Yes, this is definitely why asserts should not have side effects when they succeed. I think maybe the earlier try_lock example could be written more cleanly as: assert(lock.is_locked); Still this requires is_locked to be "strongly pure", which cannot be expressed explicitly in D. (Otherwise it could change the implicit this and cause a side-effect.) -- Marco
Feb 04 2014
next sibling parent reply "bearophile" <bearophileHUGS lycos.com> writes:
Marco Leise:

 Yes, this is definitely why asserts should not have side
 effects when they succeed.
Can't you define a impureAssert() in Phobos and use it for the few cases when you need it, and use a pure-only assert() for all other cases? Bye, bearophile
Feb 04 2014
parent reply Marco Leise <Marco.Leise gmx.de> writes:
Am Tue, 04 Feb 2014 22:38:43 +0000
schrieb "bearophile" <bearophileHUGS lycos.com>:

 Marco Leise:
 
 Yes, this is definitely why asserts should not have side
 effects when they succeed.
Can't you define a impureAssert() in Phobos and use it for the few cases when you need it, and use a pure-only assert() for all other cases? Bye, bearophile
What would happen to them in release compiles? -- Marco
Feb 04 2014
parent "bearophile" <bearophileHUGS lycos.com> writes:
Marco Leise:

 What would happen to them in release compiles?
Perhaps their body that calls the lazy argument should be wrapped in version(assert) {} else {...}. Bye, bearophile
Feb 04 2014
prev sibling parent "Daniel Murphy" <yebbliesnospam gmail.com> writes:
"Marco Leise"  wrote in message 
news:20140204184527.296f64e5 marco-leise.homedns.org...

 Still this requires is_locked to be "strongly pure", which
 cannot be expressed explicitly in D. (Otherwise it could
 change the implicit this and cause a side-effect.)
Actually it only requires const-pure to prevent side effects.
Feb 04 2014