C++ Logo

std-proposals

Advanced search

[std-proposals] Safety checks at compile time

From: <roberto.romani_at_[hidden]>
Date: Tue, 14 Feb 2023 18:02:24 +0100
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

 


Received on 2023-02-14 17:02:27