Some memory safety

bearophile bearophileHUGS at lycos.com
Mon May 18 09:13:11 PDT 2009


My theory is that catching some bugs early is better than catching less bugs early, or none.

The following example are D1, but I think the situation for D2 is the same.

-----------------

#1) Today this bug is found at compile-time, good:

void main() {
    int[5] a;
    a[6] = 1;
}

-----------------

#2) Today if not compiled with -release, if n=10 and m=15, the following program raises a ArrayBoundsError at compile time:

import std.conv: toInt;
void main(string[] args) {
    int n = args.length >= 2 ? toInt(args[1]) : 10;
    int m = args.length >= 3 ? toInt(args[2]) : 5;
    auto a = new int[n];
    a[m] = 10;
}

-----------------

#3) The following bug too is found at compile-time, but the error message shows that the index is converted silenty into an unsigned integer, so this less good:

void main() {
    int[5] a;
    a[-2] = 1;
}

temp.d(3): Error: array index 4294967294 is out of bounds a[0 .. 5]

-----------------

#4) The compiler doesn't find the following bugs at compile-time. Conceptually it's the same as #1, so I'd like the compiler to refuse this at compile time in many situations:

import std.c.stdlib: malloc;
void main() {
    int* a = cast(int*)malloc(int.sizeof * 5);
    a[10] = 1;
}


import std.c.stdlib: malloc;
void main() {
    byte* b = cast(byte*)malloc(int.sizeof * 5);
    b[25] = 1;
}

Newly written D code that uses malloc is less common, but you can often find malloc in C code ported to D.

-----------------

#5) The compiler doesn't find this out of bounds at run time if n=10 and m=15, but it's not much different from #2. The compiler when not in release mode can keep a run-time variable that stores the length of the memory zone, and produces something like MemoryBoundsError when accessed outside:

import std.c.stdlib: malloc;
import std.conv: toInt;
void main(string[] args) {
    int n = args.length >= 2 ? toInt(args[1]) : 10;
    int m = args.length >= 3 ? toInt(args[2]) : 5;
    int* a = cast(int*)malloc(int.sizeof * n);
    a[m] = 10;
}

(Well, in this situation the compiler may even note that such hidden length variable is equal to n, but I generally don't need a compiler that smart).

-----------------

5b#) This bug is a bit less easy to find at compile time:

import std.c.stdlib: malloc;
import std.conv: toInt;
void foo(short* a, int m) {
    a[m] = 10;
}
void main(string[] args) {
    int n = args.length >= 2 ? toInt(args[1]) : 10;
    int m = args.length >= 3 ? toInt(args[2]) : 5;
    short* a = cast(short*)malloc(short.sizeof * n);
    foo(a, m);
}

It may be found (when not in -release mode) translating that code to something like:

import std.c.stdlib: malloc, exit;
import std.c.stdio: fprintf, stderr;
import std.conv: toInt;
void foo(short* a, int m, size_t __alength) {
    if ((m * (*a).sizeof) >= __alength) {
        fprintf(stderr, "MemoryBoundsError(%d)\n", __LINE__  + 3);
        exit(1);
    }
    a[m] = 10;
}
void main(string[] args) {
    int n = args.length >= 2 ? toInt(args[1]) : 10;
    int m = args.length >= 3 ? toInt(args[2]) : 5;
    size_t __alength = short.sizeof * n;
    short* a = cast(short*)malloc(__alength);
    foo(a, m, __alength);
}

-----------------

All I have shown here is very primitive. Cyclone designers have given 10000 times more thinking time than me to such topics. So Cyclone is able to spot similar and more bugs.
C# uses other useful tricks that I am currently looking at.

Bye,
bearophile



More information about the Digitalmars-d mailing list