March 11

On Monday, 11 March 2024 at 08:16:13 UTC, Alex wrote:

>

Hello,

I am interesting D as memory safe language (maybe SafeD?) and have written very simple code:

@safe

import std.stdio;

The @safe attribute there does nothing, it only applies to the import declaration, and is ignored. Perhaps you meant @safe: with the trailing colon, so it applies the attribute to every declaration after it in the module.

>

So I don't see any errors or warnings from compiler when I use uninitialized variable a

a is not uninitialized - you have to use = void for that (https://dlang.org/spec/declaration.html#void_init). Uninitialized pointers/references are not allowed in @safe functions.

>

and don't see any exception with backtrace in runtime (application is build in debug mode).

Try using optimization. On Linux, the backend can detect the null dereference at compile-time:

$ dmd -O os/nullobj.d
os/nullobj.d(22): Error: null dereference in function _Dmain

Line 22:
a.run();

However, only simple cases are detected at compile-time.

>

Is it expected behavior?
Looks like it is not very safe approach and can lead to very unpleasant memory errors...

@safe only means memory-safety:
https://dlang.org/spec/memory-safe-d.html

Null-safety is not part of memory-safety, because in D it should not be possible to violate memory-safety when a pointer/reference is null.

For a long time I've wanted compile-time null-safety using non-nullable pointers/references, but there are no plans to add that AFAIK.

March 11
On Monday, 11 March 2024 at 10:45:27 UTC, Richard (Rikki) Andrew Cattermole wrote:
> On 11/03/2024 11:39 PM, Alex wrote:
>> On Monday, 11 March 2024 at 10:31:05 UTC, Richard (Rikki) Andrew Cattermole wrote:
>>> On 11/03/2024 11:20 PM, Alex wrote:
>>>> Oh... looks like null is also used for refs in D. It's sad :)
>>>> I thought it used only for pointers in unsafe mode.
>>>> I think, the null safety feature is very important in modern world (maybe "must have" :) ). Very nice to have such feature in D like in Kotlin for example.
>>>> So, as I understand, D team have the task in TODO list about implementation something like "null safety"?
>>>
>>> I'm not sure I'd call myself part of the core D team (although I have another proposal that is currently going through the DIP process that would certainly qualify me for the title!).
>>>
>>> However in saying that, memory safety is on the foundation's radar as needing solving.
>>>
>>> I'm just the weirdo that is having a go at trying to solve temporal memory safety (an unsolved problem!).
>> 
>> Thank you for the information!
>> Maybe you know: are there some guys from D foundation here?
>
> Yes, they are around including Walter, I'm sure he'll see it within the day.
>
>> Also, I figured out that I can't handle uninitialized access via try/catch:
>> 
>> ```d
>> A a;
>> try {
>>      a.run();
>> } catch(Throwable) {
>>      writeln("Error");
>> }
>> ```
>> 
>> So the catch branch not work here.
>
> The a variable was initialized, via default initialization.
>
> It is in a known state, null.
>
> What you are wanting is a way to have the compiler complain when a nonnull type state is expected, but it is initialized.
>
> D does not support that currently.

Yes, I got it about compiler, static analyzer can't detect such potential issue for now.
The instance of class `A` is initialized by default initializer - correct?.
But what about variable `a`?
Is it initialized by null or contains reference to the instance initialized by default initializer?
What happend when I tried to call method `run()` of `a` in runtime?
I see that application was abnormal termination because `writeln("Hello, world!");` was not called.
But I don't see any information in console about it (backtrace or something else).
Is it uncatched excpetion? But I have tried to catch it - not work.
March 12
On 12/03/2024 12:01 AM, Alex wrote:
> Yes, I got it about compiler, static analyzer can't detect such potential issue for now.
> The instance of class `A` is initialized by default initializer - correct?.
> But what about variable `a`?

The instance of class ``A`` only exists as a reference stored in ``a``.

``a`` is null, therefore there is no instance of ``A``.

> Is it initialized by null or contains reference to the instance initialized by default initializer?

The variable is a pointer, and it is null aka 0.

```d
void main() {
    Object o;
}
```

is

```asm
_Dmain:
		push	RBP
		mov	RBP,RSP
		sub	RSP,010h
		mov	qword ptr -8[RBP],0
		xor	EAX,EAX
		leave
		ret
```

No different if the type was ``int*`` instead of ``Object``.

```d
void main() {
    int* o;
}
```

```asm
_Dmain:
		push	RBP
		mov	RBP,RSP
		sub	RSP,010h
		mov	qword ptr -8[RBP],0
		xor	EAX,EAX
		leave
		ret
```

> What happend when I tried to call method `run()` of `a` in runtime?
> I see that application was abnormal termination because `writeln("Hello, world!");` was not called.
> But I don't see any information in console about it (backtrace or something else).
> Is it uncatched excpetion? But I have tried to catch it - not work.

A class reference is a pointer.

It basically works out to be something like this:

```d
struct Obj {}

void func(Obj* this) {}

Obj* obj;
func(obj);
```

If the function reads or writes to the this pointer, its going to segfault.

Same principle with the vtable lookup.

If you make your method on the class final, it should work the exact same way as the struct above, due to it not using the vtable.

March 11

On Monday, 11 March 2024 at 10:48:52 UTC, Nick Treleaven wrote:

>

The @safe attribute there does nothing, it only applies to the import declaration, and is ignored. Perhaps you meant @safe: with the trailing colon, so it applies the attribute to every declaration after it in the module.
Yes, I mean whole file declared as safe. Thank you!
I not so familiar with D yet :)

>

a is not uninitialized - you have to use = void for that (https://dlang.org/spec/declaration.html#void_init). Uninitialized pointers/references are not allowed in @safe functions.
Ok, got it, in my example variable is initialized by default value (null).

>

Try using optimization. On Linux, the backend can detect the null dereference at compile-time:

$ dmd -O os/nullobj.d
os/nullobj.d(22): Error: null dereference in function _Dmain

Thanks, it works on Windows to :)
Is it possible pass the compilation flag -O via dub run?

>

Line 22:
a.run();

However, only simple cases are detected at compile-time.

You right, after this trivial modification compiler fails to detect bug :(

    void doRun(A a) {
    	a.run();
    }

    int main()
    {
    	A a;
    	//a.run();
    	doRun(a);
    	writeln("Hello, world!");
    	return 0;
    }

>

@safe only means memory-safety:
https://dlang.org/spec/memory-safe-d.html

Null-safety is not part of memory-safety, because in D it should not be possible to violate memory-safety when a pointer/reference is null.

Formally yes, but segfault looks not good for language which say "I have memory safe feature", in my opinion. For example, Rust guarantees what successfully compiled code couldn't lead to crash/segfault.

>

For a long time I've wanted compile-time null-safety using non-nullable pointers/references, but there are no plans to add that AFAIK.

Very good idea! Hope you can implement it sooner or later :)

March 11
On Monday, 11 March 2024 at 11:09:40 UTC, Richard (Rikki) Andrew Cattermole wrote:
> If the function reads or writes to the this pointer, its going to segfault.
>
> Same principle with the vtable lookup.
>
> If you make your method on the class final, it should work the exact same way as the struct above, due to it not using the vtable.

Thank you very much for detailed explanation! Now I understand what is going on.
March 11

On Monday, 11 March 2024 at 13:16:04 UTC, Alex wrote:

>

Thanks, it works on Windows to :)
Is it possible pass the compilation flag -O via dub run?

Of course: dub run --build=release :). The -O flag instructs DMD to optimize generated code, and null dereference detection is just a by-product of data flow analysis that DMD performs for some optimizations.

March 11

On Monday, 11 March 2024 at 08:48:47 UTC, Richard (Rikki) Andrew Cattermole wrote:

>

On 11/03/2024 9:16 PM, Alex wrote:

>

So I don't see any errors or warnings from compiler when I use uninitialized variable |a| and don't see any exception with backtrace in runtime (application is build in debug mode).

Is it expected behavior? Looks like it is not very safe approach and can lead to very unpleasant memory errors...

This is expected behavior.

The variable a was default initialized to null.

D has not got type state analysis as part of it, so it cannot detect this situation and cause an error.

It is at the top of my todo list for memory safety research for D, as the IR it requires enables other analysis and provides a framework for it to exist in.

Rather than doing that, couldn't the compiler say A a; is not valid inside @safe?

March 12
On 12/03/2024 4:31 AM, bachmeier wrote:
> On Monday, 11 March 2024 at 08:48:47 UTC, Richard (Rikki) Andrew Cattermole wrote:
>> On 11/03/2024 9:16 PM, Alex wrote:
>>> So I don't see any errors or warnings from compiler when I use uninitialized variable |a| and don't see any exception with backtrace in runtime (application is build in debug mode).
>>>
>>> Is it expected behavior? Looks like it is not very safe approach and can lead to very unpleasant memory errors...
>>
>> This is expected behavior.
>>
>> The variable a was default initialized to null.
>>
>> D has not got type state analysis as part of it, so it cannot detect this situation and cause an error.
>>
>> It is at the top of my todo list for memory safety research for D, as the IR it requires enables other analysis and provides a framework for it to exist in.
> 
> Rather than doing that, couldn't the compiler say `A a;` is not valid inside `@safe`?

One of the improvements for type state analysis I want to make is for methods:

```d
class Foo {
	void func(this'nonnull);
}
```

Instead of:

```d
class Foo {
	void func(this'reachable);
}
```

That'll catch it when you try to call something.

However I'm not sure if disallowing null entering is a great idea, its going to enter through other methods so you might as well embrace catching that as well.
March 11

On Monday, 11 March 2024 at 08:16:13 UTC, Alex wrote:

>

Is it expected behavior?
Looks like it is not very safe approach and can lead to very unpleasant memory errors...

So I know there are a lot of responses here, with a lot of discussion. But I don't think anyone has told you why D works this way.

The explanation is that D is expecting the memory hardware to fault when you dereference null. We know that this is not the case for all situations, but it is the case for all of D's normal usage modes (e.g. as user-code on standard operating systems).

Since the memory hardware already supports this, and is essentially free, D has deferred to that mechanism to guard against dereferencing null pointers. Not assuming this behavior means all dereferences of pointers/classes in @safe code would have to be instrumented with a check, slowing down the code significantly.

I consider null pointer faults to be annoying, but not nearly as bad as dangling pointer accesses. At least a null pointer always crashes when you access it.

-Steve

March 11

On Monday, 11 March 2024 at 19:43:33 UTC, Steven Schveighoffer wrote:
Hello, Steven!

Thank you for clarifications!

>

The explanation is that D is expecting the memory hardware to fault when you dereference null.

Ok, got it, but on Windows I observe stange behaviour in runtime: application terminated without system error and any logs in console. In this case I think developer can't localize and figured out what is going wrong in code.

>

Not assuming this behavior means all dereferences of pointers/classes in @safe code would have to be instrumented with a check, slowing down the code significantly.

I agree that checks on each pointer dereferences maybe expensive in runtime. But from my point of view solution is disable any null refernces in code. The compiler can do it during compilation and validation in runtime won't be needed.