D issues are now tracked on GitHub. This Bugzilla instance remains as a read-only archive.
Issue 18598 - cyclic constructor calls have undefined behavior but are accepted in @safe code
Summary: cyclic constructor calls have undefined behavior but are accepted in @safe code
Status: NEW
Alias: None
Product: D
Classification: Unclassified
Component: dmd (show other issues)
Version: D2
Hardware: All All
: P3 normal
Assignee: No Owner
URL:
Keywords: safe
Depends on:
Blocks:
 
Reported: 2018-03-12 10:29 UTC by ag0aep6g
Modified: 2024-12-13 18:57 UTC (History)
3 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this issue.
Description ag0aep6g 2018-03-12 10:29:36 UTC
On class constructors, the spec says [1]:

> It is illegal for constructors to mutually call each other, although
> the compiler is not required to detect it. It will result in undefined
> behavior.

But DMD accepts this:

----
class C
{
    this() @safe { this(1); }
    this(int i) @safe { this(); }
}

void main() @safe
{
    auto c = new C;
}
----

According to the spec, the code has undefined behavior, so it shouldn't be accepted with the @safe attribute.

Also according to the spec, "the compiler is not required to detect" this, but that can't apply to @safe code, because the compiler is required to ensure that there is "no possibility of undefined behavior" in @safe code [2].

(As always, this can be fixed by letting DMD reject the code, or by changing the spec to give the code defined behavior.)


[1] https://dlang.org/spec/class.html#constructors
[2] https://dlang.org/spec/function.html#function-safety
Comment 1 Walter Bright 2018-03-20 07:03:43 UTC
I don't know a way to assign defined behavior to overflowing the stack :-(

This can, however, be statically detected by the compiler by keeping track of the calls between constructors. (They can't be virtual, so that'll work as long as all the constructor bodies are visible to the compiler.)
Comment 2 ag0aep6g 2018-03-20 12:25:27 UTC
(In reply to Walter Bright from comment #1)
> I don't know a way to assign defined behavior to overflowing the stack :-(

Is that a problem for normal (non-constructor) functions as well?

----
void f() @safe { g(); }
void g() @safe { f(); }
void main() @safe { f(); } /* Undefined behavior? */
----
Comment 3 Walter Bright 2018-03-21 07:11:09 UTC
(In reply to ag0aep6g from comment #2)
> Is that a problem for normal (non-constructor) functions as well?

Yes.

I'm open to advice on what to do about it.
Comment 4 ag0aep6g 2018-03-25 14:16:27 UTC
(In reply to Walter Bright from comment #3)
> I'm open to advice on what to do about it.

I'm by no means an expert here, but don't we have a guaranteed guard page beyond the stack? If we have, stack overflow is guaranteed to fail with a segfault, as long as the access doesn't jump over guard page (cf. issue 17566).

The situation is very similar to null dereferences then. A null dereference also hits a guard page, as long as the offset from null isn't too large (cf. issue 5176).

In both cases, the compiler has to detect offsets that are so large that they would jump over the guard page, and then it has to inject code that makes an earlier access to trigger the segfault. If I got the term right, for the stack that's called "stack probing".
Comment 5 RazvanN 2023-05-09 15:01:37 UTC
Well, infinite recursion also leads to a segfault due to stack overflow however it is still accepted in @safe code [1]. @safe typically refers to undefined behavior caused by memory corruption. I don't see any memory corruption happening here, since the OS guards against that, rather a programming mistake that is beyond of what the @safe checking mechanism can discover.

[1]  void fun() @safe { fun();
Comment 6 dlangBugzillaToGithub 2024-12-13 18:57:49 UTC
THIS ISSUE HAS BEEN MOVED TO GITHUB

https://github.com/dlang/dmd/issues/19408

DO NOT COMMENT HERE ANYMORE, NOBODY WILL SEE IT, THIS ISSUE HAS BEEN MOVED TO GITHUB