Trusted Manifesto

John Colvin via Digitalmars-d digitalmars-d at puremagic.com
Mon Feb 9 02:54:50 PST 2015


On Monday, 9 February 2015 at 08:19:24 UTC, Walter Bright wrote:
> Andrei and I have learned a lot from the @trusted discussions. 
> It's clear the way we were approaching the problem was 
> inadequate. So we came up with a proposal based on the ideas 
> and criticisms of the participants in the references. It 
> involves no language changes, but offers usage guidelines that 
> we believe are workable. Tell us what you think!
> ----------------------------------------------------
>
> Trusted Manifesto
> -----------------
>
> Memory safety in D has the usual definition: a memory-safe 
> program never reads
> uninitialized memory and never reads or writes memory with a 
> type incompatible
> with the type it was written. The aim of D's
> @safe, @trusted, and @system attributes is to provide as much
> mechanically verified to be safe code as possible.
>
>
> Functions
> ---------
>
> Function signatures inform what must be true about a function. 
> If
> a function is marked as @safe, it must contain only @safe code. 
> This
> safety must be mechanically checkable.
>
> Sometimes, an unsafe operation is needed even in a function
> that is overall safe. For example,
> here's a function that returns an upper case version of its
> input string:
>
>   string toUpper(string s) @safe
>   {
>      char[] r = new char[s.length];
>      foreach (i, c; s)
> 	r[i] = toUpper(c);
>      return cast(string)r; // <== unsafe operation
>   }
>
> The compiler rejects this function because it's declared as 
> safe but contains an
> unsafe operation.
> Review of the whole function shows that the operation, in this 
> instance, is safe.
> One way to deal with that is to do what is called an "escape", 
> meaning
> telling the compiler "I know what I'm doing, so allow this 
> operation":
>
>   string toUpper(string s) @safe
>   {
>      char[] r = new char[s.length];
>      foreach (i, c; s)
> 	r[i] = toUpper(c);
>
>      static string trustedCast(char[] r) @trusted
>      {
>         return cast(string)r;
>      }
>
>      return trustedCast(r);
>   }
>
> This will pass typechecking. However, the only reason it is 
> safe is because the context
> in which trustedCast() is called makes it safe. In other words, 
> a manual review of the surrounding
> context is necessary, which violates the @safe promise that its 
> contents are mechanically
> checkable.
>
> I.e. the trustedCast() function is an "escape" that injects 
> unsafety into its surrounding
> contents.
>
> This leads to:
>
> RULE 1: @trusted code accessible from @safe code must expose a 
> safe interface to unsafe operations.
>
> @trusted must not be used to inject unsafety into surrounding 
> context that is marked @safe.
> @safe code must be mechanically verifiable to be safe, and 
> subverting that is not acceptable.
>
> COROLLARY 1: @trusted functions should be as small as possible 
> to encapsulate the unsafe operation
> without injecting unsafety into @safe code.
>
> In the case of toUpper(), it is necessary to review the entire 
> function to verify that the cast is safe,
> so it is properly written:
>
>   string toUpper(string s) @trusted
>   {
>      char[] r = new char[s.length];
>      foreach (i, c; s)
> 	r[i] = toUpper(c);
>      return cast(string)r;
>   }
>
> Use of local @trusted functions with safe interfaces is 
> encouraged to minimize the amount of safety code review 
> required.
>
>
> Generic Functions
> -----------------
>
> Generic functions are templates that accept compile time 
> parameters in the form of types, values or aliases to other 
> functions.
> Whether the function is @safe or @system is not checkable until 
> the template function is instantiated with explicit
> arguments. If the template function is marked as @safe, then it 
> can only be instantiated with arguments that expose
> safe operations.
>
> If the template function is marked @safe, then RULE 1 applies.
>
> But that reduces the genericity of the function. The compiler 
> is able to deduce whether a template function is @safe or 
> @system
> when it is instantiated. For maximum utility, we need a way to 
> specify that:
>
>     This template function is @safe if the generic and 
> non-generic operations it uses are @safe as well,
>     otherwise it is @system.
>
> Consider a function to make an immutable array copy of a range:
>
>   immutable(ElementType!Range)[] toArray(Range)(Range r)
>   {
>      alias ElementType!Range E;
>      alias Unqual!E U;
>      U[] a = new U[r.length];
>      foreach (i, e; r)
> 	a[i] = e;
>      return cast(immutable)a; // <== unsafe operation
>   }
>
> Being a template function without specified attributes, the 
> compiler will infer the attributes.
> But with the unsafe cast, toArray() will always be inferred to 
> be @system. But the rest
> of the code is safe. If toArray is marked as @trusted,
>
>   immutable(ElementType!Range)[] toArray(Range)(Range r) 
> @trusted
>   {
>      alias ElementType!Range E;
>      alias Unqual!E U;
>      U[] a = new U[r.length];
>      foreach (i, e; r)
> 	a[i] = e;
>      return cast(immutable)a; // <== unsafe operation
>   }
>
> then if the range primitives (front, empty, popFront) exposed 
> by the argument to r
> happen to be @system, then those are invalidly assumed to be 
> trustable. Every usage
> of toArray() would need to be reviewed for safety, which is 
> impractical.
>
> What is needed is a way to isolate the unsafe operation, and 
> enable the compiler to
> infer the rest. In other words, a local exemption from overall 
> safety deduction is needed.
>
> Introducing the 'trusted' template to be put in std.conv:
>
>   @trusted auto trusted(alias fun)() { return fun(); }
>
> and used:
>
>   immutable(ElementType!Range)[] toArray(Range)(Range r)
>   {
>      alias ElementType!Range E;
>      alias Unqual!E U;
>      U[] a = new U[r.length];
>      foreach (i, e; r)
> 	a[i] = e;
>
>      import std.conv : trusted;
>      auto result = trusted!(() => cast(immutable)a);
>      return result;
>   }
>
> Use of the trusted escape requires the programmer to review the 
> context to determine if
> it really is safe. The compiler will infer safety from the rest 
> of the operations.
>
> RULE 2: Usage of escapes are only allowable in functions for 
> which safety is inferred,
> and never when calling into as-of-yet not defined generic 
> functions.
>
> But how can it be verified that toArray() is safe otherwise?
>
> RULE 3: An @safe unittest must be used to verify safety when 
> escapes are used.
>
>   @safe unittest
>   {
>      ... TODO: test toArray() ...
>   }
>
> A unittest may also be constructed that verifies via static 
> assert that if a type
> with @system operations is passed to the function, that the 
> function is inferred
> as @system.
>
> The programmer must still verify that the usage of escapes that 
> leak
> unsafety into the surrounding context is safe.
>
> RULE 4: Escape unsafety must not inject unsafety beyond the 
> template function it is used in.
>
> Alternatives
> ------------
>
> 1. if(0) block
>
> Provide an @trusted local function that fully encapsulates the 
> unsafe code and its context,
> providing a safe interface. For the operations on template 
> parameters that may or may not be
> safe inside the local function, represent them in an if(0) 
> block of code:
>
>   if (0) // safety inference
>   {
>     Unqual!T tmp = cast(Unqual!T)item;
>     emplaceRef!(Unqual!T)(tmp, cast(Unqual!T)item);
>   }
>
>   @trusted void emplace()
>   {
>     auto bigData = _data.arr.ptr[0 .. len + 1];
>
>     emplaceRef!(Unqual!T)(bigData[len], cast(Unqual!T)item);
>
>     //We do this at the end, in case of exceptions
>     _data.arr = bigData;
>   }
>   emplace();
>
> Although this works, it requires duplication of code in a 
> rather careful, tedious, and essentially
> unmaintainable manner. It also simply looks wrong, although it 
> could be made more palatable by
> enclosing it in a template.
>
> 2. isSafe!T template
>
> Such a template would test that all operations on type T are 
> @safe. The template function could
> then be marked @trusted. The troubles with this are (a) it is 
> all or nothing with T, i.e. if a
> template function only used an @safe subset of T, it still 
> would not be accepted and (b) it does
> not do proper inference of the safety of a template function.
>
> 3. @system escape
>
> @system would be used for escaping unsafe code in an @trusted 
> function, or in an un-attributed function
> it would instruct compiler to not use the escaped code when 
> deducing trustworthiness. Unsafe code in an @trusted function
> not so marked would generate an error. While this works, it 
> would essentially break every @trusted function
> already in existence. It is a somewhat nicer syntax than the 
> std.conv.trusted template, but the backwards compatibility
> issue makes it unworkable. It offers a technical advantage over 
> std.conv.trusted in that @system will not be
> allowed in @safe functions, while not allowing std.conv.trusted 
> escapes in @safe function would be by convention.
>
> References
> ----------
>
> https://github.com/D-Programming-Language/phobos/pull/2966
>
> http://forum.dlang.org/post/mb0uvr$2fdb$1@digitalmars.com
>
> Acknowledgements
> ----------------
>
> Everyone who participated in the references!

Looks great.

It seems to me that rules 2 and 3 could be helped along by 
tooling (compiler or external).


More information about the Digitalmars-d mailing list