www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - What should happen when the assert message expression throws?

reply RazvanN <razvan.nitu1305 gmail.com> writes:
I have stumbled upon: 
https://issues.dlang.org/show_bug.cgi?id=17226

I want to fix it, however, the solution is not obvious.

Take this code:

```d
import std.string;

void foo(int i)
{
     // In this case a %s is forgotten but it could be any other 
trivial error.
     assert(i == 42, format("Bad parameter:", i));
}

void main()
{
     foo(43);
}
```

If `format` throws, then the Exception is thrown masking the 
assert error.
Unfortunately, I don't see a clear way to rewrite this to valid D 
code as to catch the exception and then assert. Ideally, we could 
do something along the lines of:

```d
assert(i == 42,
        (const(char)[] msg,
         try { msg = format("Bad parameter:", i); },
         catch (Exception e) { msg = "Assert message evaluation 
has failed";},
         msg)
        );
```

This rewrite would be done only if it is determined that the 
assert message may throw. However, the current dmd-fe 
implementation does not allow for such a construction and I think 
that it might be overkill to implement the necessary machinery 
just to support this case.

The try catch block can also be generated outside of the assert 
expression:

```d
auto msg;
try { msg = format("Bad parameter:", i);}
catch (Exception e) { msg = "Assert message evaluation has 
failed";}
assert(i == 42, msg);
```

The difference between the 2 is that in this case we are 
evaluating the msg regardless of whether the assert condition is 
true or false.

Also, we need to take into account the case where the user tries 
to catch the exception by himself:

```d
void foo(int i)
{
     try
     {
         // In this case a %s is forgotten but it could be any 
other trivial error.
         assert(i == 42, format("Bad parameter:", i));
     }
     catch(Exception e) { /* do some sort of fix up with the 
message */}
}

void main()
{
     foo(43);
}
```

Today, this code runs successfully and no AssertError is thrown. 
If we automatically catch the exception we might break such code 
(although I would argue it would not be too dramatic).

An alternative solution would be to deprecate having an assert 
message that may throw. This has the advantage that it avoids 
complexity inside the compiler and the user is forced to write:

```d

auto msg = /* do whatever you want with throwing code */
assert(cond, msg);

```

If the user wants to catch the exception or not, it's his/hers 
business, but then the compiler has defined semantics in all 
situations.

What do you think? Is deprecating having an assert message that 
may throw a severe restriction? Are there other rewrites that I 
am missing?

Cheers,
RazvanN
Nov 18 2022
next sibling parent Meta <jared771 gmail.com> writes:
On Friday, 18 November 2022 at 12:36:14 UTC, RazvanN wrote:
 Unfortunately, I don't see a clear way to rewrite this to valid 
 D code as to catch the exception and then assert. Ideally, we 
 could do something along the lines of:

 ```d
 assert(i == 42,
        (const(char)[] msg,
         try { msg = format("Bad parameter:", i); },
         catch (Exception e) { msg = "Assert message evaluation 
 has failed";},
         msg)
        );
 ```
Why not rewrite it like so? ```d assert(i == 42, () { try { return format("Bad parameter:", i); } catch (Exception e) { return "Assert message evaluation has failed"; } }()); ```
Nov 18 2022
prev sibling next sibling parent reply Patrick Schluter <Patrick.Schluter bbox.fr> writes:
On Friday, 18 November 2022 at 12:36:14 UTC, RazvanN wrote:
 I have stumbled upon: 
 https://issues.dlang.org/show_bug.cgi?id=17226

 [...]
I question if a fix is even required. It's a bug in the program that has to be corrected in any case. When it is corrected, the assert() will manifest itself and can then be corrected also. When debugging a program, I always concentrate on the first error/bug/exception as very often subsequent errors/bugs/exceptions were more often than not just consequences of undefined behaviour of the initial error. So a "won't fix" is an appropriate approach imho if a correction involves a much to costly fix.
Nov 18 2022
parent =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 11/18/22 05:06, Patrick Schluter wrote:

 It's a bug in the program that has
 to be corrected in any case. When it is corrected, the assert() will
 manifest itself and can then be corrected also. When debugging a
 program, I always concentrate on the first error/bug/exception as very
 often subsequent errors/bugs/exceptions were more often than not just
 consequences of undefined behaviour of the initial error.
I am in the habit of catching Exceptions in main() to print a human-friendly error message because those are just messages that the users should know about. Which means, the backtrace is missing for Exceptions. In contrast, although embarrassing, I let Errors uncaught so the default behaviour of dumping the backtrace is preserved. For that reason, this issue may hide useful information about bugs that are observed only be a user. Ali
Nov 20 2022
prev sibling next sibling parent Petar Kirov [ZombineDev] <petar.p.kirov gmail.com> writes:
On Friday, 18 November 2022 at 12:36:14 UTC, RazvanN wrote:
 I have stumbled upon: 
 https://issues.dlang.org/show_bug.cgi?id=17226

 I want to fix it, however, the solution is not obvious.

 Take this code:

 ```d
 import std.string;

 void foo(int i)
 {
     // In this case a %s is forgotten but it could be any other 
 trivial error.
     assert(i == 42, format("Bad parameter:", i));
 }

 void main()
 {
     foo(43);
 }
 ```

 If `format` throws, then the Exception is thrown masking the 
 assert error.
 Unfortunately, I don't see a clear way to rewrite this to valid 
 D code as to catch the exception and then assert. Ideally, we 
 could do something along the lines of:

 ```d
 assert(i == 42,
        (const(char)[] msg,
         try { msg = format("Bad parameter:", i); },
         catch (Exception e) { msg = "Assert message evaluation 
 has failed";},
         msg)
        );
 ```

 This rewrite would be done only if it is determined that the 
 assert message may throw. However, the current dmd-fe 
 implementation does not allow for such a construction and I 
 think that it might be overkill to implement the necessary 
 machinery just to support this case.

 The try catch block can also be generated outside of the assert 
 expression:

 ```d
 auto msg;
 try { msg = format("Bad parameter:", i);}
 catch (Exception e) { msg = "Assert message evaluation has 
 failed";}
 assert(i == 42, msg);
 ```

 The difference between the 2 is that in this case we are 
 evaluating the msg regardless of whether the assert condition 
 is true or false.

 Also, we need to take into account the case where the user 
 tries to catch the exception by himself:

 ```d
 void foo(int i)
 {
     try
     {
         // In this case a %s is forgotten but it could be any 
 other trivial error.
         assert(i == 42, format("Bad parameter:", i));
     }
     catch(Exception e) { /* do some sort of fix up with the 
 message */}
 }

 void main()
 {
     foo(43);
 }
 ```

 Today, this code runs successfully and no AssertError is 
 thrown. If we automatically catch the exception we might break 
 such code (although I would argue it would not be too dramatic).

 An alternative solution would be to deprecate having an assert 
 message that may throw. This has the advantage that it avoids 
 complexity inside the compiler and the user is forced to write:

 ```d

 auto msg = /* do whatever you want with throwing code */
 assert(cond, msg);

 ```

 If the user wants to catch the exception or not, it's his/hers 
 business, but then the compiler has defined semantics in all 
 situations.

 What do you think? Is deprecating having an assert message that 
 may throw a severe restriction? Are there other rewrites that I 
 am missing?

 Cheers,
 RazvanN
Perhaps the `assert` hook in druntime could have additional overloads: ``` // pseudocode // existing assert hook (without `-checkaction=context`) void _d_assert(bool cond, string msg); // new hook overloads void _d_assert(bool cond, string function() /* infer attributes */ lazyMessage); void _d_assert(bool cond, string delegate() /* infer attributes */ lazyMessage); ``` The compiler would select the lazyMessage overloads if the the expression could throw, or perhaps deemed more computationally expensive (based on some heuristic). That way, we delay the evaluation of msg until we know for sure that the condition is false and then we could wrap that evaluation in `try`-`catch` and throw a proper nested `Throwable`. If anything, making the `msg` parameter lazy evaluated would make the asserts more consistent with [`enforce`](https://dlang.org/phobos/std_exception#enforce).
Nov 18 2022
prev sibling next sibling parent reply Dennis <dkorpel gmail.com> writes:
My thoughts:
- A wrong format string passed to `format` should be a runtime 
error (if not caught at compile time), not an exception
- The current behavior is 'correct' if you consider `assert` a 
function call, where the argument list is evaluated before 
calling the function. I'm not a fan of adding more magic to 
`assert`, especially considering:
- This issue is not limited to exceptions, but any bottom value 
generated in the assert message. 'Fixing' it for exceptions does 
not 'fix' it for other cases:

```D
assert(x, (){while(1){} return "";}()); // endless loop before 
assert
assert(x, new char[2UL ^^ 60]); // Out of Memory before assert
assert(x, assert(0)); // assert before assert
```

On Friday, 18 November 2022 at 12:36:14 UTC, RazvanN wrote:
 This rewrite would be done only if it is determined that the 
 assert message may throw. However, the current dmd-fe 
 implementation does not allow for such a construction and I 
 think that it might be overkill to implement the necessary 
 machinery just to support this case.
You could add a minimal implementation of `std.exception.collectException` to druntime and use that.
 An alternative solution would be to deprecate having an assert 
 message that may throw.
That could be annoying to users since Phobos' string functions often may throw.
Nov 18 2022
parent reply bauss <jacobbauss gmail.com> writes:
On Friday, 18 November 2022 at 13:18:39 UTC, Dennis wrote:
 My thoughts:
 - A wrong format string passed to `format` should be a runtime 
 error (if not caught at compile time), not an exception
I don't think it should be an error. Format strings could come from user code and it shouldn't break the whole application because a user put a wrong format string in. This is especially important in applications that can have multiple users interacting with it at the same time ex. a website, game server etc. If it should be an error then there should be a way to validate a format string at runtime without attempting to call format. Error should only be used for errors that aren't recoverable ex. out of memory, stackoverflow etc. it shouldn't be used for errors that are recoverable, that's exactly what exception is for.
Nov 24 2022
parent Dennis <dkorpel gmail.com> writes:
On Thursday, 24 November 2022 at 08:23:22 UTC, bauss wrote:
 Format strings could come from user code
Can you name one application that does this?
 This is especially important in applications that can have 
 multiple users interacting with it at the same time ex. a 
 website, game server etc.
I've never seen a website or game ask me to input a programming language-specific format string.
 If it should be an error then there should be a way to validate 
 a format string at runtime without attempting to call format.
Yes, I agree.
 Error should only be used for errors that aren't recoverable
True, but what you consider recoverable is a subjective design decision.
 ex. out of memory
Can be recoverable. If I try to render a scene in Blender that's too complex for my GPU, it reports 'out of memory' without closing the program. But considering out of memory is unrecoverable by default in D, you can get in trouble when the user gives you a format string that generates very large output, such as `"%2000000000s"`, so currently it's not safe to use format strings provided from user input anyway.
Nov 24 2022
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 11/18/2022 4:36 AM, RazvanN wrote:
 I have stumbled upon: https://issues.dlang.org/show_bug.cgi?id=17226
Yes, an interesting problem. I suggest that have the format() template do the check at compile time, analogously to the way printf() formats are statically checked. The signature of format() is: immutable(Char)[] format(Char, Args...)(in Char[] fmt, Args args) so it should be doable as a template constraint. This would be a good initiative anyway, as it will benefit all uses of format(). And if this enables format() itself to be nothrow, even better! Of course, this does not solve the *general* problem of arguments to assert() throwing, but as others suggested, that can be dealt with by: 1. won't fix - too bad, so sad! 2. require such arguments to be nothrow, which is a more robust solution
Nov 18 2022
parent reply H. S. Teoh <hsteoh qfbox.info> writes:
On Fri, Nov 18, 2022 at 12:41:54PM -0800, Walter Bright via 
Digitalmars-d wrote:
 On 11/18/2022 4:36 AM, RazvanN wrote:
 I have stumbled upon: 
 https://issues.dlang.org/show_bug.cgi?id=17226
Yes, an interesting problem. I suggest that have the format() template do the check at compile time,
It already does this. This works: string s = format!"blah %f blah"(123.45); and this will produce a compile error: string s = format!"blah %d blah"(123.45); So just write: assert(myAssumption, format!"%s doesn't work!"(blah)); Problem solved. T -- Life begins when you can spend your spare time programming instead of watching television. -- Cal Keegan
Nov 18 2022
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 11/18/2022 1:33 PM, H. S. Teoh wrote:
 On Fri, Nov 18, 2022 at 12:41:54PM -0800, Walter Bright via Digitalmars-d
wrote:
 On 11/18/2022 4:36 AM, RazvanN wrote:
 I have stumbled upon: > https://issues.dlang.org/show_bug.cgi?id=17226
Yes, an interesting problem. I suggest that have the format() template do the check at compile time,
It already does this. This works:         string s = format!"blah %f blah"(123.45); and this will produce a compile error:         string s = format!"blah %d blah"(123.45); So just write:         assert(myAssumption, format!"%s doesn't work!"(blah)); Problem solved.
You should post that to the bug report!
Nov 18 2022
parent =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 11/18/22 14:05, Walter Bright wrote:

 So just write:

          assert(myAssumption, format!"%s doesn't work!"(blah));

 Problem solved.
You should post that to the bug report!
The problem with the format string was just an example. What if 'blah' above is a function that throws Exception? Ali
Nov 20 2022
prev sibling next sibling parent JG <someone somewhere.com> writes:
On Friday, 18 November 2022 at 12:36:14 UTC, RazvanN wrote:
 I have stumbled upon: 
 https://issues.dlang.org/show_bug.cgi?id=17226

 [...]
What about rewriting ```d assert(false,format("oops",1)); ``` to ```d assert(false,(){ try { return format("oops",1); } catch(Exception e) { //throw equivalent error (this should be improved) throw new Error(e.msg); } }()); ```
Nov 18 2022
prev sibling next sibling parent reply Dukc <ajieskola gmail.com> writes:
On Friday, 18 November 2022 at 12:36:14 UTC, RazvanN wrote:
 What do you think? Is deprecating having an assert message that 
 may throw a severe restriction? Are there other rewrites that I 
 am missing?
In my opinion the fix is clear. When the assertion handler at DRuntime determines the assertion has failed, it should call the error message generator (it is lazy IIRC) in a try/catch block. If the generator throws an exception, the error message of assertion failure should be something like `<FormatException thrown while writing error message>`. Should the error generation exception be the `.next` member of `AssertError`? Probably, but I'm not sure. If the message generator throws an unrecoverable error, that should probably still mask the assert error. If a `RangeError` masks `AssertError` it's unlikely to be a problem since neither is likely to be caught and at least in my mind they're essentially the same - a "bug detected" error.
Nov 19 2022
next sibling parent JG <someone somewhere.com> writes:
On Saturday, 19 November 2022 at 08:11:45 UTC, Dukc wrote:
 On Friday, 18 November 2022 at 12:36:14 UTC, RazvanN wrote:
 What do you think? Is deprecating having an assert message 
 that may throw a severe restriction? Are there other rewrites 
 that I am missing?
In my opinion the fix is clear. When the assertion handler at DRuntime determines the assertion has failed, it should call the error message generator (it is lazy IIRC) in a try/catch block. If the generator throws an exception, the error message of assertion failure should be something like `<FormatException thrown while writing error message>`. Should the error generation exception be the `.next` member of `AssertError`? Probably, but I'm not sure. If the message generator throws an unrecoverable error, that should probably still mask the assert error. If a `RangeError` masks `AssertError` it's unlikely to be a problem since neither is likely to be caught and at least in my mind they're essentially the same - a "bug detected" error.
I agree with what you are saying. It think it is essentially what I wrote above but writing a better error message than I suggested.
Nov 19 2022
prev sibling parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 11/19/22 00:11, Dukc wrote:

 In my opinion the fix is clear.
[...]
 Should the error generation exception be the `.next` member of
 `AssertError`? Probably, but I'm not sure.
I think the following is what you describe: import std; void assertImpl_(string file = __FILE__, size_t line = __LINE__)(bool value, lazy string msgGen) { if (!value) { string msg; Throwable next; try { msg = msgGen; } catch (Exception exc) { msg = "(Additionally failed to generate the assertion failure message.)"; next = exc; } import core.exception : AssertError; throw new AssertError(msg, file, line, next); } } void main() { // This passes through despite the error in the message: assertImpl_(true, format("%s")); // This throws AssertError where the message formatting // is a collateral Throwable: assertImpl_(false, format("%s")); } Discussion: As I commented on the bug report as well, D is already in the best-effort business when it comes to Errors: 1) If the assert expression *will* fail, the program is already in an invalid state. (I claim, it is not a failed assert expression that puts the program into invalid state; rather, the fact that the expression will fail.) So, even evaluating the assert expression is best-effort. 2) The default behavior for the main thread is to catch Error and dump backtrace to stderr. That is best-effort because now we are catching an Error. Given the above two observations, the code inside 'if (!value)' above should also be considered best-effort and I think is the solution for this bug. The only thing that bothers me is the 'lazy' parameter which I think is a 'delegate'. Perhaps the compiler can use a 'function' lazily there. (?) Ali
Nov 20 2022
next sibling parent Salih Dincer <salihdb hotmail.com> writes:
On Sunday, 20 November 2022 at 18:30:00 UTC, Ali Çehreli wrote:
 On 11/19/22 00:11, Dukc wrote:

 In my opinion the fix is clear.
[...]
 Should the error generation exception be the `.next` member of
 `AssertError`? Probably, but I'm not sure.
I think the following is what you describe:
If we can come up with our own solutions, we shouldn't expect a solution from D. The observations are fine. Thank you... SDB 79
Nov 23 2022
prev sibling parent reply Dukc <ajieskola gmail.com> writes:
On Sunday, 20 November 2022 at 18:30:00 UTC, Ali Çehreli wrote:
 On 11/19/22 00:11, Dukc wrote:

 In my opinion the fix is clear.
[...]
 Should the error generation exception be the `.next` member of
 `AssertError`? Probably, but I'm not sure.
I think the following is what you describe: [snip]
Yes, that's correct.
 Discussion: As I commented on the bug report as well, D is 
 already in the best-effort business when it comes to Errors:
What does "best-effort" mean?
Nov 25 2022
next sibling parent reply Quirin Schroll <qs.il.paperinik gmail.com> writes:
On Friday, 25 November 2022 at 08:12:41 UTC, Dukc wrote:
 What does "best-effort" mean?
Generally, it means that a procedure is not a complete or ideal solution and won’t always give you what you need (even if it theoretically could), but still covers common and simple cases and maybe some of the uncommon or complicated ones. Usually, best-effort solutions are subject to incremental improvements. A simple example for best effort is compiler error messages: Usually, they’re pointing you to the spot where you change something and it works. Sometimes, the error is nowhere the error message indicates. Another example well-known to many D programmers that is both, best-effort and ideal, is ` safe`. It intends to be a complete (or ideal) solution in the sense that when checking for ` safe` succeeds, the code has no UB (give or take ` trusted`, of course). A compiler that accepts UB in ` safe` code has a bug! On the other hand, ` safe` is best-effort in the sense that it does not even intend to cover all non-UB code. There will always be (rather simple) code for which *you* can (rather easily) prove that it is indeed free of UB, but the compiler’s ` safe` checks reject it. The language development goes into the direction of recognizing more and more of non-UB code as ` safe` (DIP 1000 most notably), but it will never be able to cover the entirety of non-UB because that would equate to solving the [halting problem](https://en.wikipedia.org/wiki/Halting_problem), which is provably impossible. Because it *ideally* would be, but mathematically can’t, and even in cases where there is a proof showing no UB, it’s not intended the compiler finds it, because practically, it’s not worth it, it is a best-effort solution. I don’t think “best-effort” is used here with any more special meaning.
Nov 25 2022
next sibling parent kdevel <kdevel vogtner.de> writes:
On Friday, 25 November 2022 at 09:33:21 UTC, Quirin Schroll wrote:

 Another example well-known to many D programmers that is both, 
 best-effort and ideal, is ` safe`. It intends to be a complete 
 (or ideal) solution in the sense that when checking for ` safe` 
 succeeds, the code has no UB (give or take ` trusted`, of 
 course). A compiler that accepts UB in ` safe` code has a bug!
A false positive.
 On the other hand, ` safe` is best-effort in the sense that it 
 does not even intend to cover all non-UB code. There will 
 always be (rather simple) code for which *you* can (rather 
 easily) prove that it is indeed free of UB, but the compiler’s 
 ` safe` checks reject it.
A false negative.
 The language development goes into the direction of recognizing 
 more and more of non-UB code as ` safe` (DIP 1000 most 
 notably), but it will never be able to cover the entirety of 
 non-UB because that would equate to
Make safe a no-op, then there will be no more false negatives. Mission accomplished. I mean: What is the objective of safe anyhow? I stumble already at the first sentence of the manual: "Safe functions are marked with the safe attribute." Is this an imperative or a descriptive sentence?
Nov 25 2022
prev sibling parent Dukc <ajieskola gmail.com> writes:
On Friday, 25 November 2022 at 09:33:21 UTC, Quirin Schroll wrote:
 On Friday, 25 November 2022 at 08:12:41 UTC, Dukc wrote:
 What does "best-effort" mean?
Generally, it means that a procedure is not a complete or ideal solution and won’t always give you what you need (even if it theoretically could), but still covers common and simple cases and maybe some of the uncommon or complicated ones. Usually, best-effort solutions are subject to incremental improvements. [snip]
In fact it's this explanation that made it clear to me. Thanks.
Dec 08 2022
prev sibling parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 11/25/22 00:12, Dukc wrote:

 Discussion: As I commented on the bug report as well, D is already in
 the best-effort business when it comes to Errors:
What does "best-effort" mean?
(I am not sure whether I should have used the word "hopeful" here. I will continue with best-effort.) According to common assert wisdom, the program must not continue once an assertion has failed because the program is in invalid state. However, the main thread of a D program does execute code to display the backtrace to help the programmer discover why the assertion failed. By the wisdom above, this is best-effort because if the program is in an invalid state, the code that attempts to display the backtrace can e.g. delete files. I had discovered that fact when I first started catching Errors in non-main threads to do exactly what the main thread does. I used to feel guilty about that but now I know it's all best-effort anyway: it may or may not work. Add to the above another observation which I had after Patrick Schluter's message: Even the execution of the assert check is best-effort because if the check will fail, the program has been in invalid state for an unknow amount of time anyway. That logic can be carried all the way down to hardware and in the end most computing is best-effort anyway. Given all that, D's assert behavior can do anything it wants so I still agree with the code above but I think it can be improved by caching Throwable in message generation because the programmer would still be interested in the original failed assert: catch (Throwable exc) Ali
Nov 25 2022
next sibling parent reply kdevel <kdevel vogtner.de> writes:
On Friday, 25 November 2022 at 12:29:26 UTC, Ali Çehreli wrote:

 According to common assert wisdom, the program must not 
 continue once an assertion has failed because the program is in 
 invalid state.
Whatever "invalid state" means. I could not find a definition in the manual. Usually a system is said to be in some state s of a set of possible states S. Now one can devise a proper subset I ⊊ S and say "the system is in an invalid state" iff s ∈ I. That is a precise definition but is still meaningless.
 However, the main thread of a D program does execute code to 
 display the backtrace to help the programmer discover why the 
 assertion failed. By the wisdom above, this is best-effort 
 because if the program is in an invalid state, the code that 
 attempts to display the backtrace can e.g. delete files.
Pardon! What?
 I had discovered that fact when I first started catching Errors 
 in non-main threads to do exactly what the main thread does. I 
 used to feel guilty about that but now I know it's all 
 best-effort anyway: it may or may not work.

 Add to the above another observation which I had after Patrick 
 Schluter's message: Even the execution of the assert check is 
 best-effort because if the check will fail, the program has 
 been in invalid state for an unknow amount of time anyway.
int main () { int a = 1; try assert (a == 0); catch (Throwable t) {} return 0; } Which line makes the program enter the "invalid state"? Additional question: What constitutes that "invalid state"?
Nov 25 2022
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 11/25/22 05:32, kdevel wrote:
 On Friday, 25 November 2022 at 12:29:26 UTC, Ali Çehreli wrote:

 According to common assert wisdom, the program must not continue once
 an assertion has failed because the program is in invalid state.
Whatever "invalid state" means. I could not find a definition in the manual.
The assertion check defines the legal state. Its failure is the invalid state: int a = 42; foo(&a); assert(a == 42); The programmer defined the valid state of the program as being "when I pass 'a' by reference to 'foo', its value shall remain 42." If that assertion fails, the program is in an invalid state.
 However, the main thread of a D program does execute code to display
 the backtrace to help the programmer discover why the assertion
 failed. By the wisdom above, this is best-effort because if the
 program is in an invalid state, the code that attempts to display the
 backtrace can e.g. delete files.
Pardon! What?
Assume the assertion failure was an indication of a catastrophic event, which overwrote a function pointer that will end up executing unrelated code that deletes files.
 I had discovered that fact when I first started catching Errors in
 non-main threads to do exactly what the main thread does. I used to
 feel guilty about that but now I know it's all best-effort anyway: it
 may or may not work.

 Add to the above another observation which I had after Patrick
 Schluter's message: Even the execution of the assert check is
 best-effort because if the check will fail, the program has been in
 invalid state for an unknow amount of time anyway.
int main () { int a = 1; try assert (a == 0); catch (Throwable t) {} return 0; } Which line makes the program enter the "invalid state"?
Although that looks like a bug in the programmer's mind, I can still imagine the programmer expected some code perhaps running in a thread started e.g. in a 'static this' block was expected to alter the stack. It must be in an operating system that guarantees that other thread is executed so frequently that even after assigning 1 to a, it is expected to be observed as 0. There: The programmer knew things we don't see. If that was the entirety of the program, then I think the programmer is wrong.
 Additional
 question: What constitutes that "invalid state"?
I have to trust the assertion check, which may be buggy itself. Ali
Nov 25 2022
parent reply kdevel <kdevel vogtner.de> writes:
On Friday, 25 November 2022 at 14:38:57 UTC, Ali Çehreli wrote:
 On 11/25/22 05:32, kdevel wrote:
 If that assertion fails, the program is in an invalid state.
[reordering ...]
 int main ()
 {
     int a = 1;
     try
        assert (a == 0);
     catch (Throwable t)
        {}
     return 0;
 }

 Which line makes the program enter the "invalid state"?
Is it in the "invalid state" right after the condition has been evaluated or not before the AssertError has been thrown? What if the program is compiled with -release? Is it still in "invalid state"?
 Although that looks like a bug in the programmer's mind, I can 
 still imagine the programmer expected some code perhaps running 
 in a thread started e.g. in a 'static this' block was expected 
 to alter the stack.
The fabricated nine lines of code above is all I have. No static this block. It is meant as it is.
Nov 25 2022
next sibling parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 11/25/22 07:33, kdevel wrote:
 On Friday, 25 November 2022 at 14:38:57 UTC, Ali Çehreli wrote:
 On 11/25/22 05:32, kdevel wrote:
 If that assertion fails, the program is in an invalid state.
[reordering ...]
 int main ()
 {
     int a = 1;
     try
        assert (a == 0);
     catch (Throwable t)
        {}
     return 0;
 }

 Which line makes the program enter the "invalid state"?
I am not an expert here; just philosophizing. [reordering...] :)
 Although that looks like a bug in the programmer's mind, I can still
 imagine the programmer expected some code perhaps running in a thread
 started e.g. in a 'static this' block was expected to alter the stack.
The fabricated nine lines of code above is all I have. No static this block. It is meant as it is.
Your example proves that we all assume that the assertion condition is correct. I don't think the example above has a correct condition; so let's focus on correct ones.
 Is it in the "invalid state" right after the condition has been
 evaluated or not before the AssertError has been thrown? What if the
 program is compiled with -release? Is it still in "invalid state"?
I think assert is just a tool that exposes an invalid state. The invalid state may have been reached long ago. That's why I say the whole assert mechanism starting with executing the condition all the way down to printing backtrace or even not doing anything at all is best-effort. I am fine with that because such is life. But in philosophy, it gives us liberty to do anything we want. Ali
Nov 25 2022
parent reply kdevel <kdevel vogtner.de> writes:
On Friday, 25 November 2022 at 15:48:49 UTC, Ali Çehreli wrote:
 On 11/25/22 07:33, kdevel wrote:
 On Friday, 25 November 2022 at 14:38:57 UTC, Ali Çehreli
wrote:
 On 11/25/22 05:32, kdevel wrote:
 If that assertion fails, the program is in an invalid state.
[reordering ...]
 int main ()
 {
     int a = 1;
     try
        assert (a == 0);
     catch (Throwable t)
        {}
     return 0;
 }

 Which line makes the program enter the "invalid state"?
I am not an expert here; just philosophizing.
If I tell you "A program is running on a machine. There is an int variable a in memory at adress 0x47110816. The variable has the value -4." you probably can explain to me how and where the negative sign is represented in the machine. I would like to know if there is a representation of that ominous "invalid state" in terms of bits and bytes. Well, it doesn't have to. But if we are talking about "invalid state" we should be able to explain what we mean when we use that group of words. I don't use the term because I do not intuitively know what it means and the D docs do not define the term either.
 [reordering...] :)

 Although that looks like a bug in the programmer's mind, I 
 can still
 imagine the programmer expected some code perhaps running in 
 a thread
 started e.g. in a 'static this' block was expected to alter 
 the stack.
The fabricated nine lines of code above is all I have. No static this block. It is meant as it is.
Your example proves that we all assume that the assertion condition is correct.
If you allow I would like to discuss the interpretation of the code a bit more thoroughly.
 I don't think the example above has a correct condition; so 
 let's focus on correct ones.
The condition is false. That is what I want to discuss. My question is: What does that mean? Does it mean more than writing a comment // I solemnly swear a is zero. ?
 Is it in the "invalid state" right after the condition has 
 been
 evaluated or not before the AssertError has been thrown? What 
 if the
 program is compiled with -release? Is it still in "invalid 
 state"?
I think assert is just a tool that exposes an invalid state.
In your last post you wrote "If that assertion fails, the program is in an invalid state." The assertion in my program fails. According to the common inference rules the program must be in an invalid state and according to your last statement this invalid state is only exposed, i.e. the program was already in "invalid state" before the assert statment. Thus my question: What constitutes the invalid state in my example program? The program is syntactically valid, no UB, nothing else.
 The invalid state may have been reached long ago.
In my fabricated example program: Where and by what event went the program into "invalid state"?
Nov 25 2022
next sibling parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 11/25/22 08:48, kdevel wrote:

 I would like to know if there is a representation of that ominous
 "invalid state" in terms of bits and bytes. Well, it doesn't have to.
 But if we are talking about "invalid state" we should be able to explain
 what we mean when we use that group of words. I don't use the term
 because I do not intuitively know what it means and the D docs do not
 define the term either.
I think it is definition and comes from convention: - Errors represent situations where the program should not continue - AssertError is an Error, which is thrown when an assert condition fails For that reason, assert conditions do include checks that define the valid state of a program. The same condition can be used for throwing an Error or an Exception: exists(configFile) Only the programmer knows which one makes sense: - If configFile was a string input by the user, then the following is appropriate: enforce(exists(configFile), /* ... */) - If configFile was a string representing a file e.g. that has just been generated by the same program, then the following is appropriate: assert(exists(configFile), /* ... */) Only the programmer knows.
 The condition is false. That is what I want to discuss. My question is:
 What does that mean? Does it mean more than writing a comment

     // I solemnly swear a is zero.
Your example code is interesting because it expects 'a == 0' right after initializing it with 1. However, since that AssertError is caught (against convention), there is no observable remnant of that AssertError in the program. Since it did not represent a valid state anyway, no harm done there. Humans are fallible, so we expect cases where assert conditions do not represent a valid program state. We can discount those cases. Having said that, that assert means this to me: "For this program to be able continue, the value of 'a' must be 0."
 In your last post you wrote "If that assertion fails, the program is in
 an invalid state." The assertion in my program fails. According to the
 common inference rules the program must be in an invalid state
I trust the programmer makes mistakes. :) I don't think that is a correct assert condition.
 and
 according to your last statement this invalid state is only exposed,
 i.e. the program was already in "invalid state" before the assert 
statment. I strongly think so. An assertion failure is a late discovery of a problem.
 Thus my question: What constitutes the invalid state in my example
 program? The program is syntactically valid, no UB, nothing else.

 The invalid state may have been reached long ago.
In my fabricated example program: Where and by what event went the program into "invalid state"?
I am repeating myself but I think your program has a programmer error in that condition. Swallowing the thrown AssertError is interesting but I don't think that program has any invalid state. The way I see it, valid state is defined by the collection of assertion checks, which may be incorrect themselves or conflict with each other. Hopefully, the programmer catches and corrects mistakes in the checks and approaches a more correct definition of the valid state of that program. Ali
Nov 26 2022
next sibling parent reply kdevel <kdevel vogtner.de> writes:
On Saturday, 26 November 2022 at 12:32:40 UTC, Ali Çehreli wrote:
 On 11/25/22 08:48, kdevel wrote:

 I would like to know if there is a representation of that 
 ominous
 "invalid state" in terms of bits and bytes. Well, it doesn't 
 have to.
 But if we are talking about "invalid state" we should be able 
 to explain
 what we mean when we use that group of words. I don't use the 
 term
 because I do not intuitively know what it means and the D docs 
 do not
 define the term either.
I think it is definition and comes from convention: - Errors represent situations where the program should not continue
If the programmer who implements a function/method is not the author of `main` he most certainly is not in charge of deciding on the fate of the running program. [...]
 - If configFile was a string input by the user, then the 
 following is appropriate:

   enforce(exists(configFile), /* ... */)
 
 - If configFile was a string representing a file e.g. that has 
 just been generated by the same program, then the following is 
 appropriate:

   assert(exists(configFile), /* ... */)
That is a nice example! In both cases I would define an Exception-Class ConfigFileNotFound : Exception { ... } and let enforce create and throw a corresponding object. For me, it makes no difference if the file name is user input, taken from the command line, or hardcoded into the program. Well, actually I would not code such a check at all. I would let the open function throw the exception (also to avoid TOCTTOU). The decision if the program can continue without configuration file is probably taken in `main` or in the consumer of that configuration data: If there is a default configuration dataset the program might continue. [...]
 Having said that, that assert means this to me: "For this 
 program to be able continue, the value of 'a' must be 0."
In order not to convey that meaning in real programms I only use asserts in unittests. [...]
 I am repeating myself but I think your program has a programmer 
 error in that condition.
There really is none. The failure of the assert expression is on purpose.
 Swallowing the thrown AssertError is interesting but I don't 
 think that program has any invalid state.
Actually you do not extract that information from the code itself.
 The way I see it, valid state is defined by the collection of 
 assertion checks,
But sometimes you don't believe what the code says. If it does not match up with your presuppositions you testify validity though the assert checks fail. But then isn't that invalid state a non-local or even a subjective thing?
Nov 27 2022
parent =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 11/27/22 14:35, kdevel wrote:

 I think it is definition and comes from convention:

 - Errors represent situations where the program should not continue
I still agree with myself there: Errors should be used (trivially with assert) to specify the cases where the program cannot continue.
 If the programmer who implements a function/method is not the author of
 `main` he most certainly is not in charge of deciding on the fate of the
 running program.
That's agreeable. Error represents aborting mission but there are safety-critical applications where continuing with sane steps is better than aborting mission. One example given is autonomous-driving software aborting driving because of a thrown Error. It should not happen. The software should decide to drive slowly to the side. See... Even that's wishful thinking. When an assertion fails, we don't know whether the execution of that slowly driving to the side will do that. The absurd state we caught ourselves in may be due to a hardware issue where steering to the right may skid the vehicle out of control. For that reason, perhaps the software still aborts but another unit takes over. Sure... but these are outside of a program deciding to abort mission: int[] arr; populateArrar(arr); assert(arr.length > 7); What should the program do? Attempt to add arbitrary items to the array? What if that fails as well? If it could work, then it should not be assert in that last line, rather the following: if (arr.length <= 7) { log(/* ... */); } // continue
 For me, it
 makes no difference if the file name is user input, taken from the
 command line, or hardcoded into the program. Well, actually I would not
 code such a check at all. I would let the open function throw the
 exception (also to avoid TOCTTOU).
I may do that as well. However, the following does not strike me as wrong at all: createConfigFile("foo"); assert(exists("foo")); Even though another actor in the system may delete the file after the check, it is also conceivable that a newly-created file should exist.
 The decision if the program can continue without configuration file is
 probably taken in `main` or in the consumer of that configuration data:
 If there is a default configuration dataset the program might continue.
I was imagining the file that should exist as the default configuration. I wonder whether my example is wrong or whether it is always conceivable that all assert checks can be removed.
 Having said that, that assert means this to me: "For this program to
 be able continue, the value of 'a' must be 0."
In order not to convey that meaning in real programms I only use asserts in unittests.
That's the approach some safety-critical software takes: asserts exist only in test phase. There should be no assert left in the final product. All assertion failures should be caught during development phase.
 The way I see it, valid state is defined by the collection of
 assertion checks,
But sometimes you don't believe what the code says. If it does not match up with your presuppositions you testify validity though the assert checks fail. But then isn't that invalid state a non-local or even a subjective thing?
I don't understand. But the way I see it, yes, invalid state is completely subjective. The programmer defines it with assert checks and the program ensures it stays in that state. Ali
Dec 07 2022
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 11/26/22 13:32, Ali Çehreli wrote:
 - If configFile was a string input by the user, then the following is 
 appropriate:
 
    enforce(exists(configFile), /* ... */)
 
 - If configFile was a string representing a file e.g. that has just been 
 generated by the same program, then the following is appropriate:
 
    assert(exists(configFile), /* ... */)
 
 Only the programmer knows.
I don't think it is appropriate to assert that the filesystem is not being changed concurrently by some other process unless the OS provides explicit functionality to enforce it.
Dec 08 2022
parent reply "H. S. Teoh" <hsteoh qfbox.info> writes:
On Thu, Dec 08, 2022 at 04:03:35PM +0100, Timon Gehr via Digitalmars-d wrote:
 On 11/26/22 13:32, Ali ehreli wrote:
 - If configFile was a string input by the user, then the following
 is appropriate:
 
   enforce(exists(configFile), /* ... */)
 
 - If configFile was a string representing a file e.g. that has just
 been generated by the same program, then the following is
 appropriate:
 
   assert(exists(configFile), /* ... */)
 
 Only the programmer knows.
I don't think it is appropriate to assert that the filesystem is not being changed concurrently by some other process unless the OS provides explicit functionality to enforce it.
Yeah, for checking the existence of a file I'd use enforce, not assert. Assert is for catching logic errors in the program; a missing file in the filesystem isn't a logic error, it's either an installation problem (your program wasn't installed properly) or an incidental issue (user removed the drive for whatever reason, or hardware is failing). T -- Study gravitation, it's a field with a lot of potential.
Dec 08 2022
parent reply kdevel <kdevel vogtner.de> writes:
On Thursday, 8 December 2022 at 16:02:14 UTC, H. S. Teoh wrote:

[...]

 Yeah, for checking the existence of a file I'd use enforce, not 
 assert.
You would write enforce (exists (filename)); auto text = readText (filename); instead of simply auto text = readText (filename); // (1) ? I mean isn't the enforce redundant and also prone to TOCTTOU?
 Assert is for catching logic errors in the program; a missing 
 file in the filesystem isn't a logic error,
Logic error means that a program does not implement the specification. Example: The program should read the config file "config.cfg" which is misspelled as in enum config_filename = "config.cgf"; auto config = readText (config_filename); When the program is started a FileException is thrown. Nobody is forced to catch logic errors with asserts. Now: A file system is essentially a key/value store. (1) can also be written as auto value = readText (key); With a nicer notation this becomes auto value = obj [key]; // (2) where obj is an instance of some Filesystem class. IMNSHO it is hard to explain, why in case of string[string] obj; string key; or string [] obj; int key; the expression (2) throws an Error (capital E) while in the case of Filesystem obj; string key; it throws an Exception.
Dec 09 2022
next sibling parent "H. S. Teoh" <hsteoh qfbox.info> writes:
On Fri, Dec 09, 2022 at 02:36:04PM +0000, kdevel via Digitalmars-d wrote:
 On Thursday, 8 December 2022 at 16:02:14 UTC, H. S. Teoh wrote:
 
 [...]
 
 Yeah, for checking the existence of a file I'd use enforce, not
 assert.
You would write enforce (exists (filename)); auto text = readText (filename); instead of simply auto text = readText (filename); // (1) ? I mean isn't the enforce redundant and also prone to TOCTTOU?
[...] No, I meant, *if* I wanted to check for the existence of a file, I would use enforce rather than assert. If I was going to just read the file, I might as well just catch the exception when opening it failed. It would be pointless to use .enforce to check for its existence. T -- "Holy war is an oxymoron." -- Lazarus Long
Dec 09 2022
prev sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 12/9/22 15:36, kdevel wrote:
 
 Now: A file system is essentially a key/value store. (1) can also be
 written as
 
     auto value = readText (key);
 
 With a nicer notation this becomes
 
     auto value = obj [key]; // (2)
 ...
You mean with a deliberately more confusing notation.
 where obj is an instance of some Filesystem class. IMNSHO it is hard
 to explain, why in case of
 
     string[string] obj;
     string key;
 
 or
 
     string [] obj;
     int key;
 
 the expression (2) throws an Error (capital E) while in the case of
 
     Filesystem obj;
     string key;
 
 it throws an Exception.
It is not hard to explain. The version throwing an error is a more fundamental building block that can be used to implement the version throwing an exception. The file system does not expose the more low-level operation because the file system is shared between multiple processes and it also does not expose primitives to synchronize file system accesses. I.e., you can't be sure the programmer is to blame for attempting to e.g. open an nonexistent file. It could be different, but that would require a OS design that supports some sort of filesystem ownership semantics.
Dec 09 2022
prev sibling parent Dukc <ajieskola gmail.com> writes:
On Friday, 25 November 2022 at 16:48:39 UTC, kdevel wrote:
 I don't think the example above has a correct condition; so 
 let's focus on correct ones.
The condition is false. That is what I want to discuss. My question is: What does that mean? Does it mean more than writing a comment // I solemnly swear a is zero.
"A is *always* zero when this line is passed by the program. I have thought it through, and when I have tested it, it always has been so. Therefore, if a ever is something else when the program gets here, by definition I have no idea what is happening, and what could have caused it. It follows I can say nothing what would happen if you let this program to continue, so it must be aborted *immediately*."
Dec 08 2022
prev sibling parent Quirin Schroll <qs.il.paperinik gmail.com> writes:
On Friday, 25 November 2022 at 15:33:07 UTC, kdevel wrote:
 On Friday, 25 November 2022 at 14:38:57 UTC, Ali Çehreli wrote:
 On 11/25/22 05:32, kdevel wrote:
 If that assertion fails, the program is in an invalid state.
[reordering ...]
 int main ()
 {
     int a = 1;
     try
        assert (a == 0);
     catch (Throwable t)
        {}
     return 0;
 }

 Which line makes the program enter the "invalid state"?
Is it in the "invalid state" right after the condition has been evaluated or not before the AssertError has been thrown? What if the program is compiled with -release? Is it still in "invalid state"?
I don’t know for D because the spec is not that precise. In C++, which is comparable in this regard, the invalid state (aka. undefined behavior, UB) is entered when executing a code that is UB is inevitable. An optimizer may reason: * Throwable is caught; if it happens to be an Error, that is UB. * The only statement in the try block succeeds or throws an Error. (It may thus remove the assert and the try-catch entirely.) * Variable a is never re-assigned, it is effectively const. * Therefore, the assert always fails. (It must not be treated like assert(false), tho.) * Therefore, any execution will throw and catch an Error. The program may be replaced by virtually anything, including doing nothing at all. No clue if D allows or forbids UB back-propagation. Note that in C++, a lot more simple things are UB which means a lot more things risk UB. Signed arithmetic in C++ would not qualify for D’s safe because signed overflow is UB in C++ (not unsigned, tho); signed overflow it is defined as being modulo 2ⁿ in D.
Jan 09 2023
prev sibling parent reply Dukc <ajieskola gmail.com> writes:
On Friday, 25 November 2022 at 12:29:26 UTC, Ali Çehreli wrote:
 On 11/25/22 00:12, Dukc wrote:

 Discussion: As I commented on the bug report as well, D is
already in
 the best-effort business when it comes to Errors:
What does "best-effort" mean?
(I am not sure whether I should have used the word "hopeful" here. I will continue with best-effort.) [snip]
Thanks for the explanation.
 Add to the above another observation which I had after Patrick 
 Schluter's message: Even the execution of the assert check is 
 best-effort because if the check will fail, the program has 
 been in invalid state for an unknow amount of time anyway.
I disagree slightly. Yes, the higher level purpose the assertion is accomplishing is best-effort - that is, shutting down a program overtaken by a bug. But I think the implementation itself should not be so. If we assert that x is 5, catching x being 4 must happen every single time, not on best effort basis, unless the assertion is disabled by a compiler switch.
Dec 08 2022
parent =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 12/8/22 08:22, Dukc wrote:

 Add to the above another observation which I had after Patrick
 Schluter's message: Even the execution of the assert check is
 best-effort because if the check will fail, the program has been in
 invalid state for an unknow amount of time anyway.
I disagree slightly. Yes, the higher level purpose the assertion is accomplishing is best-effort - that is, shutting down a program overtaken by a bug. But I think the implementation itself should not be so. If we assert that x is 5, catching x being 4 must happen every single time, not on best effort basis, unless the assertion is disabled by a compiler switch.
The program may be affected by undefined behavior, which may cause x to have any value. I used "hopeful" and then "best-effort" but a better phrase might have been "wishful": A program is trying to catch itself in good (or bad) state by executing some code, code usually related to the bad state that is about to be caught: assert(o.foo == o.bar * 2); foo and bar may be member functions of an object in bad state. It is wishful thinking because I am using the same object to catch its bad state. Let's say I am happy I caught the program in bad state and am happy to about to abort the program: That's only happy thinking because I should have aborted when the invariant was broken in the first place, not when I haphazardly realized at a later time. What damage has already been done? If I am happy I am aborting, am I sadder I am aborting only now, some time later than that unknown time when the invariant was broken? I got into this theoretical discussion but I don't feel well equipped. Maybe we use assert to catch both logic errors as well as hardware issues. Perhaps we need to classify error categories better first. I don't know enough. Ali
Dec 08 2022
prev sibling next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 11/18/22 13:36, RazvanN wrote:
 Is deprecating having an assert message that may throw a severe restriction?
Yes.
Nov 19 2022
prev sibling next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 11/18/22 13:36, RazvanN wrote:
 Ideally, we could do something along the lines of:
 
 ```d
 assert(i == 42,
         (const(char)[] msg,
          try { msg = format("Bad parameter:", i); },
          catch (Exception e) { msg = "Assert message evaluation has 
 failed";},
          msg)
         );
 ```
Please don't just swallow the exception and error message. This particular fix is worse than the problem.
Nov 19 2022
prev sibling parent Iain Buclaw <ibuclaw gdcproject.org> writes:
On Friday, 18 November 2022 at 12:36:14 UTC, RazvanN wrote:
 I have stumbled upon: 
 https://issues.dlang.org/show_bug.cgi?id=17226

 I want to fix it, however, the solution is not obvious.

 Take this code:
Surely this could be simplified to ``` throw new Exception(assert(0)); ``` Or... ``` assert(false, assert(false)); ```
Jan 10 2023