www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Making Errors errors

reply Max Haughton <maxhaton gmail.com> writes:
It has been discussed in a different thread (Making throwing an 
error an instant failure, catching Error etc.)

I am starting to put together a patch to try out this behaviour, 
what do we actually want it to do - should it call a user 
specified handler, druntime, c etc.?

The rationale makes perfect sense (Errors should indicate 
something has gone wrong, the program is in an invalid state - by 
definition you cannot recover), but the exact behaviour must be 
specified.
Jan 28 2021
next sibling parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Thursday, 28 January 2021 at 17:59:41 UTC, Max Haughton wrote:
 I am starting to put together a patch to try out this 
 behaviour, what do we actually want it to do - should it call a 
 user specified handler, druntime, c etc.?
Here's a crazy idea: when this switch is in place, it just never actually honors your catch handlers. so then throw Error is the same as an uncaught exception and thus trigger the debugger at the throw site instantly.
 (Errors should indicate something has gone wrong, the
 program is in an invalid state)
In real life Errors are rarely this extreme. Most of them indicate that the program *would* get into an invalid state if it proceeded, but since it stopped before actually executing it, things are still fine.
Jan 28 2021
parent reply Max Haughton <maxhaton gmail.com> writes:
On Thursday, 28 January 2021 at 18:33:17 UTC, Adam D. Ruppe wrote:
 On Thursday, 28 January 2021 at 17:59:41 UTC, Max Haughton 
 wrote:
 I am starting to put together a patch to try out this 
 behaviour, what do we actually want it to do - should it call 
 a user specified handler, druntime, c etc.?
Here's a crazy idea: when this switch is in place, it just never actually honors your catch handlers. so then throw Error is the same as an uncaught exception and thus trigger the debugger at the throw site instantly.
 (Errors should indicate something has gone wrong, the
 program is in an invalid state)
In real life Errors are rarely this extreme. Most of them indicate that the program *would* get into an invalid state if it proceeded, but since it stopped before actually executing it, things are still fine.
Not a bad idea. I think I'll try that first. And that may well be true but when I use A assert, for example, I often think of it in the same context as one would in formal verification i.e if this is wrong something catastrophic has happened. If the problem is exceptional but ultimately recoverable use Exceptions. I also feel that, potentially against the grain of Phobos, parsers should not throw - e.g. std.conv probably fails more than it succeeds in many code bases.
Jan 28 2021
parent reply sighoya <sighoya gmail.com> writes:
On Thursday, 28 January 2021 at 18:47:27 UTC, Max Haughton wrote:
 I often think of it in the same context as one would in formal 
 verification i.e if this is wrong something catastrophic has 
 happened.
Catastrophic in which sense? To the whole program? As mentioned by Adam, logical errors rarely justify whole program abortion. Downstream code doesn't necessarily require computations to succeed on the whole line if it can deal with partial results or provide alternate ways to retrieve missing information.
 If the problem is exceptional but ultimately recoverable use 
 Exceptions.
Not all Exceptions are recoverable especially if we don't know how to deal with. But there may be exemptions silently ignore any kind of exception. The distinction between error and exception is sometimes hard to resolve. Is it a usual error if the document doesn't parse or is it exceptional? I think it all depends on subjective assumptions.
 I also feel that, potentially against the grain of Phobos, 
 parsers should not throw - e.g. std.conv probably fails more 
 than it succeeds in many code bases.
This example seems reiterated infinite times and seems true in most cases as a parser error is just a value to deal with, we don't abort the program immediately, we collect errors in order to display them as a sequence of mistakes. And what to do for cases where aren't interested in any partial results, seldom, but such cases would rather be implemented with exception handling.
Jan 28 2021
parent reply Paul Backus <snarwin gmail.com> writes:
On Thursday, 28 January 2021 at 19:39:41 UTC, sighoya wrote:
 On Thursday, 28 January 2021 at 18:47:27 UTC, Max Haughton 
 wrote:
 I often think of it in the same context as one would in formal 
 verification i.e if this is wrong something catastrophic has 
 happened.
Catastrophic in which sense? To the whole program? As mentioned by Adam, logical errors rarely justify whole program abortion. Downstream code doesn't necessarily require computations to succeed on the whole line if it can deal with partial results or provide alternate ways to retrieve missing information.
From now on, whenever this topic comes up, I'm just going to post this link: http://joeduffyblog.com/2016/02/07/the-error-model/#bugs-arent-recoverable-errors
Jan 28 2021
parent reply sighoya <sighoya gmail.com> writes:
On Thursday, 28 January 2021 at 19:42:54 UTC, Paul Backus wrote:
 From now on, whenever this topic comes up, I'm just going to 
 post this link:

 http://joeduffyblog.com/2016/02/07/the-error-model/#bugs-arent-recoverable-errors
I haven't consumed the whole page, but the examples of both sections (bugs vs recoverable errors) seems to overlap to some extent which justifies the statements I made. It is sometimes very hard to distinguish between bugs and recoverable errors (which mostly aren't recoverable anyway, i.e. the term is confusing). A bug is even misleading here because it doesn't relate to error handling only, the majority of bugs doesn't cause errors just silently change the expected result. A bug is just the mismatch of specification and implementation. It makes sense to model them in language with formal verification requiring the compiler to reject your program if the specification doesn't match the implementation. Given range indexing as an example, we would certainly think of index violations as bugs. That may be true. However, the code following after the random access wouldn't be executed anyway and the caller may or may not depend hard on the result of the code fragment. A better take to go would definitely exclude pessimistically out-of-range-errors and bubble up possible errors on a higher level of understanding but that's even hard to do with (path) dependent typing. The cost is simply too high to support and more overly to adapt such fine-grained modeling. And introducing errors/bugs in addition to exceptions bifurcates your world again in two parts. I know that errors exist in Java productive development or just no one cares about them as their classification as errors is generally debatable.
Jan 28 2021
next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Thu, Jan 28, 2021 at 11:41:44PM +0000, sighoya via Digitalmars-d wrote:
 On Thursday, 28 January 2021 at 19:42:54 UTC, Paul Backus wrote:
 From now on, whenever this topic comes up, I'm just going to post
 this link:
 
 http://joeduffyblog.com/2016/02/07/the-error-model/#bugs-arent-recoverable-errors
[...]
 Given range indexing as an example, we would certainly think of index
 violations as bugs. That may be true. However, the code following
 after the random access wouldn't be executed anyway and the caller may
 or may not depend hard on the result of the code fragment.
[...] Actually, in the absence of a range check (in C, or D with -release), code following the access *will* be executed, with undefined behaviour. Also known by the more familiar name "buffer overflow exploit". T -- Why did the mathematician reinvent the square wheel? Because he wanted to drive smoothly over an inverted catenary road.
Jan 28 2021
prev sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Thursday, 28 January 2021 at 23:41:44 UTC, sighoya wrote:
 Given range indexing as an example, we would certainly think of 
 index violations as bugs. That may be true. However, the code 
 following after the random access wouldn't be executed anyway 
 and the caller may or may not depend hard on the result of the 
 code fragment.
If out-of-bounds array access is defined by the language spec as an unrecoverable error, an optimizing compiler is allowed to assume that no program ever recovers from it, and potentially re-order code based on that assumption. So you cannot actually be sure that "the code following after the random access wouldn't be executed." Of course, the key phrase here is "defined by the language spec." As a language designer, you are free to define out-of-bounds indexing as either recoverable or unrecoverable. But as a programmer, once the decision has been made and the spec has been written, you do not get a choice--either you play by the rules, or your code is wrong. I suspect a lot of the confusion on this issue comes from people mixing up the programmer's perspective and the language designer's perspective.
Jan 28 2021
next sibling parent reply sighoya <sighoya gmail.com> writes:
On Friday, 29 January 2021 at 00:39:08 UTC, Paul Backus wrote:
 If out-of-bounds array access is defined by the language spec 
 as an unrecoverable error, an optimizing compiler is allowed to 
 assume that no program ever recovers from it, and potentially 
 re-order code based on that assumption. So you cannot actually 
 be sure that "the code following after the random access 
 wouldn't be executed."
Okay, if out-of-bound exceptions cause UB, then we can neither abort the program entirely because UB now belongs to our semantic or the compiler have to completely reject any way of UB at compile time. I'm a fan of banning any occurrence of UB in a language, rather I prefer things to be implementation defined which is sometimes just not possible because of OS mystery. On the other side, it isn't anyway possible to assure the implementation satisfies the needs of the frontend, e.g. malloc may not allocate memory at all just print a smiley to console. From this point we could define any operation as UB, but it doesn't make that sense...
Jan 29 2021
parent reply Paul Backus <snarwin gmail.com> writes:
On Friday, 29 January 2021 at 14:19:02 UTC, sighoya wrote:
 On Friday, 29 January 2021 at 00:39:08 UTC, Paul Backus wrote:
 If out-of-bounds array access is defined by the language spec 
 as an unrecoverable error, an optimizing compiler is allowed 
 to assume that no program ever recovers from it, and 
 potentially re-order code based on that assumption. So you 
 cannot actually be sure that "the code following after the 
 random access wouldn't be executed."
Okay, if out-of-bound exceptions cause UB, then we can neither abort the program entirely because UB now belongs to our semantic or the compiler have to completely reject any way of UB at compile time.
"Unrecoverable error" does not mean the same thing as UB.
Jan 29 2021
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 29.01.21 15:23, Paul Backus wrote:
 On Friday, 29 January 2021 at 14:19:02 UTC, sighoya wrote:
 On Friday, 29 January 2021 at 00:39:08 UTC, Paul Backus wrote:
 If out-of-bounds array access is defined by the language spec as an 
 unrecoverable error, an optimizing compiler is allowed to assume that 
 no program ever recovers from it, and potentially re-order code based 
 on that assumption. So you cannot actually be sure that "the code 
 following after the random access wouldn't be executed."
Okay, if out-of-bound exceptions cause UB, then we can neither abort the program entirely because UB now belongs to our semantic or the compiler have to completely reject any way of UB at compile time.
"Unrecoverable error" does not mean the same thing as UB.
Why is that an useful thing to mandate on the language level? I can always choose to terminate the process if I actually think nothing useful remains to be done after hitting some condition. Even if the error is not recoverable, I might have something to say about how I want the program to not recover. The problem that needs to be solved is that you can catch errors and errors can be thrown from nothrow functions, and Walter does not want to allow exceptional control flow out of nothrow functions, as that defeats one of the reasons why that feature exists in the first place.
Jan 29 2021
prev sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 29.01.21 01:39, Paul Backus wrote:
 
 Of course, the key phrase here is "defined by the language spec." As a 
 language designer, you are free to define out-of-bounds indexing as 
 either recoverable or unrecoverable. But as a programmer, once the 
 decision has been made and the spec has been written, you do not get a 
 choice--either you play by the rules, or your code is wrong.
What you seem to ignore here is that it's the language's job to accommodate the programmer's use cases, not the other way around and that this is a forum where we discuss changes to the language specification.
Jan 29 2021
prev sibling next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 28.01.21 18:59, Max Haughton wrote:
 It has been discussed in a different thread (Making throwing an error an 
 instant failure, catching Error etc.)
 
 I am starting to put together a patch to try out this behaviour, what do 
 we actually want it to do - should it call a user specified handler, 
Ideally, yes. The handler should not be allowed to return and it would be useful if there was some way to allow stack unwinding in code compiled using a special compiler flag. (This requires some support from the compiler, basically the backend has to ignore nothrow in such builds.)
 druntime,
I guess that's the same as the first option, just a bit harder to access.
 c etc.?
I don't think the behavior should be hard-coded into the compiler.
 
 The rationale makes perfect sense (Errors should indicate something has 
 gone wrong, the program is in an invalid state - by definition you 
 cannot recover),
You may be able to recover if the failure is contained in a nonessential component. Process separation is sometimes a good way to do that, but it may be overkill for some projects.
 but the exact behaviour must be specified.
If the program were truly in an invalid state the behavior would be undefined and immediate termination would by definition be impossible to ensure. That's not always useful though, there's plenty of circumstances where immediately killing the program is not what you want, e.g. if the program was recording a sequence of user inputs that will allow you to reproduce the error. I think the immediate elevation of every bug into an issue that completely undermines the state of the abstract machine is not right for every use case. Those are at different levels of abstraction. Your abstract machine state is still okay even if some access fails a bounds check and if it happens in a nothrow function, there is no way to resume execution after the handler.
Jan 28 2021
prev sibling next sibling parent reply Andre Pany <andre s-e-a-p.de> writes:
On Thursday, 28 January 2021 at 17:59:41 UTC, Max Haughton wrote:
 It has been discussed in a different thread (Making throwing an 
 error an instant failure, catching Error etc.)

 I am starting to put together a patch to try out this 
 behaviour, what do we actually want it to do - should it call a 
 user specified handler, druntime, c etc.?

 The rationale makes perfect sense (Errors should indicate 
 something has gone wrong, the program is in an invalid state - 
 by definition you cannot recover), but the exact behaviour must 
 be specified.
What would be the effect of this change on the unit test runners we have (d-unit, silly, unit-threaded)? This might break their functionality, as they might catch Errors (unit tests calling assert). Kind regards Andre
Jan 28 2021
next sibling parent Max Haughton <maxhaton gmail.com> writes:
On Friday, 29 January 2021 at 06:03:08 UTC, Andre Pany wrote:
 On Thursday, 28 January 2021 at 17:59:41 UTC, Max Haughton 
 wrote:
 It has been discussed in a different thread (Making throwing 
 an error an instant failure, catching Error etc.)

 I am starting to put together a patch to try out this 
 behaviour, what do we actually want it to do - should it call 
 a user specified handler, druntime, c etc.?

 The rationale makes perfect sense (Errors should indicate 
 something has gone wrong, the program is in an invalid state - 
 by definition you cannot recover), but the exact behaviour 
 must be specified.
What would be the effect of this change on the unit test runners we have (d-unit, silly, unit-threaded)? This might break their functionality, as they might catch Errors (unit tests calling assert). Kind regards Andre
It would need to be a flag, I doubt this will ever be the default - however consider that Phobos at least already doesn't throw asserterrors if you test with release - if we wanted it as a option to be widely used it would just be a part of the test suite like any other feature.
Jan 29 2021
prev sibling next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 29.01.21 07:03, Andre Pany wrote:
 On Thursday, 28 January 2021 at 17:59:41 UTC, Max Haughton wrote:
 It has been discussed in a different thread (Making throwing an error 
 an instant failure, catching Error etc.)

 I am starting to put together a patch to try out this behaviour, what 
 do we actually want it to do - should it call a user specified 
 handler, druntime, c etc.?

 The rationale makes perfect sense (Errors should indicate something 
 has gone wrong, the program is in an invalid state - by definition you 
 cannot recover), but the exact behaviour must be specified.
What would be the effect of this change on the unit test runners we have (d-unit, silly, unit-threaded)? This might break their functionality, as they might catch Errors (unit tests calling assert).
Even the language itself catches AssertError to implement in contract inheritance.
Jan 29 2021
prev sibling parent Atila Neves <atila.neves gmail.com> writes:
On Friday, 29 January 2021 at 06:03:08 UTC, Andre Pany wrote:
 On Thursday, 28 January 2021 at 17:59:41 UTC, Max Haughton 
 wrote:
 It has been discussed in a different thread (Making throwing 
 an error an instant failure, catching Error etc.)

 I am starting to put together a patch to try out this 
 behaviour, what do we actually want it to do - should it call 
 a user specified handler, druntime, c etc.?

 The rationale makes perfect sense (Errors should indicate 
 something has gone wrong, the program is in an invalid state - 
 by definition you cannot recover), but the exact behaviour 
 must be specified.
What would be the effect of this change on the unit test runners we have (d-unit, silly, unit-threaded)? This might break their functionality, as they might catch Errors (unit tests calling assert). Kind regards Andre
Depends on the usage. The reason that unit-threaded catches errors is to support unit tests written with asserts, since those might even have been written before unit-threaded itself was. The idea is to use the custom assertions. My own projects wouldn't be affected in the slightest, for instance.
Jan 29 2021
prev sibling parent Jacob Carlborg <doob me.com> writes:
On Thursday, 28 January 2021 at 17:59:41 UTC, Max Haughton wrote:
 It has been discussed in a different thread (Making throwing an 
 error an instant failure, catching Error etc.)

 I am starting to put together a patch to try out this 
 behaviour, what do we actually want it to do - should it call a 
 user specified handler, druntime, c etc.?
I think there are two things that need to take into consideration: * Currently druntime catches all uncaught exceptions, that is `Throwable`, to be able to print the exception message and a stack trace. * When running unit tests there needs to be safe way to catch whatever `assert` is throwing to be able to continue running other unit tests. druntime relies on this. -- /Jacob Carlborg
Jan 29 2021