forked from OSchip/llvm-project
177 lines
6.5 KiB
ReStructuredText
177 lines
6.5 KiB
ReStructuredText
.. title:: clang-tidy - cppcoreguidelines-owning-memory
|
|
|
|
cppcoreguidelines-owning-memory
|
|
===============================
|
|
|
|
This check implements the type-based semantics of ``gsl::owner<T*>``, which allows
|
|
static analysis on code, that uses raw pointers to handle resources like
|
|
dynamic memory, but won't introduce RAII concepts.
|
|
|
|
The relevant sections in the `C++ Core Guidelines <https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md>`_ are I.11, C.33, R.3 and GSL.Views
|
|
The definition of a ``gsl::owner<T*>`` is straight forward
|
|
|
|
.. code-block:: c++
|
|
|
|
namespace gsl { template <typename T> owner = T; }
|
|
|
|
It is therefore simple to introduce the owner even without using an implementation of
|
|
the `Guideline Support Library <https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#gsl-guideline-support-library>`_.
|
|
|
|
All checks are purely type based and not (yet) flow sensitive.
|
|
|
|
The following examples will demonstrate the correct and incorrect initializations
|
|
of owners, assignment is handled the same way. Note that both ``new`` and
|
|
``malloc()``-like resource functions are considered to produce resources.
|
|
|
|
.. code-block:: c++
|
|
|
|
// Creating an owner with factory functions is checked.
|
|
gsl::owner<int*> function_that_returns_owner() { return gsl::owner<int*>(new int(42)); }
|
|
|
|
// Dynamic memory must be assigned to an owner
|
|
int* Something = new int(42); // BAD, will be caught
|
|
gsl::owner<int*> Owner = new int(42); // Good
|
|
gsl::owner<int*> Owner = new int[42]; // Good as well
|
|
|
|
// Returned owner must be assigned to an owner
|
|
int* Something = function_that_returns_owner(); // Bad, factory function
|
|
gsl::owner<int*> Owner = function_that_returns_owner(); // Good, result lands in owner
|
|
|
|
// Something not a resource or owner should not be assigned to owners
|
|
int Stack = 42;
|
|
gsl::owner<int*> Owned = &Stack; // Bad, not a resource assigned
|
|
|
|
In the case of dynamic memory as resource, only ``gsl::owner<T*>`` variables are allowed
|
|
to be deleted.
|
|
|
|
.. code-block:: c++
|
|
|
|
// Example Bad, non-owner as resource handle, will be caught.
|
|
int* NonOwner = new int(42); // First warning here, since new must land in an owner
|
|
delete NonOwner; // Second warning here, since only owners are allowed to be deleted
|
|
|
|
// Example Good, Ownership correctly stated
|
|
gsl::owner<int*> Owner = new int(42); // Good
|
|
delete Owner; // Good as well, statically enforced, that only owners get deleted
|
|
|
|
The check will furthermore ensure, that functions, that expect a ``gsl::owner<T*>`` as
|
|
argument get called with either a ``gsl::owner<T*>`` or a newly created resource.
|
|
|
|
.. code-block:: c++
|
|
|
|
void expects_owner(gsl::owner<int*> o) { delete o; }
|
|
|
|
// Bad Code
|
|
int NonOwner = 42;
|
|
expects_owner(&NonOwner); // Bad, will get caught
|
|
|
|
// Good Code
|
|
gsl::owner<int*> Owner = new int(42);
|
|
expects_owner(Owner); // Good
|
|
expects_owner(new int(42)); // Good as well, recognized created resource
|
|
|
|
// Port legacy code for better resource-safety
|
|
gsl::owner<FILE*> File = fopen("my_file.txt", "rw+");
|
|
FILE* BadFile = fopen("another_file.txt", "w"); // Bad, warned
|
|
|
|
// ... use the file
|
|
|
|
fclose(File); // Ok, File is annotated as 'owner<>'
|
|
fclose(BadFile); // BadFile is not an 'owner<>', will be warned
|
|
|
|
|
|
Options
|
|
-------
|
|
|
|
.. option:: LegacyResourceProducers
|
|
|
|
Semicolon-separated list of fully qualified names of legacy functions that create
|
|
resources but cannot introduce ``gsl::owner<>``.
|
|
Defaults to ``::malloc;::aligned_alloc;::realloc;::calloc;::fopen;::freopen;::tmpfile``.
|
|
|
|
|
|
.. option:: LegacyResourceConsumers
|
|
|
|
Semicolon-separated list of fully qualified names of legacy functions expecting
|
|
resource owners as pointer arguments but cannot introduce ``gsl::owner<>``.
|
|
Defaults to ``::free;::realloc;::freopen;::fclose``.
|
|
|
|
|
|
Limitations
|
|
-----------
|
|
|
|
Using ``gsl::owner<T*>`` in a typedef or alias is not handled correctly.
|
|
|
|
.. code-block:: c++
|
|
|
|
using heap_int = gsl::owner<int*>;
|
|
heap_int allocated = new int(42); // False positive!
|
|
|
|
The ``gsl::owner<T*>`` is declared as a templated type alias.
|
|
In template functions and classes, like in the example below, the information
|
|
of the type aliases gets lost. Therefore using ``gsl::owner<T*>`` in a heavy templated
|
|
code base might lead to false positives.
|
|
|
|
Known code constructs that do not get diagnosed correctly are:
|
|
|
|
- ``std::exchange``
|
|
- ``std::vector<gsl::owner<T*>>``
|
|
|
|
.. code-block:: c++
|
|
|
|
// This template function works as expected. Type information doesn't get lost.
|
|
template <typename T>
|
|
void delete_owner(gsl::owner<T*> owned_object) {
|
|
delete owned_object; // Everything alright
|
|
}
|
|
|
|
gsl::owner<int*> function_that_returns_owner() { return gsl::owner<int*>(new int(42)); }
|
|
|
|
// Type deduction does not work for auto variables.
|
|
// This is caught by the check and will be noted accordingly.
|
|
auto OwnedObject = function_that_returns_owner(); // Type of OwnedObject will be int*
|
|
|
|
// Problematic function template that looses the typeinformation on owner
|
|
template <typename T>
|
|
void bad_template_function(T some_object) {
|
|
// This line will trigger the warning, that a non-owner is assigned to an owner
|
|
gsl::owner<T*> new_owner = some_object;
|
|
}
|
|
|
|
// Calling the function with an owner still yields a false positive.
|
|
bad_template_function(gsl::owner<int*>(new int(42)));
|
|
|
|
|
|
// The same issue occurs with templated classes like the following.
|
|
template <typename T>
|
|
class OwnedValue {
|
|
public:
|
|
const T getValue() const { return _val; }
|
|
private:
|
|
T _val;
|
|
};
|
|
|
|
// Code, that yields a false positive.
|
|
OwnedValue<gsl::owner<int*>> Owner(new int(42)); // Type deduction yield T -> int *
|
|
// False positive, getValue returns int* and not gsl::owner<int*>
|
|
gsl::owner<int*> OwnedInt = Owner.getValue();
|
|
|
|
Another limitation of the current implementation is only the type based checking.
|
|
Suppose you have code like the following:
|
|
|
|
.. code-block:: c++
|
|
|
|
// Two owners with assigned resources
|
|
gsl::owner<int*> Owner1 = new int(42);
|
|
gsl::owner<int*> Owner2 = new int(42);
|
|
|
|
Owner2 = Owner1; // Conceptual Leak of initial resource of Owner2!
|
|
Owner1 = nullptr;
|
|
|
|
The semantic of a ``gsl::owner<T*>`` is mostly like a ``std::unique_ptr<T>``, therefore
|
|
assignment of two ``gsl::owner<T*>`` is considered a move, which requires that the
|
|
resource ``Owner2`` must have been released before the assignment.
|
|
This kind of condition could be caught in later improvements of this check with
|
|
flowsensitive analysis. Currently, the `Clang Static Analyzer` catches this bug
|
|
for dynamic memory, but not for general types of resources.
|