Hi all

I guess many of you have read articles about Microsoft, Google and NSA saying that it is better to stop to use C++ and use instead memory safe languages like Rust or Java. 

 

Is it possible to make C++ a memory safe language? For sure it is possible to make it safer, at least for new code that does not use legacy code.

It needs a few extensions to the language, and to add libraries that privilege safety instead of performance, to be used when their performances are still acceptable.

First step. The below idea is inspired by Rust, it is not simple, and it still needs to be discussed in order to be improved.

 

Let’s start with examples of the problems that this proposal could solve.

 

void example1()

{

    std::unique_ptr<int> ptr_i = std::make_unique<int>(1);

 

    std::unique_ptr<int> ptr_j{ std::move(ptr_i) };

 

    std::cout << *ptr_i << "\n"// this ptr_i is not valid, it should not compile

}

 

In the above case Visual Studio is able to detect that there is something wrong, it returns this warning: https://learn.microsoft.com/en-us/cpp/code-quality/c26800?view=msvc-170 , but I am still able to compile it, run it, and crash.

 

Visual Studio and GCC in the next example don’t return any warning.

 

 

std::unique_ptr<int> move_ptr(std::unique_ptr<int>& p)

{

    std::unique_ptr<int> j{ std::move(p) };

    return j;

}

 

 

void example2()

{

    std::unique_ptr<int> ptr_i = std::make_unique<int>(1);

 

    std::unique_ptr<int> ptr_j = move_ptr(ptr_i);

 

    std::cout << *ptr_i << "\n"; // this should not compile

}

 

 

In the next example ptr_i may be valid but maybe not, anyway it is unsafe code.

 

void example3()

{

    std::unique_ptr<int> ptr_i = std::make_unique<int>(1);

   

    if (something())

    {

        std::unique_ptr<int> ptr_j = std::move(ptr_i);

    }

    std::cout << *ptr_i << "\n"// this should not compile

}

 

After an object is move from cannot be used!!! Or more generic after an object get unsafe cannot be used.

 

 

std::string* ret_null()

{

    return nullptr;

}

 

void example3()

{

    std::string *s;

 

    std::cout << s->empty();  // Visual Studio and GCC finds the issue

 

    s = nullptr;

    std::cout << s->empty();  // VS finds the issue, GCC no

 

    s = ret_null();

    std::cout << s->empty();

}

 

 

A not initialized object is unsafe.

To copy an unsafe object (ie nullptr) makes also the destination unsafe.

Of course, there may be some exception to the rule, shall we care about them? Which ones? How?

Ie. The assign operator and the move operators usually give back a safe object, and in general there are methods that could still be used also if the object is unsafe.

 

Some definitions: an object is Safe if all the possible operations on it have a well-defined behaviour otherwise it is Unsafe (therefore it is not just about memory safe). On an Unsafe object it is possible to make some operations.

There are operators that don’t change its safety status.

 

How could it be implemented?

Introduce 3 new keywords, first proposal: to_unsafe, to_safe, unsafe.

 

If I declare a function f like this:

void f(to_unsafe T& i);

 

and the I try to call it like this

 

T o = …;

f(o);

 

Then after the call the compiler knows that o may be unsafe and should not allow me to use it for all the operations that require safe objects. The parameter of std::move should have the attribute to_unsafe.

 

If I call the above function f inside another function like this:

void g(to_unsafe int& j)

{

    f(j);

}

 

Then the compiler must impose me to declare j to_unsafe.

 

Another example: after the call of the delete operators objects get unsafe, therefore the parameter of the delete operators should have the attribute to_unsafe.

 

to_unsafe as qualifier of a member function indicates that the object may get unsafe after the call of the function. unique_ptr::relese()  should have the attribute to_unsafe.

 

to_safe is the opposite of to_unsafe.

 

If I declare the function h like this:

h(to_safe T& i)

 

T o=…

h(o);

 

o will be safe after the call of h o.

 

The keyword unsafe can be the attribute of a function parameter, the qualifier of a member function, the qualifier of a return type of a function or the qualifier of a variable.

 

m(unsafe std::unique_ptr & p)

{

    if (p)….

    else…

}

 

unsafe indicates that the function m can handle the parameter p that can be unsafe.

 

The bool operator of std::unique_ptr is unsafe because it can be called also on unsafe objects.

 

Unsafe can be used also for object declaration.

By default, an object is safe

nullptr is an unsafe object.

A not initialized variable is unsafe, any type of variable not only the pointers.

 

The return value of function that can returns an unsafe value (ie nullptr) must declare unsafe.

 

Copy and Move operators that support unsafe sources must be declared to_unsafe.

For example, like this:

unsafe A& operator=(const unsafe A &src) to_unsafe;

unsafe A& operator=(unsafe A &&src) to_unsafe;

 

Typically, if a class has copy and move operators that support unsafe source then they will have copy and move operators that support only safe sources.

 

This proposal has backwards compatibility issues, we must also introduce a #pragma to enable and disable these checks.

 

 

Best Regards

Roberto