Comment #0 by petar.p.kirov — 2017-08-19T21:51:08Z
At first I was about to name this issue "The compiler treats 'scope ref T' and 'scope ref T[1]' differently" (see `use0x3()` and `use0x4()`), but then I decided to dig a little more, so here's what I've found so far:
$ cat > scope_bug.d << DBUG
@safe:
struct Context0x0 { char[] str; }
struct Parent0x1 { Context0x0 c; }
struct Parent0x2 { Context0x0[1] csa; }
struct Parent0x3 { Context0x0[] csl; }
struct Parent0x4 { Context0x0* cp; }
struct Parent0x5 { Parent0x1 p1; }
struct Parent0x6 { Parent0x5 p5; }
struct Parent0x7 { Parent0x6 p6; }
struct Parent0x8 { Parent0x2[1]* p2; }
struct Parent0x9 { Parent0x8[1] p8; }
struct Parent0xA { Parent0x9[1] p9; }
struct Parent0xB { Parent0x4* p4; }
struct Parent0xC { Parent0xB* pb; }
struct Parent0xD { Parent0xC* pc; }
void main()
{
char[16] buf;
use0x0(buf);
char[] charSlice = buf;
use0x1(charSlice);
// use0x2(&charSlice); // NG - rejects valid
Context0x0[1] c = Context0x0(charSlice);
use0x3(c[0]);
use0x4(c);
use0x5(c);
auto p1 = Parent0x1(c[0]);
use0x6(p1);
auto p2 = Parent0x2(c);
use0x7(p2);
Context0x0[] contextSlice = c[];
auto p3 = Parent0x3(contextSlice);
use0x8(p3);
auto p4 = Parent0x4(&c[0]);
use0x9(p4);
auto p5 = Parent0x7(Parent0x6(Parent0x5(p1)));
use0xA(p5);
Parent0x2[1] p2sa = Parent0x2(c);
Parent0xA p6 = Parent0xA(Parent0x9(Parent0x8(&p2sa)));
use0xB(p6);
// auto pbAttemp1 = Parent0xB(&p4); // NG - rejects valid
Parent0x4[1] p4WorkAround = Parent0x4(&c[0]);
Parent0xB[1] pb = Parent0xB(&p4WorkAround[0]);
Parent0xC[1] pc = Parent0xC(&pb[0]);
Parent0xD[1] pd = Parent0xD(&pc[0]);
use0xC(pd[0]);
}
char[] global;
void use0x0(scope char[] arr)
{
// global = arr; // OK - this does not compile
}
void use0x1(scope ref char[] arr)
{
// global = arr; // OK - this does not compile
}
void use0x2(scope char[]* arr)
{
global = *arr; // NG - accepts invalid
}
void use0x3(scope ref Context0x0 c)
{
// global = c.str; // OK - this does not compile
}
void use0x4(scope ref Context0x0[1] c)
{
global = c[0].str; // NG - accepts invalid
}
void use0x5(scope Context0x0[] c)
{
global = c[0].str; // NG - accepts invalid
}
void use0x6(scope ref Parent0x1 p)
{
// global = p.c.str; // OK - this does not compile
}
void use0x7(scope ref Parent0x2 p)
{
global = p.csa[0].str; // NG - accepts invalid
}
void use0x8(scope ref Parent0x3 p)
{
global = p.csl[0].str; // NG - accepts invalid
}
void use0x9(scope ref Parent0x4 p)
{
global = p.cp.str; // NG - accepts invalid
}
void use0xA(scope ref Parent0x7 p)
{
// global = p.p6.p5.p1.c.str; // OK - this does not compile
}
void use0xB(scope ref Parent0xA p)
{
global = (*p.p9[0].p8[0].p2)[0].csa[0].str; // NG - accepts invalid
}
void use0xC(scope ref Parent0xD p)
{
global = p.pc.pb.p4.cp.str; // NG - accepts invalid
}
DBUG
$ dmd -dip1000 scope_bug.d
scope_bug.d(11): Error: cannot take address of scope local c in @safe function main
$ dmd --version
DMD64 D Compiler v2.076.0-b1-master-32bb4ed
Comment #1 by petar.p.kirov — 2017-08-19T21:52:12Z
Typo: the last lines were meant to be:
$ dmd -dip1000 scope_bug2.d
$ echo $?
0
$ dmd --version
DMD64 D Compiler v2.076.0-b1-master-32bb4ed
Comment #2 by bugzilla — 2017-08-27T02:33:25Z
Starting with this one:
> void use0x2(scope char[]* arr)
> {
> global = *arr; // NG - accepts invalid
> }
`scope` is not transitive. It applies to the 'head' only. `*arr` is no longer the head, and `scope` doesn't apply to it. A casual look at the rest of the cases shows similar.
If you would please filter out all the cases of more than one indirection, I can look at the rest.
Comment #3 by bugzilla — 2018-03-11T21:51:51Z
The following remain:
---
@safe:
struct Context0x0 { char[] str; }
struct Parent0x1 { Context0x0 c; }
struct Parent0x2 { Context0x0[1] csa; }
struct Parent0x3 { Context0x0[] csl; }
struct Parent0x4 { Context0x0* cp; }
struct Parent0x5 { Parent0x1 p1; }
struct Parent0x6 { Parent0x5 p5; }
struct Parent0x7 { Parent0x6 p6; }
struct Parent0x8 { Parent0x2[1]* p2; }
struct Parent0x9 { Parent0x8[1] p8; }
struct Parent0xA { Parent0x9[1] p9; }
struct Parent0xB { Parent0x4* p4; }
struct Parent0xC { Parent0xB* pb; }
struct Parent0xD { Parent0xC* pc; }
char[] global;
void use0x2(scope char[]* arr)
{
global = *arr; // NG - accepts invalid
}
void use0x9(scope ref Parent0x4 p)
{
global = p.cp.str; // NG - accepts invalid
}
void use0xB(scope ref Parent0xA p)
{
global = (*p.p9[0].p8[0].p2)[0].csa[0].str; // NG - accepts invalid
}
void use0xC(scope ref Parent0xD p)
{
global = p.pc.pb.p4.cp.str; // NG - accepts invalid
}
---
All of them are instances of transitive scope, but scope is not transitive. Not compiler bugs.
Comment #4 by ag0aep6g — 2019-06-09T13:04:18Z
(In reply to Walter Bright from comment #3)
> All of them are instances of transitive scope, but scope is not transitive.
> Not compiler bugs.
Reopening.
I don't know if this report shows a problem with `scope` in particular, but it does show holes in @safe. We can't have a global that points to stack memory in @safe code.
If @safe works as designed here, you need to revisit the design, because it's insufficient.
Here's a single example based on use0x5, modified to show more blatantly that safety is violated:
----
int** global;
immutable int imm;
static this() { imm = 42; }
void main() @safe
{
f(); /* `global` now points to the stack. Uh-oh. */
stomp(); /* `*global` now points to `imm`. */
**global = 13; /* Overwrites `imm`. */
assert(imm == 42);
/* Fails. We've (accidentally) mutated an immutable int. */
}
void f() @safe
{
int* buf;
static struct Context0x0 { int** str; }
Context0x0[1] c = Context0x0(&buf);
global = c[0].str; /* This should be rejected. */
}
void stomp() @safe
{
immutable int*[4] x = &imm;
}
----
Some of the other variations also seem to boil down to this. Maybe all of them do.
Comment #5 by bugzilla — 2022-07-28T06:21:56Z
A simpler version that should fail to compile:
int** global;
struct S { int** str; }
void f() @safe
{
int* buf;
S[1] c = S(&buf);
global = c[0].str; /* This should be rejected. */
}
Comment #6 by bugzilla — 2022-07-28T06:22:59Z
The `c` should be inferred as `scope`.
Comment #7 by bugzilla — 2022-07-28T06:58:01Z
Declaring c as:
S c = S(&buf);
does cause it to be inferred to be `scope`, and the next line is then correctly diagnosed.
So, the trouble is the scope inference should be looking at the elements of the static array, but is not.
Comment #8 by bugzilla — 2022-08-12T07:02:37Z
(In reply to ag0aep6g from comment #4)
> global = c[0].str; /* This should be rejected. */
And it is now in master:
Error: scope variable `c` assigned to non-scope `global`