March 12
On Tuesday, 12 March 2024 at 17:47:32 UTC, Walter Bright wrote:
> Yeah, I know about that article. It's very popular. I've written a sort of rebuttal:
>
> https://www.digitalmars.com/articles/C-biggest-mistake.html

If you ask me about root of evil in C I will answer it is pointers with their arithmetic.
If you ask me about root of evil in Java I will answer it is null "pointer" (reference actually) :)

> With Java, it is also not a memory safety issue when there's a null exception.

I think it depends on what we means "memory safety". If only "dangling pointers", I agree. But if we want to have strict guarantee at compilation time that any accessible reference leads to alive object answer is no, SafeD can't provide such guarantee yet.

> It is always better to catch null mistakes at compile time rather than runtime, but it isn't a memory safety issue.

Are D foundation considering the possibility to implement something like this in future version of D?

March 12
On Tue, Mar 12, 2024 at 07:33:17PM +0000, Alex via Digitalmars-d wrote: [...]
> I think it depends on what we means "memory safety".

In D, memory safety means memory cannot be corrupted. That is, writing to a pointer intended for one variable will not overwrite values of another, unrelated variable.  This includes things like buffer overflows, stack corruption, overwriting pointers with maliciously crafted values that causes data to be written to places that isn't supposed to be written to, etc..

Dereferencing a null pointer is not a memory corruption according to this definition. (Even though in practice, having an application abort because of a null pointer can also become an issue, e.g., if a malicious outsider is able to trigger that condition consistently, it could be exploited in a DoS attack.)


T

-- 
Life is complex. It consists of real and imaginary parts. -- YHL
March 12

On Monday, 11 March 2024 at 10:35:55 UTC, Sergey wrote:

>

On Monday, 11 March 2024 at 10:21:43 UTC, Alex wrote:

>

No. It was test how D handle uninitialized variables.

Oh right. Didn't get from the first read.

Based on the info from here https://en.wikipedia.org/wiki/Void_safety
Only TypeScript, C#/F#, Kotlin, Rust, Swift and Dart have this feature.

And Objective-C, in which calling methods on (er, sending messages to) null references is not only allowed, it's just good manners. Coming to D from that was a bit of a shock with the sudden segfaults, though eventually we adapt. Still, it would be nice to have the option of Errors thrown rather than a straight crash. I assume this is for efficiency reasons, but couldn't a compiler flag be a consideration? Debuggers are nice but it's still an extra, external step.

March 12
On 3/12/2024 12:48 PM, H. S. Teoh wrote:
> (Even though in practice, having an application abort
> because of a null pointer can also become an issue, e.g., if a malicious
> outsider is able to trigger that condition consistently, it could be
> exploited in a DoS attack.)

The same goes for assert() failures, buffer overflow exceptions, and other runtime checks D inserts into the code.

March 12
On 3/12/2024 12:33 PM, Alex wrote:
> I think it depends on what we means "memory safety". If only "dangling pointers", I agree.

Memory safety is not something we've uniquely defined. It's generally accepted that it specifically means no memory corruption. "Safe" programs can offer additional guarantees, like no race conditions.


> But if we want to have strict guarantee at compilation time that any accessible reference leads to alive object answer is no, SafeD can't provide such guarantee yet.

In general, we cannot guarantee that assert()'s won't trip at runtime, nor buffer overflow exceptions. All we can do is say we stop the program when it happens.

Actual memory corruption is infinitely worse than promptly stopping the program when it detects an internal bug.


>> It is always better to catch null mistakes at compile time rather than runtime, but it isn't a memory safety issue.
> 
> Are D foundation considering the possibility to implement something like this in future version of D?

Consider the following:
```
class A { void bar(); }

void foo(int i) {
    A a;
    if (i) a = new A();
    ...
    if (i) a.bar();
}
```
What happens if we apply data flow analysis to determine the state of `a` when it calls `bar()`? It will determine that `a` has the possible values (`null`, new A()`). Hence, it will give an error that `a` is possibly null at that point.

Yet the code is correct, not buggy.

Yes, the compiler could figure out that `i` is the same, but the conditions can be more complex such that the compiler cannot figure it out (the halting problem).

So that doesn't work.

We could lower `a.bar()` to `NullCheck(a).bar()` which throws an exception if `a` is null. But what have we gained there? Nothing. The program still aborts with an exception, just like if the hardware checked. Except we've got this manual check that costs extra code and CPU time.

BTW, doing data flow analysis is very expensive in terms of compiler run time. The optimizer does it, but running the optimizer is optional for that reason.

March 12
On 3/12/2024 4:42 PM, cc wrote:
> And Objective-C, in which calling methods on (er, sending messages to) null references is not only allowed, it's just good manners.  Coming to D from that was a bit of a shock with the sudden segfaults, though eventually we adapt.  Still, it would be nice to have the option of Errors thrown rather than a straight crash.  I assume this is for efficiency reasons, but couldn't a compiler flag be a consideration?  Debuggers are nice but it's still an extra, external step.

One some platforms, seg faults are intercepted by the D runtime and a stack trace is emitted.

March 12
On 3/12/2024 12:00 PM, Steven Schveighoffer wrote:
>>> FWIW, I was actually talking about environments where the null page does not segfault, like in a kernel.
>>
>> I wonder why anyone would design it that way.
> 
> e.g.: https://en.wikipedia.org/wiki/Zero_page#Interrupt_vectors

As the article says, only the x86 in real mode does that.

March 13
On Tuesday, 12 March 2024 at 19:48:22 UTC, H. S. Teoh wrote:
> Even though in practice, having an application abort because of a null pointer can also become an issue, e.g., if a malicious outsider is able to trigger that condition consistently, it could be exploited in a DoS attack.

Division by zero also crashes the program but nobody makes a big deal out of it.
March 13

On Wednesday, 13 March 2024 at 06:05:35 UTC, Walter Bright wrote:

>

Memory safety is not something we've uniquely defined. It's generally accepted that it specifically means no memory corruption. "Safe" programs can offer additional guarantees, like no race conditions.

Yeah, race condition is the second headache after memory safety (include null pointer issues) :)
I know only one language which give guarantee at compilation time about absence of race condition (but not deadlock). It is Rust.
But D have shared keyword and as I understand it can provide such guarantee at compilation time for SafeD, right?

>

In general, we cannot guarantee that assert()'s won't trip at runtime, nor buffer overflow exceptions. All we can do is say we stop the program when it happens.
As I understand there is only one case when D stop program - it is null pointer, correct?
So if D will support null safety it will be very reliable solution :)

>

Consider the following:

I mean nullable types like in Kotlin (originally in Ceylon) and null safety model around it: https://kotlinlang.org/docs/null-safety.html

A? a; // <type name>? is nullable. The variable 'a' is implicity initialized by zero. Compiler allow this.

A b; // compilation error, because type 'A' (without question mark) is not nullable. So you should initialize it explicity.

A c = new A; // ok

a.run(); // compilation error, because variable 'a' can be null (type is nullable 'A?')

if (a != null) {
    a.run(); // ok, because type of variable 'a' is A (not nullable) in this branch.
}

c.run() // ok, because type of variable 'c' is A (not nullable)

In your example (see comment after line void foo(int i) {):

class A { void bar(); }

void foo(int i) {
    A a; // compilation error, type A is not nullable
    if (i) a = new A();
    ...
    if (i) a.bar();
}
>

What happens if we apply data flow analysis to determine the state of a when it calls bar()? It will determine that a has the possible values (null, new A()). Hence, it will give an error that a` is possibly null at that point.

Yet the code is correct, not buggy.
Yes, the compiler could figure out that i is the same, but the conditions can be more complex such that the compiler cannot figure it out (the halting problem).

So that doesn't work.

BTW, doing data flow analysis is very expensive in terms of compiler run time. The optimizer does it, but running the optimizer is optional for that reason.

The general strategy for compiler to allow or deny dereference is based on variable type. If variable is nullable type dereference is denied. If variable is not nullable type dereference is allowed. Variable type can be implicity changed by explicity check it value with null and will be not nullable type inside branch. Certainly variable should be effectively immutable in it life cycle scope.
Such strategy at comilation time is not very expensive and shouldn't depends on compiler optimizations.
Anyway I think developer can accept some delay of project build in exchange for reliable software :)

>

We could lower a.bar() to NullCheck(a).bar() which throws an exception if a is null. But what have we gained there? Nothing. The program still aborts with an exception, just like if the hardware checked. Except we've got this manual check that costs extra code and CPU time.

I agree that NullCheck(a).bar() is not solution, because just moves issue in runtime.

March 13
On Wednesday, 13 March 2024 at 06:07:32 UTC, Walter Bright wrote:
> One some platforms, seg faults are intercepted by the D runtime and a stack trace is emitted.

Would be nice to have platform independent behavior which fixed in language specification.