@trusted assumptions about @safe code
ag0aep6g
anonymous at example.com
Mon May 25 23:04:49 UTC 2020
Deep in the discussion thread for DIP 1028 there is this little remark
by Zoadian [1]:
> you can break previously verified @trusted code by just writing @safe code today.
That statement fits something that occurred to me when trying to lock
down the definition of "safe interfaces" [2].
Consider this little program that prints the address and first character
of a string in a convoluted way:
import std.stdio;
char f(string s) @trusted
{
immutable(char)* c = s.ptr;
writeln(g(* cast(size_t*) &c));
return *c;
}
size_t g(ref size_t s) @safe
{
return s;
}
void main() @safe
{
writeln(f("foo"));
}
As the spec stands, I believe it allows f to be @trusted. The function
doesn't exhibit undefined behavior, and it doesn't leak any unsafe
values or unsafe aliasing. So it has a safe interface and can be @trusted.
With f correctly @trusted and everything else being @safe, that code is
good to go safety-wise.
Now imagine that some time passes. Code gets added and shuffled around.
Maybe g ends up in another module, far away from f. And as it happens,
someone adds a line to g:
size_t g(ref size_t s) @safe
{
s = 0xDEADBEEF; /* ! */
return s;
}
g is still perfectly @safe, and there's not even any @trusted code in
the vicinity to consider. So review comes to the conclusion that the
change is fine safety-wise. But g violates an assumption in f. And with
that broken assumption, memory safety comes crumbling down, "by just
writing @safe code".
I think that's a problem. Ideally, it should not be possible to cause
memory corruption by adding a line to @safe code. But I know that I'm
more nitpicky than many when it comes to that rule and @safe/@trusted in
general.
Anyway, one way to address this would be disallowing f's call to g.
I.e., add a sentence like this to the spec:
Undefined behavior: Calling a safe function or a trusted
function with unsafe values or unsafe aliasing has undefined
behavior.
The aliasing of `immutable(char)*` and `size_t` is unsafe, so the call
becomes invalid. That means f can no longer be @trusted as it it's now
considered to have undefined behavior.
The downside is that functions may become invalid even when they don't
make bad assumptions. For example, this f2 would (arguably?) also be
invalid, because the unsafe aliasing is still there even though it's not
being used:
char f2(string s) @trusted
{
immutable(char)* c = s.ptr;
writeln(g(* cast(size_t*) &c));
return 'x'; /* ! */
}
Or maybe we say that c gets invalidated by the call to g and using it
afterwards triggers undefined behavior. Then f2 is ok. Would still have
to disallow calls that pass both ends of an unsafe aliasing to an
@safe/@trusted function, though.
Thoughts? Am I overthinking it as usual when it comes to @trusted?
[1] https://forum.dlang.org/post/iwddwsdpsntajyblnttk@forum.dlang.org
[2] https://dlang.org/spec/function.html#safe-interfaces
More information about the Digitalmars-d
mailing list