Preventing memory corruption bugs with @safe

Memory corruption bugs are one of the most frustrating problems a C programmer can encounter. Memory corruption is writing to what you believed was memory owned by one object; it affects another object in unpredictable ways, leading to crashes. D aims to reduce the scope and occurrence of these bugs by providing a statically checked memory-safe subset which, ideally, exhibits no undefined behavior. In practice, @safe isn't perfect due to bugs in the specification and the implementation, but it nevertheless helps to significantly reduce the probability of memory corruption bugs.

How to do it…

Perform the following steps by using @safe:

  1. Mark as many functions as possible with the @safe annotation and try to compile. The compiler will tell you which functions failed the test.
  2. Use high-level constructs, such as foreach, references, and D array slices instead of low-level features such as pointers wherever possible.
  3. Instead of pointer arithmetic, use array slicing. For example, replace ptr++; with slice = slice[1 .. $];.
  4. Avoid the cast operator.
  5. Delegate arguments to be used in the @safe functions must also be marked @safe.
  6. If a function must perform prohibited actions, but can be manually verified to be correct, you may mark that function as @trusted.
  7. Trust the garbage collector. Freeing memory manually is difficult to verify; if you free memory while there's still a reference to it, using that reference will result in memory corruption. So, unless all references are very carefully encapsulated, they should not be marked @trusted. The garbage collector will never free a reference prematurely.
  8. To turn off @safe, mark functions @system. These attributes work on the function level, so you may have to re-factor code into additional functions to reach the level of granularity you need.

    Tip

    You may add @safe: to the top of your module and aggregate definitions to apply the annotation to all functions that follow, instead of writing it on each individual function.

    Consider the following code snippet:

    @safe void foo() {}

How it works…

The @safe annotation (sometimes called SafeD because that was the first-draft name of the concept) is an opt-in set of restrictions that aim to make D code have no undefined behavior and be memory safe, ensuring that memory is never unexpectedly corrupted.

The @safe annotation prohibits the use of several of D's lower-level features:

  • Pointer manipulation is one such feature, since a pointer mistake is the most likely cause of memory corruption
  • D's lower-level feature also include inline assembly, since the compiler cannot guarantee it is used correctly
  • Casting between pointer types and using unions with pointers because type safety is lost
  • Calling functions not marked @safe or @trusted, since the compiler cannot verify whether they are implemented correctly
  • Taking the address of a local variable, since returning an address of a local can lead to stack corruption
  • Catching any exception not derived from Exception, since the Throwable tree derived from Error signify unrecoverable errors
  • Accessing globally shared data, since a race condition between threads may cause undefined behavior
  • Explicitly casting between immutability or shared, since this potentially breaks type system guarantees by allowing modification of immutable data as well as violations of thread safety

    Note

    @safe does not prevent null pointer dereferences. Since the program is terminated by a null pointer (through a segmentation fault or an unrecoverable Error being thrown), no memory corruption will occur. A crashing program might be annoying and may fail to release resources or leave a file half-written, but since the behavior of immediate termination is well-defined, it is not prevented by restrictions of @safe. It is recommended that you use early-and-often assertions and/or NotNull types to aid in debugging null reference issues.

The recurring theme is that @safe is about guarantees. If the compiler cannot guarantee it is correct, it is prohibited. What about the times when the compiler cannot make a guarantee, but the programmer can? This is where @trusted comes into play.

The @trusted functions violate one of the @safe rules, but is manually verified to do it correctly; the compiler trusts you to get it right. For example, using pointer arithmetic is memory safe, if and only if kept within bounds. If you can manually verify this is done correctly, you may mark the function @trusted. If you do, the function should be limited to returning a strongly-typed D slice (whose bounds are known to the D compiler and/or runtime and managed automatically), which is then worked with inside a fully @safe function. A @trusted function should be as small and as simple as possible.

Note

Array bound checks may be turned off with the –noboundscheck flag to dmd, but only in non-@safe functions. In @safe functions, array bound checks are always present to ensure memory cannot be corrupted by writing past the array length.

It is a mistake to mark too many functions @trusted. It is better to rewrite functions to use higher level features whenever possible. Pointer arithmetic can be translated into array slice operations: slice = slice[1 .. $] generates the same code as ++ptr; (although with range boundary checks inserted when the compiler cannot statically ensure the operation is in bounds). The slice[0] = 0; statement generates the same code as *ptr = 0;.

The --ptr; value is a bit trickier to convert, since slice = slice[-1 .. $]; will generate an out-of-bounds error. To solve this problem, use two variables: a buffer slice and an index integer. Manipulate the integer instead of the slice or pointer.

The @system functions have no restrictions. It is the default safety level because of D's heritage from C. If you want to ensure your entire program is memory-safe, you may mark main @safe; though @safe is opt-in, this may limit your usage of libraries.

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.12.153.212