February 22, 2022

On Monday, 21 February 2022 at 22:56:30 UTC, Dennis wrote:

>

On Monday, 21 February 2022 at 21:50:31 UTC, Paul Backus wrote:

>

If the goal is being able to define custom pointer types, then the DIP should use that as an example instead of talking about file descriptors, and it should explain exactly which part of the example depends on this feature for memory safety (as the other examples do).

A double fclose on a FILE* is basically a double free. I thought the same would apply to raw file descriptors, but I just read that a double close simply results in an EBADF error, so maybe it's not a good example.

A more pertinent example around file descriptors and memory safety is void-initialization:

struct File
{
    void write(const(void)[] data) @safe;
    // ...
    private int fd;
}

void main() @safe
{
    File f = void; // this compiles in current language, because `File` doesn't have pointers
    f.write("hello"); // may corrupt memory if (implementation-defined) value of `f.fd` happens to correspond to an existing mapping
}
February 22, 2022

On Tuesday, 22 February 2022 at 08:47:55 UTC, Stanislav Blinov wrote:

>

A more pertinent example around file descriptors and memory safety is void-initialization:

struct File
{
    void write(const(void)[] data) @safe;
    // ...
    private int fd;
}

void main() @safe
{
    File f = void; // this compiles in current language, because `File` doesn't have pointers
    f.write("hello"); // may corrupt memory if (implementation-defined) value of `f.fd` happens to correspond to an existing mapping
}

If you attempt to fill in the missing part of your example, I think you will find that you cannot actually demonstrate memory corruption resulting from void-initialization of a file descriptor without the use of @trusted code (e.g., to cast the void* returned from mmap to some other type of pointer whose target type has unsafe values).

February 22, 2022

On Sunday, 20 February 2022 at 15:16:30 UTC, Dukc wrote:

>

Why we want to do that at all? DIP1000. Most of the potential of DIP1000 is wasted if you cannot prevent destruction before end of the scope:

@safe void abuse()
{ auto cont = SomeRaiiContainer([1,2,3]);
  scope ptr = &cont.front;
  destroy(cont);
  int oops = *ptr;
}

Doesn't the same problem occur just with reassignment?:

  scope ptr = &cont.front;
  cont = cont.init;
  int oops = *ptr;
February 22, 2022

On Tuesday, 22 February 2022 at 13:13:43 UTC, Paul Backus wrote:

>

On Tuesday, 22 February 2022 at 08:47:55 UTC, Stanislav Blinov wrote:

>

A more pertinent example around file descriptors and memory safety is void-initialization:

struct File
{
    void write(const(void)[] data) @safe;
    // ...
    private int fd;
}

void main() @safe
{
    File f = void; // this compiles in current language, because `File` doesn't have pointers
    f.write("hello"); // may corrupt memory if (implementation-defined) value of `f.fd` happens to correspond to an existing mapping
}

If you attempt to fill in the missing part of your example, I think you will find that you cannot actually demonstrate memory corruption resulting from void-initialization of a file descriptor without the use of @trusted code (e.g., to cast the void* returned from mmap to some other type of pointer whose target type has unsafe values).

Yes, the implementation of File would need @trusted code. How would that invalidate the example?

Your process can inherit fds from its parent. Or you may have pipes, shared memfds, sockets. Plenty of ways of obtaining a valid fd without requiring any casts OR having to deal with pointers. The example shows a way to (unintentionally) alias an existing fd (obtained through whichever means) and write to it, in @safe context.

An fd is an unsafe quantity encoded in a safe type. We need a way to express that in the language.

February 22, 2022

On Tuesday, 22 February 2022 at 15:55:16 UTC, Stanislav Blinov wrote:

>

Yes, the implementation of File would need @trusted code. How would that invalidate the example?

If completing the example required incorrect use of @trusted (i.e., on a function that does not have a safe interface), it would not be valid.

Using @trusted in the implementation of File.write to call POSIX write would not be a problem.

>

Your process can inherit fds from its parent. Or you may have pipes, shared memfds, sockets. Plenty of ways of obtaining a valid fd without requiring any casts OR having to deal with pointers. The example shows a way to (unintentionally) alias an existing fd (obtained through whichever means) and write to it, in @safe context.

The example shows a write to an fd, and then hand-waves about how this could maybe, hypothetically, somehow, cause memory corruption. Aliasing an fd does not, by itself, constitute memory corruption.

Remember, if you can cause memory corruption in @safe code, that means you can also cause undefined behavior in @safe code. So if you cannot write a program that uses this alleged loophole to cause UB, then what you have found is not actually memory corruption. (Although it may still be "data corruption".)

February 22, 2022

On Tuesday, 22 February 2022 at 16:16:30 UTC, Paul Backus wrote:

>

On Tuesday, 22 February 2022 at 15:55:16 UTC, Stanislav Blinov wrote:

>

Yes, the implementation of File would need @trusted code. How would that invalidate the example?

If completing the example required incorrect use of @trusted (i.e., on a function that does not have a safe interface), it would not be valid.

It doesn't. The program, as presented, is enough.

>

Using @trusted in the implementation of File.write to call POSIX write would not be a problem.

More the reason for me to not understand the objection then.

> >

Your process can inherit fds from its parent. Or you may have pipes, shared memfds, sockets. Plenty of ways of obtaining a valid fd without requiring any casts OR having to deal with pointers. The example shows a way to (unintentionally) alias an existing fd (obtained through whichever means) and write to it, in @safe context.

The example shows a write to an fd, and then hand-waves about how this could maybe, hypothetically, somehow, cause memory corruption.

I don't follow. It seems pretty clear to me how the example is expressed. Where's the handwaving? Compare to this:

void main() @safe {
    char[5]* ptr = void;
    *ptr = "hello";
}

The above won't compile, since void-initialization of pointers is not allowed in @safe code, with good reason. Is there anything to handwave here? The fd example, however, will compile in current language, despite doing the same thing.

>

Aliasing an fd does not, by itself, constitute memory corruption.

I did not say it did. Writing to an fd initialized with "implementation-defined" (read: garbage) value may - that's what I said, and that's what the example shows.

>

Remember, if you can cause memory corruption in @safe code, that means you can also cause undefined behavior in @safe code. So if you cannot write a program that uses this alleged loophole to cause UB, then what you have found is not actually memory corruption. (Although it may still be "data corruption".)

You can write such a program with fds. The example is one such program. Do you have any suggestions on how to make it clearer?

February 22, 2022

On Tuesday, 22 February 2022 at 17:29:46 UTC, Stanislav Blinov wrote:

>

On Tuesday, 22 February 2022 at 16:16:30 UTC, Paul Backus wrote:

>

Remember, if you can cause memory corruption in @safe code, that means you can also cause undefined behavior in @safe code. So if you cannot write a program that uses this alleged loophole to cause UB, then what you have found is not actually memory corruption. (Although it may still be "data corruption".)

You can write such a program with fds. The example is one such program. Do you have any suggestions on how to make it clearer?

The example, as written, does not link, because File.write is missing a function body. If I fill in the obvious implementation, I get the following program:

struct File
{
    void write(const(void)[] data) @safe
    {
        import core.sys.posix.unistd: write;
        () @trusted { write(fd, data.ptr, data.length); }();
    }
    private int fd;
}

void main() @safe
{
    File f = void;
    f.write("hello");
}

The above program does not have undefined behavior. The call to write will either fail with EBADF, or attempt to write the string "hello" to some unspecified open file.

If you believe there is some way to get the above program to produce undefined behavior, or to complete your original example in such a way that it produces undefined behavior without the use of incorrect @trusted code, I'm afraid you will have to spell it out for me.

February 23, 2022

On Tuesday, 22 February 2022 at 18:33:58 UTC, Paul Backus wrote:

>

On Tuesday, 22 February 2022 at 17:29:46 UTC, Stanislav Blinov wrote:

> >

You can write such a program with fds. The example is one such program. Do you have any suggestions on how to make it clearer?

The example, as written, does not link, because File.write is missing a function body.

If you're going to go there, then...

>

If I fill in the obvious implementation, I get the following program:

struct File
{
    void write(const(void)[] data) @safe
    {
        import core.sys.posix.unistd: write;
        () @trusted { write(fd, data.ptr, data.length); }();
    }
    private int fd;
}

...that @trusted code is incorrect, at least on some platforms (yes, I can nitpick too). Or we can simply agree that File.write is implemented correctly in terms of write (which is the important part) and leave it at that, as the rest is irrelevant to the example. I am seriously perplexed at this kind of nitpicking, not to mention the implied expectation of having to spell out full-blown libraries in an example code.

>

void main() @safe
{
File f = void;
f.write("hello");
}


The above program does not have undefined behavior. The call to `write` will either fail with `EBADF`, or attempt to write the string `"hello"` to some unspecified open file.

I am reasonably certain that the results may be much more varied than that, including some that aren't specified:

https://pubs.opengroup.org/onlinepubs/9699919799/functions/write.html

>

If you believe there is some way to get the above program to produce undefined behavior, or to complete your original example in such a way that it produces undefined behavior without the use of incorrect @trusted code, I'm afraid you will have to spell it out for me.

Not exhaustive:
It may corrupt a given GC's implementation's heap, which means what occurs after the } is anyone's guess.
It may mutate data that's supposed to be immutable (i.e. in a parent process, though you could argue that might not be relevant to the DIP).
It may block indefinitely, or crash, or complete with no effect.

If you could demonstrate that it cannot possibly exhibit at least the above, I'll happily accept being mistaken.

...but seriously. What is it with all the condescending tone on the forums lately?

February 23, 2022

On Tuesday, 22 February 2022 at 15:05:14 UTC, Nick Treleaven wrote:

>

On Sunday, 20 February 2022 at 15:16:30 UTC, Dukc wrote:

>

Why we want to do that at all? DIP1000. Most of the potential of DIP1000 is wasted if you cannot prevent destruction before end of the scope:

@safe void abuse()
{ auto cont = SomeRaiiContainer([1,2,3]);
  scope ptr = &cont.front;
  destroy(cont);
  int oops = *ptr;
}

Doesn't the same problem occur just with reassignment?:

  scope ptr = &cont.front;
  cont = cont.init;
  int oops = *ptr;

Reassignment can be forbidden at least. But still, my point does not stand scrutiny, because:

1: No reassignment is clumsy.
2: Even accepting that, this would still do the same thing:

  auto pCont = new SomeRaiiContainer([1,2,3]);
  scope ptr = &pCont.front();
  pCont = null;
  GC.collect;
  int oops = *ptr;

So, stratch that. RAII or reference counted containers can only be @safe with a callback based usage, as suggested by Paul: https://github.com/dlang/phobos/pull/8368#issuecomment-1024917439 . And that idiom is @safe even with present destructors. Hopefully: We have discovered so many in our DIP1000-based memory safety schemes lately so there is no guarantee that this isn't still just some oversight.

February 23, 2022

On Wednesday, 23 February 2022 at 00:14:13 UTC, Stanislav Blinov wrote:

>

If you're going to go there, then...

...

...that @trusted code is incorrect, at least on some platforms (yes, I can nitpick too).

Why? I don't see it.

>

...but seriously. What is it with all the condescending tone on the forums lately?

I think Paul makes a valid point and uses an appropriate tone. The DIP should not be hand-wavy about how scope checking would help memory safety when using file descriptors. I didn't go into much detail there because I didn't think it would be a contested addition.