@trusted assumptions about @safe code

ag0aep6g anonymous at example.com
Wed May 27 06:36:14 UTC 2020


On 27.05.20 02:50, Paul Backus wrote:
> On Tuesday, 26 May 2020 at 22:52:09 UTC, ag0aep6g wrote:
>> But yeah, the @trusted function might rely on the @safe function 
>> returning 42. And when it suddenly returns 43, all hell breaks loose. 
>> There doesn't need to be any monkey business with unsafe aliasing or 
>> such. Just an @safe function returning an unexpected value.
>>
>> I suppose the only ways to catch that kind of thing would be to forbid 
>> calling @safe (and other @trusted?) functions from @trusted (and 
>> @system?) code, or to mandate that the exact behavior of @safe 
>> functions (including their return values) cannot be relied upon for 
>> safety. Those would be really, really tough sells.
> 
> All that's necessary is to have the @trusted function check that the 
> assumption it's relying on is actually true:
> 
> @safe int foo() { ... }
> 
> @trusted void bar() {
>      int fooResult = foo();
>      assert(fooResult == 42);
>      // proceed accordingly
> }
> 
> If the assumption is violated, the program will crash at runtime rather 
> than potentially corrupt memory.

I.e., "the exact behavior of @safe functions (including their return 
values) cannot be relied upon for safety". I think it's going to be hard 
selling that to users. Especially, because there is no such requirement 
when calling @system functions.

Say you have this code:

     void f() @trusted
     {
         import core.stdc.string: strlen;
         import std.stdio: writeln;
         char[5] buf = "foo\0\0";
         char last_char = buf.ptr[strlen(buf.ptr) - 1];
         writeln(last_char);
     }

That's ok, right? `f` doesn't have to verify that `strlen` returns a 
value that is in bounds. It's allowed to assume that `strlen` counts 
until the first null byte.

Now you realize that you can calculate the length of the string more 
safely than C's strlen does, so you change the code to:

     size_t my_strlen(ref char[5] buf) @safe
     {
         foreach (i; 0 .. buf.length) if (buf[i] == '\0') return i;
         return buf.length;
     }
     void f() @trusted
     {
         import std.stdio: writeln;
         char[5] buf = "foo\0\0";
         char last_char = buf.ptr[my_strlen(buf) - 1];
         writeln(last_char);
     }

Nice. Now you're safe even if you forget to put a null-terminator into 
the buffer.

But oh no, `my_strlen` is @safe. That means `f` cannot assume that the 
returned value is in bounds. It now has to verify that. Somehow, it's 
harder to call the @safe function correctly than the @system one.

What user is going to remember those subtleties?


More information about the Digitalmars-d mailing list