Bug 17284 – Template function attribute inference wrongly infers @safe for accessing overlapping pointer fields in unions
Status
RESOLVED
Resolution
FIXED
Severity
major
Priority
P1
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2017-03-29T17:47:40Z
Last change time
2018-03-12T10:50:40Z
Keywords
safe
Assigned to
No Owner
Creator
hsteoh
Comments
Comment #0 by hsteoh — 2017-03-29T17:47:40Z
Code:
------
class C { }
union U {
C c;
int i;
}
void main() @safe {
U u1;
u1.c = new C; // compile error (correct, this is unsafe)
u1.i++; // (because you can do this)
import std.experimental.typecons : Final;
Final!U u2;
u2.c = new C; // compiles (!!!)
u2.i++; // uh-oh
}
------
Expected behaviour: Final!U should not allow user code to bypass compiler's @safety checks on assigning pointers to unions.
Or, at the minimum, Final should not be usable with unions. (It is questionable, in fact, whether modifying members of a Final!U should even be allowed in the first place.)
Comment #1 by petar.p.kirov — 2017-03-29T19:41:30Z
The issue has little to do with std.experimental.typecons.Final. Final just uses std.typecons.Proxy. I added an assert(0) on line 5585 in std.typecons from dmd 2.073.2 and got this:
$ ./main
core.exception.AssertError@/home/zlx/dlang/dmd-2.073.2/linux/bin64/../../src/phobos/std/typecons.d(5585): Assertion failure
----------------
??:? _d_assert [0x4296e4]
??:? pure nothrow ref @property @nogc return @safe int std.experimental.typecons.Final!(main.U).Final.__mixin8.opDispatch!("i").opDispatch!(std.experimental.typecons.Final!(main.U).Final).opDispatch() [0x428684]
??:? _Dmain [0x42845b]
??:? _D2rt6dmain211_d_run_mainUiPPaPUAAaZiZ6runAllMFZ9__lambda1MFZv [0x429c9f]
??:? void rt.dmain2._d_run_main(int, char**, extern (C) int function(char[][])*).tryExec(scope void delegate()) [0x429bc7]
??:? void rt.dmain2._d_run_main(int, char**, extern (C) int function(char[][])*).runAll() [0x429c44]
??:? void rt.dmain2._d_run_main(int, char**, extern (C) int function(char[][])*).tryExec(scope void delegate()) [0x429bc7]
??:? _d_run_main [0x429b33]
??:? main [0x42871f]
??:? __libc_start_main [0x4b801f44]
(dmd-2.073.2)/mnt/d/tmp_code/final_safe
This issue can easily be reproduced with the following code:
class C { }
union U {
C c;
int i;
}
struct S(T)
{
private T value__;
@property auto ref opDispatch(string name)()
{
return mixin("value__." ~ name);
}
}
void main() @safe {
S!U u;
u.c = new C; // compiles (!!!)
u.i++; // uh-oh
}
Therefore I'm changing the component to dmd.
Comment #2 by petar.p.kirov — 2017-03-29T19:53:49Z
I reduced it a bit further:
Case 1:
```
class C { }
union U {
C c;
int i;
}
ref C getC1(ref U u) { return u.c; }
ref C getC2(T)(ref T u) { return u.c; }
void main() @safe {
U u;
u.getC2() = new C; // compiles (!!!)
}
```
Case 2:
```
class C { }
union U {
C c;
int i;
}
ref C getC1(ref U u) { return u.c; }
ref C getC2(T)(ref T u) { return u.c; }
void main() @safe {
U u;
u.getC1() = new C; // (Line 12) Doesn't compile
}
```
main.d(12): Error: @safe function 'D main' cannot call @system function 'main.getC1'
Case 3:
```
class C { }
union U {
C c;
int i;
}
@safe:
ref C getC1(ref U u) { return u.c; } // (Line 9)
ref C getC2(T)(ref T u) { return u.c; }
void main() @safe {
U u;
u.getC1() = new C;
}
```
main.d(9): Error: field U.c cannot access pointers in @safe code that overlap other fields
Case 4:
```
class C { }
union U {
C c;
int i;
}
@safe:
ref C getC1(ref U u) { return u.c; } // (Line 9)
ref C getC2(T)(ref T u) { return u.c; } // (Line 10)
void main() @safe {
U u;
u.getC2() = new C; // (Line 14)
}
```
main.d(9): Error: field U.c cannot access pointers in @safe code that overlap other fields main.d(10): Error: field U.c cannot access pointers in @safe code that overlap other fields main.d(14): Error: template instance main.getC2!(U) error instantiating
Comment #3 by petar.p.kirov — 2017-03-29T19:57:19Z
So it seems that the bug has to do with the @safe-ty inference on template functions, since the code does not compile if the function template is explicitly marked with @safe.
Comment #4 by hsteoh — 2017-03-29T20:04:51Z
Interesting. So it would appear that the problem is caused by attribute inference wrongly inferring @safe for a template function that returns an overlapping pointer field.
I.e.:
----
class C { }
union U { C c; int i; }
ref C func(T)(ref T t) { return t.c; }
pragma(msg, typeof(func!U));
----
Compiler output:
----
pure nothrow @nogc ref @safe C(return ref U t)
----
The correct inferred type should be:
----
pure nothrow @nogc ref @system C(return ref U t)
----
Comment #5 by hsteoh — 2017-03-29T20:17:06Z
Oh my, it gets worse: ref has nothing to do with it at all! Look at this blatant violation of @safe:
------
class C { }
union U { C c; int i; }
void func(T)(T t)
{
t.c = new C;
t.i++; // !!!
}
pragma(msg, typeof(func!U));
------
Compiler output:
------
pure nothrow @safe void(U t)
------
Looks like attribute inference is a free license to violate @safety. :-D Or, more precisely, attribute inference completely forgets to check for overlapping pointers.
Comment #6 by petar.p.kirov — 2017-03-29T21:20:43Z
> Looks like attribute inference is a free license to violate @safety. :-D
Yeah with templates one enters 'god mode' in :D
Though the situation isn't very clear-cut. Take this code for example:
```
struct S { int* ptr; }
void func1()() { S s; s.ptr++; }
pragma (msg, "typeof(&func1!()): ", typeof(&func1!()));
class C { }
union U { C c; int i; }
void func2()() { U u; u.c = null; }
pragma (msg, "typeof(&func2!()): ", typeof(&func2!()));
int[] func3()() { int[5] sa; return sa; }
pragma (msg, "typeof(&func3!()): ", typeof(&func3!()));
int[] func4a(int[] a) @safe { return a; }
int[] func4b()() { int[5] sa; return func4a(sa); }
pragma (msg, "typeof(&func4b!()): ", typeof(&func4b!()));
void main() {}
```
$ dmd safe_infer_test1.d
typeof(&func1!()): void function() pure nothrow @nogc @system
typeof(&func2!()): void function() pure nothrow @nogc @safe
typeof(&func3!()): safe_infer_test1.d(10): Error: escaping reference to local variable sa
int[] function() pure nothrow @nogc @safe
typeof(&func4b!()): int[] function() @safe
So, depending on the code in question, sometimes inferences works correctly, and sometimes not.
Also note that the attribute inference of func4b!() changes depending on if you compile with -dip1000 or not:
$ dmd -dip1000 safe_infer_test1.d
typeof(&func1!()): void function() pure nothrow @nogc @system
typeof(&func2!()): void function() pure nothrow @nogc @safe
typeof(&func3!()): safe_infer_test1.d(10): Error: escaping reference to local variable sa
int[] function() pure nothrow @nogc @safe
typeof(&func4b!()): int[] function() @system