January 22

On Monday, 22 January 2024 at 01:14:06 UTC, Steven Schveighoffer wrote:

>

On Sunday, 21 January 2024 at 16:05:40 UTC, Gavin Gray wrote:

>

The following code:

ulong charlie = 11;
long johnstone = std.algorithm.comparison.max(0, -charlie);
writeln(format!"johnstone %s"(johnstone));

Results in (without any warning(s)):
johnstone -11

However you choose to look at it, this means -11 > 0 (regardless of all arguments concerning implicit conversions, 1's and 2's complements, being efficient, etc).

The language should not allow unary unsigned anything.

This is unlikely to get fixed, just due to the nature of D's philosophy when it comes to C compatibility.

There's a hope that OpenD may try to improve the current situation. A related discussion can be found here: https://github.com/orgs/opendlang/discussions/4

>

It would also break a lot of existing code.

How did you estimate that it's a lot of existing code? As an experiment, I tried to patch Druntime and Phobos to avoid signed overflows roughly a year ago: https://github.com/ssvb/gcc/commits/gdc-ftrapv-phobos-20220209/

And there were not too many places in the code that actually needed any fixes. Additionally taking care of unsigned overflows would surely require more changes, but I doubt that they are going to be big. In most cases encountering an arithmetic overflow is unexpected and undesired, it's typically the symptom of a bug in the code. Some clever bit-tricks relying on two's complement wrap-around exist, but they are: 1) not very common 2) can be easily debugged if arithmetic overflows are trapped at runtime 3) can be easily patched up. The two's complement wraparound behavior mandated by the D language spec is a non-technical political decision, intended to make life easier for the DMD compiler developers, but ignoring the needs of the users.

January 23

On Monday, 22 January 2024 at 17:15:55 UTC, bachmeier wrote:

>

I get incorrect results, and when I'm lucky, my program segfaults because I accessed something I shouldn't. When I'm not, it silently and happily gives me the wrong answer.

Maybe a compiler warning (not error) would help with detecting
the unsigned into signed issue, within same size types?

core.checkedint.negs probably doesn't help, because unsigned into signed is a different issue?

January 23

On Monday, 22 January 2024 at 19:49:19 UTC, Siarhei Siamashka wrote:

>

Additionally taking care of unsigned overflows would surely require more changes, but I doubt that they are going to be big.

If it would cause too much problems, it could be behind
a compiler switch -strict, which I would enable by default.

Maybe there is already such a flag for not allowing implicit
unsigned <> signed conversion?

Important stuff if we want to go onto a mission to Mars. ;)

January 23

On Monday, 22 January 2024 at 19:11:50 UTC, Siarhei Siamashka wrote:

>

On Monday, 22 January 2024 at 16:39:10 UTC, Nick Treleaven wrote:

>

Memory safety issues are a worse class of bug than arithmetic bugs. The latter are reproducible if you feed them the same input.

Memory safety bugs are reproducible with the tools like valgrind.

Not necessarily, valgrind can execute programs too slowly for human input, so anything that relies on timing is difficult to reproduce. It also uses far more memory, it could be too much memory for the system.

>

Whereas arithmetic overflow bugs are a real PITA to debug. Assuming that the incorrect results are even noticed.

You're talking about debugging, whereas I'm saying you often don't even have a chance to notice memory-safety bugs, because they might not even occur on the development system, only on the production system.

And even if you know there's a memory-safety problem, you can't easily narrow down where it is (without language support for memory-safety). With arithmetic problems it's far easier to narrow down which code is causing them.

But I'm strongly in favour of catching any bugs at compile-time (and have been since before I discovered D). I just object to anyone trying to downgrade the importance of automated memory-safety checking.

January 23

On Monday, 22 January 2024 at 19:49:19 UTC, Siarhei Siamashka wrote:

>

The two's complement wraparound behavior mandated by the D language spec is a non-technical political decision, intended to make life easier for the DMD compiler developers, but ignoring the needs of the users.

Actually it is for compatibility when porting C code to D.

January 23

On Tuesday, 23 January 2024 at 12:34:38 UTC, Nick Treleaven wrote:

>

But I'm strongly in favour of catching any bugs at compile-time (and have been since before I discovered D). I just object to anyone trying to downgrade the importance of automated memory-safety checking.

I'm not downgrading the importance of memory safety. All I'm saying is that you can't sell D as a safe language if has bugs like this.

Here's a reduced version of one of the most bizarre bugs I've dealt with in any language. The only reason I didn't move on to another language was because I was too busy at the time.

The code allows for initial values if the index is less than 0, otherwise it returns the element.

import std;

double value(T)(T index, double * x) {
  if (index - 5 < 0) {
    return 0.0;
  } else {
    return x[index-5];
  }
}

void main() {
  double[] v = [1.1, 2.2, 3.3];
  // Works
  writeln(value(3, v.ptr));
  // Lucky: program segfaults
  writeln(value(v.length, v.ptr));
}

I noticed this behavior only because the program crashes. Once I figured out what was going on, I realized that the thousands of lines of code I had already written needed to be checked and possibly rewritten. If only I had a compiler to do that for me.

January 23

On Tuesday, 23 January 2024 at 17:54:25 UTC, bachmeier wrote:

>

On Tuesday, 23 January 2024 at 12:34:38 UTC, Nick Treleaven wrote:

>

But I'm strongly in favour of catching any bugs at compile-time (and have been since before I discovered D). I just object to anyone trying to downgrade the importance of automated memory-safety checking.

I'm not downgrading the importance of memory safety. All I'm saying is that you can't sell D as a safe language if has bugs like this.

Here's a reduced version of one of the most bizarre bugs I've dealt with in any language. The only reason I didn't move on to another language was because I was too busy at the time.

The code allows for initial values if the index is less than 0, otherwise it returns the element.

import std;

double value(T)(T index, double * x) {
  if (index - 5 < 0) {
    return 0.0;
  } else {
    return x[index-5];
  }
}

void main() {
  double[] v = [1.1, 2.2, 3.3];
  // Works
  writeln(value(3, v.ptr));
  // Lucky: program segfaults
  writeln(value(v.length, v.ptr));
}

I noticed this behavior only because the program crashes. Once I figured out what was going on, I realized that the thousands of lines of code I had already written needed to be checked and possibly rewritten. If only I had a compiler to do that for me.

This code seems to be doing everything it can to run into undefined behaviour, though?

Why is index of a type T that has no requirements at all (when the implementation quite clearly wants size_t, or at least an unsigned numerical value)? Why is it using a pointer for x when clearly you intend to use it as a slice? You probably have context that I don't, but I would never expect this sort of code to be anywhere near @safe :D

January 23

On Tuesday, 23 January 2024 at 19:27:26 UTC, Renato wrote:

> >

Here's a reduced version of one of the most bizarre bugs I've dealt with in any language. The only reason I didn't move on to another language was because I was too busy at the time.

The code allows for initial values if the index is less than 0, otherwise it returns the element.

import std;

double value(T)(T index, double * x) {
  if (index - 5 < 0) {
    return 0.0;
  } else {
    return x[index-5];
  }
}

void main() {
  double[] v = [1.1, 2.2, 3.3];
  // Works
  writeln(value(3, v.ptr));
  // Lucky: program segfaults
  writeln(value(v.length, v.ptr));
}

I noticed this behavior only because the program crashes. Once I figured out what was going on, I realized that the thousands of lines of code I had already written needed to be checked and possibly rewritten. If only I had a compiler to do that for me.

This code seems to be doing everything it can to run into undefined behaviour, though?

Why is index of a type T that has no requirements at all (when the implementation quite clearly wants size_t, or at least an unsigned numerical value)? Why is it using a pointer for x when clearly you intend to use it as a slice? You probably have context that I don't, but I would never expect this sort of code to be anywhere near @safe :D

There are two things things that cause the problem. One is the use of a template and the other is passing an unsigned type. The reason the first parameter uses a template is because there are a lot of types I could send as the first argument, and for some of them there was a transformation of index (for instance, you can pass a date as a long[2], or you can pass another type and pull out the length, that sort of thing). It's using a pointer because I was working with a C library, and that's how the data is stored and passed around.

The data is time series. If after the transformations the index is less than zero, it returns 0.0, which is used for all pre-sample values. If it's non-negative, return the element at that position.

One of the nice things about D is the ability to write this kind of code in such a natural and (I thought) intuitive style. I really like the way all this comes together. There's really no way that code should have been able to do anything wrong. What's terribly frustrating is that the compiler had full knowledge of what was happening, but by choice it didn't say anything, even though D is supposed to prevent these things that happen in C.

January 23

On Tuesday, 23 January 2024 at 21:18:53 UTC, bachmeier wrote:

>

There are two things things that cause the problem. One is the use of a template and the other is passing an unsigned type. The reason the first parameter uses a template is because there are a lot of types I could send as the first argument, and for some of them there was a transformation of index (for instance, you can pass a date as a long[2], or you can pass another type and pull out the length, that sort of thing). It's using a pointer because I was working with a C library, and that's how the data is stored and passed around.

The data is time series. If after the transformations the index is less than zero, it returns 0.0, which is used for all pre-sample values. If it's non-negative, return the element at that position.

One of the nice things about D is the ability to write this kind of code in such a natural and (I thought) intuitive style. I really like the way all this comes together. There's really no way that code should have been able to do anything wrong. What's terribly frustrating is that the compiler had full knowledge of what was happening, but by choice it didn't say anything, even though D is supposed to prevent these things that happen in C.

While I can understand your frustration, it seems to me D is not to blame in this instance because the code is quite patently using unsafe constructs (D does not claim to be fully safe).

Would something like this work?

double value(T)(T index, double* x) if (is(T : size_t))
{
    if (index < 5 || x == null)
    {
        return 0.0;
    }
    else
    {
        return x[index - 5];
    }
}

void main()
{
    import std.stdio;
    import std.range : iota;

    double[] ds = [1, 2, 3, 4, 5, 6];
    ubyte b = 1;
    foreach (_; iota(12))
    {
        writeln(value(b++, ds.ptr));
    }
}

This will still read rubbish if the index goes past the actual array (because I assume you can't get the exact length from the C code? If you can, you should pass that in and do the bounds check yourself) but there's no unsigned type mistakes (notice that it's almost always a mistake to subract from any unsigned type - D scanner correctly warns about that).

January 23

On Tuesday, 23 January 2024 at 21:40:46 UTC, Renato wrote:

>

While I can understand your frustration, it seems to me D is not to blame in this instance because the code is quite patently using unsafe constructs

I wouldn't blame bachmeier, because many reduced testcases distilled from the real code tend to look nonsensical. The arithmetic overflows, silent undesirable signed/unsigned casts and other pitfalls happen in the @safe code too. The use of pointers and other unsafe constructs in the provided testcase is a red herring.