digitalmars.D - Make using compiled libs with debug code better
- Steven Schveighoffer (37/37) Oct 18 2021 I wrote about this in a reply to a learn thread, but realized I should
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (5/10) Oct 18 2021 Isn't this how contracts should work in general? You test the
- Imperatorn (3/6) Oct 18 2021 The first step is to just have an assert-enabled version at least.
- H. S. Teoh (11/16) Oct 18 2021 We've brought this up before years ago. For whatever reason nothing
- Elronnd (6/11) Oct 18 2021 What if I take a pointer to a function with contracts and call it
- Steven Schveighoffer (16/28) Oct 19 2021 A good point. There are also issues with virtual functions along these
- Paul Backus (8/21) Oct 19 2021 1. Add a `-preview=releaseModeChecks` switch that enables
- Tim (6/11) Oct 19 2021 For virtual functions it would be better to evaluate the in
- bauss (5/36) Oct 19 2021 The solution to that problem would be assertion levels, like you
- apz28 (8/12) Oct 18 2021 Learn from Delphi. It implement long time ago.
- Paul Backus (11/16) Oct 18 2021 My radical idea (which I also brought up on Discord) is that we
- rikki cattermole (14/18) Oct 18 2021 That isn't radical at all.
- jfondren (9/16) Oct 18 2021 Nim has the same division, with the opposite default, in `assert`
- Paul Backus (6/13) Oct 18 2021 My guess is that if we changed the behavior of `-release`, D
- Tejas (6/22) Oct 18 2021 I thought `enforce` was our `-release` mode error checking
- rikki cattermole (3/5) Oct 18 2021 Right now code that could fail loudly, is failing silently.
- Paul Backus (6/10) Oct 18 2021 `enforce` and `assert` are not interchangeable. Walter discusses
- Tejas (4/16) Oct 18 2021 I was thinking more about exceptions that should not be
- bauss (7/45) Oct 19 2021 In theory, this issue means that any code using Phobos is open to
- Imperatorn (2/10) Oct 19 2021 Yeah, we should take this seriously
I wrote about this in a reply to a learn thread, but realized I should really post it here on its own. A user posted seemingly innocuous code that caused a segmentation fault [here](https://forum.dlang.org/post/nkfejgkrzntybqetkvkj forum.dlang.org), and also asked about it on Discord. We worked through trying to figure out the issue, and it turns out it was a memory corruption (the seg fault was deep inside the garbage collector). But not one that I would have expected. It was an out-of-bounds access passed into a [std.bitmanip.BitArray](https://dlang.org/phobos/std_bitmanip.html#BitArray), which corrupted GC metadata, and caused a later allocation to fail. Like any reasonable array type, `BitArray` uses asserts to enforce bounds. And he did NOT turn off bounds checks. So what happened? Phobos is compiled in release mode, even if *your code is not*. So bounds checks are disabled based on the type. If `BitArray` was a template, it might have bounds checks enabled. But maybe not, if the compiler detected it was already instantiated inside Phobos and decided it didn't need to emit the code for it. This kind of Russian Roulette seems prone to cause hours of debugging when it could give you an answer instantly. How do we fix such a thing? The easiest thing I can think of is to ship an assert-enabled version of Phobos, and use that when requested. This at least allows a quick mechanism to test your code with bounds checks (and other asserts) enabled. LDC actually already has this, see https://d.godbolt.org/z/feETE8W78 (courtesy of Max Haughton) But this feels awkward. If you build your code with bounds checks, you would like the libraries you use to have them also. And a compiler switch isn't going to help you when you are using other libs. Now consider that `BitArray`, being quite old, uses contracts to enforce its bounds checks. However, D runs the contracts inside the function itself, instead of at the call site. But this seems wrong to me, as the contracts are checking the *caller's* code, not the *callee's*. Would it be a reasonable thing to change contracts to be called by the caller instead of the callee? Is that something that could make its way into D, such that the input checking of functions that are compiled for release still can run when you compile your code in non-release mode? -Steve
Oct 18 2021
On Monday, 18 October 2021 at 13:04:47 UTC, Steven Schveighoffer wrote:Would it be a reasonable thing to change contracts to be called by the caller instead of the callee? Is that something that could make its way into D, such that the input checking of functions that are compiled for release still can run when you compile your code in non-release mode?Isn't this how contracts should work in general? You test the preconditions before calling the function, hoping that the optimizer will resolve them at compiletime?
Oct 18 2021
On Monday, 18 October 2021 at 13:04:47 UTC, Steven Schveighoffer wrote:I wrote about this in a reply to a learn thread, but realized I should really post it here on its own. [...]The first step is to just have an assert-enabled version at least.
Oct 18 2021
On Mon, Oct 18, 2021 at 09:04:47AM -0400, Steven Schveighoffer via Digitalmars-d wrote: [...]Would it be a reasonable thing to change contracts to be called by the caller instead of the callee?We've brought this up before years ago. For whatever reason nothing came of that discussion. But yeah, it doesn't make sense for contracts to be called in the callee; it should be called in the caller. Well, in-contracts, anyway. Out-contracts probably should be in the callee.Is that something that could make its way into D, such that the input checking of functions that are compiled for release still can run when you compile your code in non-release mode?[...] That would be very nice. T -- If it breaks, you get to keep both pieces. -- Software disclaimer notice
Oct 18 2021
On Monday, 18 October 2021 at 13:04:47 UTC, Steven Schveighoffer wrote:Would it be a reasonable thing to change contracts to be called by the caller instead of the callee? Is that something that could make its way into D, such that the input checking of functions that are compiled for release still can run when you compile your code in non-release mode?What if I take a pointer to a function with contracts and call it from someplace that doesn't know about those contracts? Better to leave assertions on by default, and make you have to go out of your way to turn them off. IMO.
Oct 18 2021
On 10/18/21 5:51 PM, Elronnd wrote:On Monday, 18 October 2021 at 13:04:47 UTC, Steven Schveighoffer wrote:A good point. There are also issues with virtual functions along these lines. The only thing I can think of is to compile the contract code, provide 2 entry points for the function, and let the caller pick the one they want. This would have the same effect.Would it be a reasonable thing to change contracts to be called by the caller instead of the callee? Is that something that could make its way into D, such that the input checking of functions that are compiled for release still can run when you compile your code in non-release mode?What if I take a pointer to a function with contracts and call it from someplace that doesn't know about those contracts?Better to leave assertions on by default, and make you have to go out of your way to turn them off. IMO.This would leave Phobos in pretty bad shape for performance. Especially with invariants. I know for instance that RedBlackTree has an invariant that validates the tree correctness on every operation (and this runs before and after every function). However, if you build your project with dub, every library you depend on (with source available) will be built the same as your application. So Phobos is kind of alone in this regard based on the way we distribute libraries. I'm not sure what the right answer is, but that is what this thread is for -- can we solve this in a non-disruptive or minimally-disruptive way? -Steve
Oct 19 2021
On Tuesday, 19 October 2021 at 13:57:31 UTC, Steven Schveighoffer wrote:1. Add a `-preview=releaseModeChecks` switch that enables assertions and contract checks in `-release` builds. 2. Build Phobos's unit tests with and without the new `-preview` switch and check for performance regressions. 3. Add `debug` to any assertions or contracts that cause significant performance regressions.Better to leave assertions on by default, and make you have to go out of your way to turn them off. IMO.This would leave Phobos in pretty bad shape for performance. Especially with invariants. I know for instance that RedBlackTree has an invariant that validates the tree correctness on every operation (and this runs before and after every function). However, if you build your project with dub, every library you depend on (with source available) will be built the same as your application. So Phobos is kind of alone in this regard based on the way we distribute libraries. I'm not sure what the right answer is, but that is what this thread is for -- can we solve this in a non-disruptive or minimally-disruptive way?
Oct 19 2021
On Tuesday, 19 October 2021 at 13:57:31 UTC, Steven Schveighoffer wrote:On 10/18/21 5:51 PM, Elronnd wrote:For virtual functions it would be better to evaluate the in contract at the call site, because the caller has to fulfill the in contract. Here is the relevant bug report: https://issues.dlang.org/show_bug.cgi?id=6857What if I take a pointer to a function with contracts and call it from someplace that doesn't know about those contracts?A good point. There are also issues with virtual functions along these lines.
Oct 19 2021
On Tuesday, 19 October 2021 at 13:57:31 UTC, Steven Schveighoffer wrote:On 10/18/21 5:51 PM, Elronnd wrote:The solution to that problem would be assertion levels, like you declare how important an assertion is and then you can toggle based on that.On Monday, 18 October 2021 at 13:04:47 UTC, Steven Schveighoffer wrote:A good point. There are also issues with virtual functions along these lines. The only thing I can think of is to compile the contract code, provide 2 entry points for the function, and let the caller pick the one they want. This would have the same effect.Would it be a reasonable thing to change contracts to be called by the caller instead of the callee? Is that something that could make its way into D, such that the input checking of functions that are compiled for release still can run when you compile your code in non-release mode?What if I take a pointer to a function with contracts and call it from someplace that doesn't know about those contracts?Better to leave assertions on by default, and make you have to go out of your way to turn them off. IMO.This would leave Phobos in pretty bad shape for performance. Especially with invariants. I know for instance that RedBlackTree has an invariant that validates the tree correctness on every operation (and this runs before and after every function). However, if you build your project with dub, every library you depend on (with source available) will be built the same as your application. So Phobos is kind of alone in this regard based on the way we distribute libraries. I'm not sure what the right answer is, but that is what this thread is for -- can we solve this in a non-disruptive or minimally-disruptive way? -Steve
Oct 19 2021
On Monday, 18 October 2021 at 13:04:47 UTC, Steven Schveighoffer wrote:I wrote about this in a reply to a learn thread, but realized I should really post it here on its own. A user posted seemingly innocuous code that caused a segmentation faultLearn from Delphi. It implement long time ago. It always deploy library in release (default) and debug. The compiler has option to compile/use Debug Library (Use Debug DCUs). So for D, just deploy ...lib\debug and add compiler option to use it Happy coding!
Oct 18 2021
On Monday, 18 October 2021 at 13:04:47 UTC, Steven Schveighoffer wrote:Would it be a reasonable thing to change contracts to be called by the caller instead of the callee? Is that something that could make its way into D, such that the input checking of functions that are compiled for release still can run when you compile your code in non-release mode?My radical idea (which I also brought up on Discord) is that we should enable *all* contract checks and asserts in release mode by default, and tell programmers to use `debug assert(...)` if they want a particular check to be removed in release builds. Of course, if you really wanted to disable contract checks or assertions at build time, you would still be able to do so with `-check=assert=off` and `-check=[in|out|invariant]=off`. But requiring an explicit opt-in would make this much less of a footgun than it currently is.
Oct 18 2021
On 19/10/2021 12:17 PM, Paul Backus wrote:My radical idea (which I also brought up on Discord) is that we should enable *all* contract checks and asserts in release mode by default, and tell programmers to use `debug assert(...)` if they want a particular check to be removed in release builds.That isn't radical at all. "I believe that range checking should be used far more often than it currently is, but not everywhere. On the other hand I am really assuming infallible hardware when I say this; surely I wouldn't want to remove the parity check mechanism from the hardware, even under a hypothetical assumption that it was slowing down the computation. Additional memory protection is necessary to prevent my program from harming someone else's, and theirs from clobbering mine. My arguments are directed towards compiled-in tests, not towards the hardware mechanisms which are really needed to ensure reliability." - 1974 Structured Programming with go to Statements - Donald Knuth. In context having the default be sanity checks turned on, is probably the right way to go.
Oct 18 2021
On Monday, 18 October 2021 at 23:29:09 UTC, rikki cattermole wrote:On 19/10/2021 12:17 PM, Paul Backus wrote:Nim has the same division, with the opposite default, in `assert` (erased in release builds) and `doAssert` (retained in release builds). D can simulate a bunch of different kinds of asserts by abusing dmd flags and different places that asserts can appear in - function bodies, contract bodies, unittests, and debug statements, version blocks, but what D's missing is the *convention* of using these asserts differently.My radical idea (which I also brought up on Discord) is that we should enable *all* contract checks and asserts in release mode by default, and tell programmers to use `debug assert(...)` if they want a particular check to be removed in release builds.That isn't radical at all.
Oct 18 2021
On Monday, 18 October 2021 at 23:37:51 UTC, jfondren wrote:Nim has the same division, with the opposite default, in `assert` (erased in release builds) and `doAssert` (retained in release builds). D can simulate a bunch of different kinds of asserts by abusing dmd flags and different places that asserts can appear in - function bodies, contract bodies, unittests, and debug statements, version blocks, but what D's missing is the *convention* of using these asserts differently.My guess is that if we changed the behavior of `-release`, D programmers would learn the new convention pretty quickly. Even if they didn't, though, the change is fail-safe--it will not break any code that isn't already broken in debug builds. So as compiler changes go, this one is relatively low-risk.
Oct 18 2021
On Monday, 18 October 2021 at 23:29:09 UTC, rikki cattermole wrote:On 19/10/2021 12:17 PM, Paul Backus wrote:I thought `enforce` was our `-release` mode error checking mechanism? Why don't we encourage that instead of trying to change things(which will result in a battle of inertia).[...]That isn't radical at all. "I believe that range checking should be used far more often than it currently is, but not everywhere. On the other hand I am really assuming infallible hardware when I say this; surely I wouldn't want to remove the parity check mechanism from the hardware, even under a hypothetical assumption that it was slowing down the computation. Additional memory protection is necessary to prevent my program from harming someone else's, and theirs from clobbering mine. My arguments are directed towards compiled-in tests, not towards the hardware mechanisms which are really needed to ensure reliability." - 1974 Structured Programming with go to Statements - Donald Knuth. In context having the default be sanity checks turned on, is probably the right way to go.
Oct 18 2021
On 19/10/2021 3:02 PM, Tejas wrote:Why don't we encourage that instead of trying to change things(which will result in a battle of inertia).Right now code that could fail loudly, is failing silently. There is no inertia to worry about.
Oct 18 2021
On Tuesday, 19 October 2021 at 02:02:31 UTC, Tejas wrote:I thought `enforce` was our `-release` mode error checking mechanism? Why don't we encourage that instead of trying to change things(which will result in a battle of inertia).`enforce` and `assert` are not interchangeable. Walter discusses the difference at length in this thread from 2014: https://forum.dlang.org/thread/m07gf1$18jl$1 digitalmars.com TL;DR: exceptions are for recoverable failures; assertions are for non-recoverable failures.
Oct 18 2021
On Tuesday, 19 October 2021 at 03:09:16 UTC, Paul Backus wrote:On Tuesday, 19 October 2021 at 02:02:31 UTC, Tejas wrote:I was thinking more about exceptions that should not be caught(the class `Error` inheriting from `object.throwable`). Stuff like [this](https://dlang.org/phobos/object.html#.Error)I thought `enforce` was our `-release` mode error checking mechanism? Why don't we encourage that instead of trying to change things(which will result in a battle of inertia).`enforce` and `assert` are not interchangeable. Walter discusses the difference at length in this thread from 2014: https://forum.dlang.org/thread/m07gf1$18jl$1 digitalmars.com TL;DR: exceptions are for recoverable failures; assertions are for non-recoverable failures.
Oct 18 2021
On Monday, 18 October 2021 at 13:04:47 UTC, Steven Schveighoffer wrote:I wrote about this in a reply to a learn thread, but realized I should really post it here on its own. A user posted seemingly innocuous code that caused a segmentation fault [here](https://forum.dlang.org/post/nkfejgkrzntybqetkvkj forum.dlang.org), and also asked about it on Discord. We worked through trying to figure out the issue, and it turns out it was a memory corruption (the seg fault was deep inside the garbage collector). But not one that I would have expected. It was an out-of-bounds access passed into a [std.bitmanip.BitArray](https://dlang.org/phobos/std_bitmanip.html#BitArray), which corrupted GC metadata, and caused a later allocation to fail. Like any reasonable array type, `BitArray` uses asserts to enforce bounds. And he did NOT turn off bounds checks. So what happened? Phobos is compiled in release mode, even if *your code is not*. So bounds checks are disabled based on the type. If `BitArray` was a template, it might have bounds checks enabled. But maybe not, if the compiler detected it was already instantiated inside Phobos and decided it didn't need to emit the code for it. This kind of Russian Roulette seems prone to cause hours of debugging when it could give you an answer instantly. How do we fix such a thing? The easiest thing I can think of is to ship an assert-enabled version of Phobos, and use that when requested. This at least allows a quick mechanism to test your code with bounds checks (and other asserts) enabled. LDC actually already has this, see https://d.godbolt.org/z/feETE8W78 (courtesy of Max Haughton) But this feels awkward. If you build your code with bounds checks, you would like the libraries you use to have them also. And a compiler switch isn't going to help you when you are using other libs. Now consider that `BitArray`, being quite old, uses contracts to enforce its bounds checks. However, D runs the contracts inside the function itself, instead of at the call site. But this seems wrong to me, as the contracts are checking the *caller's* code, not the *callee's*. Would it be a reasonable thing to change contracts to be called by the caller instead of the callee? Is that something that could make its way into D, such that the input checking of functions that are compiled for release still can run when you compile your code in non-release mode? -SteveIn theory, this issue means that any code using Phobos is open to buffer overflows :) Which is a huge problem for D, considering it has marketed itself as a language where it's (in theory again) impossible to have such problems.
Oct 19 2021
On Tuesday, 19 October 2021 at 11:46:25 UTC, bauss wrote:On Monday, 18 October 2021 at 13:04:47 UTC, Steven Schveighoffer wrote:Yeah, we should take this seriously[...]In theory, this issue means that any code using Phobos is open to buffer overflows :) Which is a huge problem for D, considering it has marketed itself as a language where it's (in theory again) impossible to have such problems.
Oct 19 2021