Trusted Manifesto

H. S. Teoh via Digitalmars-d digitalmars-d at puremagic.com
Mon Feb 9 18:21:20 PST 2015


On Mon, Feb 09, 2015 at 12:19:10AM -0800, Walter Bright via Digitalmars-d wrote:
[...]
> Trusted Manifesto
> -----------------
[...]
> 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.
[...]
> Generic Functions
> -----------------
[...]
> 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(); }

This seems to be a step in the right direction, but doesn't this
contradict rule 1? Certainly, trusted() doesn't provide a @safe API, yet
it is marked @trusted.  What stops the following abuse of @trusted via
trusted()?

	int* myFunc(void* p) @safe // <-- I'm claiming to be @safe
	{
		// But actually I'm not! Though I can convince the
		// compiler that I am...
		return trusted!(() => cast(int*)p);
	}

	char c;
	auto p = myFunc(&c); // oops
	*p = 999; // kaboom

Are we just relying on convention that trusted() will not be abused in
this way?

And how would we prevent users from defining similar templates for
escaping safety without being readily detectable, e.g., by grepping for
'trusted'?

	auto sneaky(alias fun)() @trusted { return fun(); }

Or is this a case of "if you insist on shooting your own foot the
compiler can't/won't help you"?


[...]
> 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.

I wonder if there's a way, in the current language, to make it illegal
to call trusted() from a function explicitly marked @safe...?



[...]
> 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.

Makes sense, and does reflect current usage in Phobos. Thanks!!


> RULE 4: Escape unsafety must not inject unsafety beyond the template
> function it is used in.

IOW, the following is illegal?

	int* myTemplate(void* p) @trusted // <-- illegal leakage of unsafety
	{
		return trusted!(() => cast(int*) p);
	}


> 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:
[...]
> 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.

Yeah, this doesn't look viable.


> 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.

What about isSafe!(T, method1, method2, ...)? I.e., test the safety of
an explicit list of operations that the template function will be using?

Of course, this isn't a fully-functional solution, since it doesn't
handle the case where you *want* your template function to accept types
with unsafe methods (just that the template function becomes @system
instead of @safe). You'd have to copy-n-paste the function body and mark
one @safe (with isSafe!(...)) and the other @system (with
!isSafe!(...)).

Unless there's a way of injecting function attributes based on the
result of isSafe, like:

	// Hypothetical syntax
	auto myTemplate(R)(R range)
		{ @safe if (isSafe!(R, empty, front, popFront)) }
	{ ... }

But since we're trying not to require additional language support, this
isn't really an option either.


> 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.
[...]

If there was a way to force a compile error if someone tried to
instantiate trusted() from inside an explicitly-marked @safe function,
we could have our cake and eat it too. One (very hackish) way might be a
pragma or __trait that tests for explicit @safe marking:

	auto trusted(alias fun)() @trusted {
		// callerAttribute is a hypothetical trait.
		static if (__traits(callerAttribute, @safe)) {
			static assert(0, "Abuse of trusted()");
		} else {
			return fun();
		}
	}

	void abusive(void* p) @safe {
		int* ip = trusted!(() => cast(int*)p); // compile error: "Abuse of trusted()"
	}

It's probably a bad idea to extend __traits in this way, but the point
is that if we can *somehow* determine the @safe marking of the caller,
then we could prevent blatant abuses of trusted(). I don't know if the
current language is able to achieve that, though. Can it?


T

-- 
Computers are like a jungle: they have monitor lizards, rams, mice, c-moss, binary trees... and bugs.


More information about the Digitalmars-d mailing list