This code compiles, but should not:
-----
import std.stdio;
class C {
int x;
this(int _x) { x = _x; }
ref C evil() {
return this; // <-- should not compile but does
}
}
void hmm(int x, int y, ref C c) {
c = null; // corrupt memory
writefln("%d %d", x, y); // prints "0 2"
}
void main() {
auto c = new C(1);
auto d = new C(2);
hmm(1, 2, c.evil()); // N.B., we passed 1 and 2 to hmm()
}
-----
Explanation: C.evil() returns a dangling pointer to an out-of-scope local variable (i.e., 'this'), which is passed into hmm() which overwrites that memory location. On my system (Debian/Linux amd64) it just so happens that this memory location coincides with the address of the parameter 'x', thus causing x to get overwritten.
Cause of bug: it should be illegal to return 'this' in a ref function, because it's a local variable (albeit implicit).
Comment #1 by hsteoh — 2014-07-13T04:54:37Z
Better yet, the following variation shows @safe breakage:
----
import std.stdio;
class C {
int x;
this(int _x) { x = _x; }
ref C evil() @safe {
return this; // <-- should not compile but does
}
}
void hmm(int x, int y, ref C c) {
() @safe {
c = null; // corrupt memory -- in @safe block
}();
writefln("%d %d", x, y); // prints "0 2"
}
void main() {
auto c = new C(1);
auto d = new C(2);
hmm(1, 2, c.evil()); // N.B., we passed 1 and 2 to hmm()
}
----
Comment #2 by hsteoh — 2014-07-13T04:58:20Z
Attempting to do the same with returning a local variable produces a compile error:
test.d(10): Error: escaping reference to local variable c
which is correct. So looks like 'this' was overlooked as a local variable (albeit an implicit one) in the check for escaping references.
Comment #3 by hsteoh — 2014-07-14T14:41:47Z
Note that it's OK to do this with structs because the address of the original struct is returned, not the address of the `this` implicit variable.
As Kenji points out, this is part of a more general problem where `super` and `this` in class methods are lvalues, so they are liable to illegal rebindings:
----
class Base {
int x;
}
void rebind(ref Base b) { b = new Base; }
void rebind(ref Derived d) { d = new Derived; }
class Derived : Base {
int y;
void evil() {
rebind(super);
x = 123; // this modifies a different copy of Base.x than this one!
rebind(this);
y = 123; // this modifies a different copy of this.y !
}
}
----
This problem can be solved if we make `this` and `super` rvalues in class methods. Note that in struct methods, `this` is OK to be an lvalue, because structs are by-value types so no rebinding is involved in the struct analog of the above code; it modifies the struct in-place.
Comment #6 by github-bugzilla — 2014-08-05T14:59:13Z