Bug 12941 – Everything should be @unsafe by default, and explicitly vetted (and documented) as @safe
Status
RESOLVED
Resolution
INVALID
Severity
enhancement
Priority
P1
Component
dmd
Product
D
Version
D2
Platform
x86
OS
Linux
Creation time
2014-06-18T17:18:00Z
Last change time
2016-06-07T08:40:37Z
Keywords
safe
Assigned to
nobody
Creator
hsteoh
Comments
Comment #0 by hsteoh — 2014-06-18T17:18:00Z
Since everyone keeps talking about this in the NG but nobody does anything about it, I'll put myself in the firing range and do it.
Basically, the compiler should treat everything as non-@safe, except for a list of @safe operations that is well-defined and well-documented. For starters, let's make a list of operations that are known to be @safe on this bug, and once the list is reasonably complete, we can start looking at the compiler sources to tweak the current implementation to match this list.
Comment #1 by deadalnix — 2014-06-18T17:27:50Z
Here is my list:
- The whole language
Good, now that everything is @system, let's start a list of what is @safe instead.
Comment #2 by hsteoh — 2014-06-18T18:03:17Z
Um, isn't that stating the obvious? I thought I made it clear that the list was supposed to be a list of @safe constructs, not a list of un-@safe constructs.
Comment #3 by hsteoh — 2014-06-19T17:30:05Z
Hmm, nobody seems interested to add anything here, so I'll start.
The empty statement is @safe, so by extension, an empty function is @safe.
Therefore, invoking a @safe function is also @safe. So, function that (only) calls @safe functions (and does nothing else), is also @safe.
Expressions using built-in types without indirection are @safe. (Right?) Returning by-value types is also @safe.
All declarations without initializers are @safe, regardless of the type of the declared symbol(s). Declarations with initializers are @safe provided the initializer expression is @safe.
Control-flow constructs (if, else, for, foreach, do, while, goto, etc.) are all @safe, provided their constituent elements are @safe. E.g., a for-loop is @safe as long as the initializer, loop condition, loop increment, and loop body, are all @safe. Same thing goes for compound statements: a compound statment is @safe if all constituent statements are @safe.
These should cover the basics. Anything else? Once all basic constructs are covered then we can look at cases that directly involve memory safety, like references, pointers, slices, etc..
Comment #4 by timon.gehr — 2014-06-19T22:46:11Z
My thoughts were:
@safety is to be checked after lowering has been applied. (such as template instantiation
The following are all @safe:
expressions:
- reading and writing (by the means of the = operator) of (thread-local or immutable) static and function-local (mutable, const, immutable, inout) variables.
- reading and writing of (struct) member variables of @safe receivers. (extra precautions need to be taken for class members.)
- accessing the 'this'/'super' reference.
- null literals
- boolean literals
- $
- all forms of integer, character and float literals
- string literals
- array literals with @safe constituents
- associative array literals with @safe constituents
- all function literals
- assert expressions with @safe arguments
- built-in new expressions with @safe arguments for types without destructors.
- built-in member new expressions with @safe receiver of class type and @safe arguments for types without destructors.
- slicing of expressions of type T[] for some T
- indexing into expressions of type T[] for some T (!=void)
- pre- and post- ++ and -- for unqualified basic types
- calling @safe/@trusted static functions with by-value or 'ref' @safe arguments
- calling @safe/@trusted member functions on with by-value or 'ref' @safe arguments on @safe receivers
- taking the address of (all!) static functions
- universal construction for (qualified) basic types with an @safe argument.
- binary ||, &&, |, ^, &, ==, !=, >, >=, <, <=, !<>=, <>, <>=, !<=, !<, !>=, !>, !<>, <<, >>, >>>, +, -, *, /, %, ^^ and unary -, +, !, ~ expressions on (possibly const, immutable or inout) basic type @safe operands.
- conditional expressions (?:) on @safe operands
- assign expressions of the form 'a op= b' are @safe if 'a=a op b' is @safe. (TODO: formalize properly)
- comma expressions (,) on @safe operands.
statements:
- the empty (;) statement
- a block statement containing only @safe statements
- case statements, case range statements, default statements consisting of @safe statements.
- a labelled @safe statement.
- an expression statement consisting of a @safe expression
- a declaration statement consisting of a @safe nested-declaration
- if statements with @safe condition, @safe then statement and @safe or missing else statement.
- while statements with @safe condition and @safe body.
- do statements with @safe body and @safe condition
- for statements with @safe initializer, @safe test, @safe increment and @safe body.
- (final) switch statements with @safe switch expression and @safe body.
- continue statements
- break statements
- by value return statements with @safe or missing return expression
- goto statements (assuming that all skip-initialization cases are caught.)
- with statements with @safe with expression and @safe body
- try statements with @safe body and @safe catch / finally statements
- catch statements with @safe body
- finally statements with @safe body
- throw statements with @safe throw expression
nested-declarations:
- declarations not directly influencing code generation: alias declarations, aggregate declarations, enum declarations, import declarations
- template mixin with @safe body
- variable declarations which are default-initialized or initialized using an @safe expression
A function can be annotated @safe if its body and its contracts (if any) are @safe statements.
TODO: identify missing items on this list
TODO: think properly about class references and unary '*' (dereferencing null pointers runs into issues in the (*nullptr).foo case.)
TODO: think about and specify which type casts are @safe. (This should include at least the implicit conversions between built-in types.)
TODO: shared, synchronized?
TODO: Specify the invariants that @safe functions assume relating to the memory contents. Those invariants need to be established by @trusted functions before returning and by all functions before calling into @safe functions. @safe functions are guaranteed to preserve those invariants given that all @trusted functions do.
TODO: Maybe give a semi-formal proof of the above assertion.
TODO: probably this is better with an accompanying wiki page or something
Comment #5 by hsteoh — 2014-06-19T23:07:42Z
Are you sure you have excluded pointer arithmetic in the section under expressions?
Comment #6 by hsteoh — 2014-06-19T23:11:36Z
Also, slicing of static arrays should be considered @system, because unless we implement scope properly, they will break @safe-ty.
class C {
int[] data;
this(int[] _data) {
data = _data;
}
}
void func() {
int[4] arr = [1,2,3,4];
return new C(arr[]); // oops
}
Comment #7 by issues.dlang — 2014-06-19T23:19:55Z
> Also, slicing of static arrays should be considered @system, because unless we implement scope properly, they will break @safe-ty.
And even then, they still have to be @system unless they're immediately passed as a function argument to a parameter that's marked scope. The same goes for taking the address of a local variable. Essentially, the two operations are identical, save for the fact that slicing a static array also involves copying the length.
Comment #8 by hsteoh — 2014-06-19T23:27:13Z
Furthermore, delegates that close over struct members are @system if the closure leaves the construction scope, for example:
struct S {
int x;
void delegate() dg;
this() {
dg = (){ x++; }; // N.B. closure over x
}
}
S makeS() {
return S(); // oops -- S may get moved during 'return'
}
void main() {
auto s = makeS();
s.dg(); // memory corruption
}
Comment #9 by timon.gehr — 2014-06-20T06:32:18Z
> Are you sure you have excluded pointer arithmetic in the section under
> expressions?
Yes, this section only allows basic types as operands (http://dlang.org/type.html).
> Also, slicing of static arrays should be considered @system, because unless we implement scope properly, they will break @safe-ty.
This is taken care of. Slicing of static arrays is not included in the list.
> Furthermore, delegates that close over struct members are @system if the closure leaves the construction scope, for example:
This is a very good point (the list does not actually include delegate literals yet though, only function literals. This terminological distinction is possibly confusing, but I think it is what the compiler developers use.)
I'll extend my post slightly:
The following are also @safe:
- taking the length of an @safe expression of type T[] for some T
- indexing into @safe expressions of static array type
- operations on associative arrays (TODO: examine them one by one.)
TODO: determine which delegate literals are @safe.
Comment #10 by dfj1esp02 — 2014-12-11T08:09:14Z
(In reply to hsteoh from comment #3)
> All declarations without initializers are @safe, regardless of the type of
> the declared symbol(s).
What if the default initializer violates the type's invariant?
Comment #11 by issues.dlang — 2014-12-13T10:22:19Z
(In reply to Sobirari Muhomori from comment #10)
> (In reply to hsteoh from comment #3)
> > All declarations without initializers are @safe, regardless of the type of
> > the declared symbol(s).
>
> What if the default initializer violates the type's invariant?
That doesn't make anything unsafe. It just means that calling anything on the type will violate the invariant, and an AssertError will be thrown when built with assertions enabled. @safe is safety with regards to memory safety and really has nothing to do with invariants aside from whether an invariant is marked with @safe, @trusted, or @system. And having an invariant doesn't save you from @safety problems with default-initializers which are @system (e.g. initializing to void), because the invariant isn't run in release mode. And even if the invariant were guaranteed to be run, the only way that it could guarantee @safety with a void initializer would be if it checked the value that that member was initialized to, and the compiler verified that that was being done, which it doesn't do. As far as the compiler is concerned, the invariant is pretty much just a normal function that happens to have assertions in it. It's only special because it gets run before and after calls to public functions and gets compiled out in release. The compiler doesn't generally care about what's actually inside the invariant.
Comment #12 by bearophile_hugs — 2014-12-14T19:24:52Z
See also Issue 13838
Comment #13 by dfj1esp02 — 2014-12-15T08:15:40Z
(In reply to Jonathan M Davis from comment #11)
> That doesn't make anything unsafe. It just means that calling anything on
> the type will violate the invariant, and an AssertError will be thrown when
> built with assertions enabled.
The method's safety can rely on invariant. If assertions are disabled the method will proceed and corrupt memory?
Comment #14 by bugzilla — 2016-06-07T08:40:37Z
I don't think this is the place for a general open-ended discussion like this. Bugzilla issues should have specific action items that can be resolved. Seems like this should be reopened as a DIP if people care to pursue it.
For lack of anything better, I'll mark it as invalid.