cppcoreguidelines-owning-memory

此检查实现了 gsl::owner<T*> 的基于类型的语义,允许对使用原始指针处理资源(如动态内存)的代码进行静态分析,但不会引入 RAII 概念。

此检查实现了来自 C++ 核心准则的 I.11C.33R.3GSL.Viewsgsl::owner<T*> 的定义非常简单

namespace gsl { template <typename T> owner = T; }

因此,即使不使用 准则支持库 的实现,也很容易引入所有者。

所有检查都是纯粹基于类型的,目前还没有流敏感性。

以下示例将演示所有者的正确和不正确初始化,分配的处理方式相同。注意 newmalloc() 之类的资源函数都被认为会产生资源。

// 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

在动态内存作为资源的情况下,只有 gsl::owner<T*> 变量可以被删除。

// 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

此外,检查将确保,期望 gsl::owner<T*> 作为参数的函数,要么使用 gsl::owner<T*>,要么使用新创建的资源进行调用。

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

选项

LegacyResourceProducers

以分号分隔的旧式函数的完全限定名列表,这些函数创建资源但不能引入 gsl::owner<>。默认值为 ::malloc;::aligned_alloc;::realloc;::calloc;::fopen;::freopen;::tmpfile

LegacyResourceConsumers

以分号分隔的旧式函数的完全限定名列表,这些函数期望资源所有者作为指针参数,但不能引入 gsl::owner<>。默认值为 ::free;::realloc;::freopen;::fclose

局限性

在 typedef 或别名中使用 gsl::owner<T*> 处理不正确。

using heap_int = gsl::owner<int*>;
heap_int allocated = new int(42); // False positive!

gsl::owner<T*> 被声明为模板类型别名。在模板函数和类中,如以下示例所示,类型别名的信息丢失。因此,在大量模板化的代码库中使用 gsl::owner<T*> 可能会导致误报。

已知无法正确诊断的代码结构:

  • std::exchange

  • std::vector<gsl::owner<T*>>

// 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();

当前实现的另一个局限性是仅基于类型的检查。假设你拥有以下代码

// 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;

gsl::owner<T*> 的语义与 std::unique_ptr<T> 非常类似,因此两个 gsl::owner<T*> 的赋值被视为移动,这需要在赋值之前释放资源 Owner2。这种类型的条件可以在该检查的后续改进中通过流敏感分析来捕获。目前,Clang 静态分析器 会捕获动态内存的此错误,但不会捕获一般类型的资源的此错误。