@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