Lifted from the forum: https://forum.dlang.org/post/[email protected]
There is an assumption that you can use visibility attributes like `private` to protect your stuff from outside meddling, and that you can rely on this to ensure safety (à la @safe).
For example, std.stdio.File assumes this. It `malloc`s its internal data and keeps a reference count. It does an `@trusted` call to `free` when the count hits 0. The data is stored in a `private` field, and `File` assumes that it can't be messed with from the outside, at least not in an `@safe` manner.
As far as I'm aware, that's exactly how `@safe` reference counting is supposed to be done.
But `tupleof` ignores `private` even in `@safe` code. So it can be used to violate the assumption, which leads to memory corruption.
The `File` example in code (<https://run.dlang.io/is/1QSsUk>):
----
void main() @safe
{
import std.stdio: File, writeln;
auto hosts = File("/etc/hosts");
{
auto hosts_copy = hosts;
hosts_copy.tupleof[0].refs = 1; /* uh-oh */
}
auto self = File(__FILE__);
writeln(hosts.rawRead(new char[1000]));
/* Reads from __FILE__ instead of /etc/hosts. */
}
----
And the issue reduced to its core (<https://run.dlang.io/is/reMMQt>):
----
--- foo.d
struct S
{
private int* p;
this(int x, int y) @safe { p = &[x, y][0]; }
int get2nd() @trusted { return p is null ? 0 : p[1]; }
/* Assuming that p is only ever set by the constructor.
So it's either null or there must be two elements. */
}
--- bar.d
import foo;
import std.stdio;
void main() @safe
{
auto s = S(1, 2);
s.tupleof[0] = &[10][0]; /* Should not be allowed. */
writeln(s.get2nd()); /* garbage */
}
----