未经检查的访问禁止

huangapple go评论121阅读模式
英文:

Unchecked access ban

问题

尝试禁止不安全的访问操作(比如指针的operator*)并替换它们为自定义函数的人是否存在?目的是将未定义行为(UB)替换为异常和/或在发生无效访问时提供更多调试信息(如文件、行或堆栈跟踪)。例如,指针的自定义解引用函数可以如下所示:

template<class T> T& value(T* ptr, std::source_location sl = std::source_location::current())
{
    if (ptr) return *ptr;
    throw std::runtime_error(std::format("bad access in {}:{}:{}", sl.file_name(), sl.line(), sl.column()));
    // 错误日志也可以打印。它可能包含堆栈跟踪。
}

类似的方法也可以用于智能指针、可选类型、变体、vector::front 等。未检查的访问操作可以通过像clang TIDY这样的静态分析器来强制执行。此外,unsafe() 函数可以用于某些性能关键部分 - 它将在调试构建中终止对无效访问的检查,但在发布构建中不会检查有效性。

是否有人能看到这种方案的问题?编译时间?性能?冗长?或者也许有更好的解决方案?

编辑:我将尝试澄清一些事情:

  1. 我不打算在这里修复所有内存问题,只是无检查的访问 - 空指针解引用,std::nullopt解引用,一些越界访问问题等(我猜我不应该称它为“不安全访问”)。
  2. 我的代码库有数百万行代码。不可能用不同的语言重写它。
  3. 检查应该在发布构建中运行,除非使用unsafe() 函数而不是value()
  4. 我更喜欢期望(expectation)而不是崩溃,因为系统可以从异常中恢复 - 可以删除触发错误的客户端。
  5. 即使操作系统在空指针解引用时崩溃,编译器优化可能会重写代码,导致一些不可预测的行为发生。
  6. std::optionalnullopt解引用时不会崩溃 - 它将导致难以预测的问题。
英文:

Anyone tried to ban unsafe access operations (like operator* for pointers) from the codebase and replace them with custom functions? The purpose would be to replace UB with an exception and/or to provide more debug information (like file, line or stack-trace) when invalid access happens. For example, custom de-referencing function for pointers could look like this:

template&lt;class T&gt; T&amp; value(T* ptr, std::source_location sl = std::source_location::current())
{
    If (ptr) return *ptr;
    throw std::runtime_error(std::format(“bad access in {}:{}:{}”, sl.file_name(), sl.line(), sl.column()));
    // error log could also be printed. It could contain stacktrace.
}

Something similar could be implemented for smart pointers, optionals, variants, vector::front etc.
Unchecked access ban could then be enforced by static analyzer like clang TIDY.
Additionally the unsafe() function could be provided for use in some performance critical parts - it would terminate on invalid access in debug build, but wouldn't check validity in release build.

Can anyone see any problem with this scheme? Compilation times? Performance? Verbosity? Or maybe there is a better solution?

EDIT: I will try to clarify few things:

  1. I didn’t intend to fix all memory issues here, only unchecked access – nullptr dereference, std::nullopt dereference, some index out of bounds issues etc. (I guess I shouldn’t call it “unsafe access”)
  2. My code base has millions of LoC. Rewriting it in a different language is impossible.
  3. Checks should be run in release build as well, unless unsafe() function is used instead of value().
  4. I prefer expectation to crash because system could recover from exception - It is possible to remove the client that triggered the bug.
  5. Even if operating system would crash the app on nullptr dereference the compiler optimizations could rewrite the code, so that some unpredictable behavior would happen instead.
  6. std::optional wont crash on nullopt dereference - it will cause hard to predict problems.

答案1

得分: 3

我看到你的方法最大的问题是它并不是很有用。等于 nullptr 的指针是很容易出现的错误。更常见、更难追踪的错误是其他无效的指针。

例如(为了示例而编写的糟糕代码,仅用于示例,它确实存在一个错误!):

struct foo { int value = 42; };

struct bar { foo* f; };

int main() {
   bar b;
   {
       foo f; 
       b.f = &f;
   }
   return b.f->value; // 未定义的!解引用悬挂指针
}

b.f 是一个悬挂指针。除了智能指针,没有一种干净的方法可以在指向的对象生命周期结束时将 b.f 设置为 nullptr。无法确定 b.f 是否为有效指针。走出困境的方法是首先使用智能指针来避免进入这种情况。

你可以像你的 value 函数一样使用一个函数,将文件和行号的调试日志添加到捕获 nullptr 解引用(对于智能指针,它也会捕获大多数无效智能指针的解引用)。然而,你需要考虑不应该总是使用它。它会增加通常不需要的开销。例如,对于智能指针 ptr

for (const auto& element : some_container) {
    e(*ptr);
}

你不希望在每次迭代中检查指针是否有效。

英文:

The biggest issue I see with your approach is that it is not that useful. A pointer that equals nullptr is the easy bug. The more common and more difficult to track down bugs is other invalid pointers.

For example (poor code, just for the sake of the example it does have a bug!):

struct foo { int value = 42; };

struct bar { foo* f; };

int main() {
   bar b;
   {
       foo f; 
       b.f = &amp;f;
   }
   return b.f-&gt;value; // UNDEFINED ! Dereferencing a dangling pointer
}

b.f is a dangling pointer. Smart pointers aside, there is no clean way to set b.f to nullptr when the object it points to ends its lifetime. It is not possible to know if b.f is a valid pointer. The way out is to not get into this situation in the first place by using smart pointers.

You can use a function like your value to add debug logs with file, and line number to catch nullptr dereferences (and with smart pointers is will also catch most dereference of invalid smart pointers). However, you need to consider that you should not use it always. It adds overhead which is often unwanted. For example with a smart pointer ptr:

  for (const auto&amp; element : some_container) {
      e(*ptr);
  }

You do not want to check in every iteration that the pointer is valid.

huangapple
  • 本文由 发表于 2023年3月7日 02:15:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/75654414.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定