c++ - 模板化运行时检查调用包装器以检测输入参数或返回值中的转换溢出

标签 c++ templates c++17 variadic-templates auto

问题

我需要一个 checked_cast_call<function>函数的通用包装器,它将在运行时检查涉及调用函数或获取值的任何强制转换。

例如,使用大于 2GB 的输入缓冲区调用以下函数会导致一些问题(因为 int inl 输入大小参数会溢出):

int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, const unsigned char *in, int inl);

我不完美的解决方案

为了实现这一目标,借助来自其他 stackoverflow 主题的有见地的帮助,我最终得到了以下解决方案,不幸的是,它远非完美:

  • 首先,我编写了一个小模板来运行时检查强制转换(如果强制转换溢出,它将抛出 std::runtime_error)。
#include <stdexcept>
#include <type_traits>

/**
 * Runtime-checked cast to a target type.
 * @throw std::runtime_error If the cast overflowed (or underflowed).
 */
template <class Target, class Source>
Target inline checked_cast(Source v)
{
    if constexpr (std::is_pointer<Source>::value) {
        return v;
    } else if constexpr (std::is_same<Target, Source>::value) {
        return v;
    else {
        const auto r = static_cast<Target>(v);
        if (static_cast<Source>(r) != v) {
            throw std::runtime_error(std::string("cast failed: ") + std::string(__PRETTY_FUNCTION__));
        }
        return r;
    }
}
  • 然后,一个小的模板容器来保存一个类型,并允许对可能的另一种类型进行运行时检查的转换。这个容器可以用来保存函数的返回值,但它也可以用来保存函数的每个输入参数,依赖于operator T ()。提供经过运行时检查的转换值:
/**
 * Container holding a type, and allowing to return a cast runtime-checked casted value.
 * @example
 *     const size_t my_integer = foo();
 *     const checked_cast_call_container c(my_integer);
 *     int a = static_cast<int>(c);
 */
template <typename T>
class checked_cast_call_container {
public:
    inline checked_cast_call_container(T&& result)
        : _result(std::move(result))
    {
    }

    template <typename U>
    inline operator U() const
    {
        return checked_cast<U>(_result);
    }

private:
    const T _result;
};
  • 最后的包装器,采用 decltype函数指针和函数指针本身,用我们的容器扩展打包参数,并将结果也放入容器中:
/**
 * Wrapped call to a function, with runtime-checked casted input and output values.
 * @example checked_cast_call<decltype(&my_function), &my_function>(str, 1, size, output)
 */
template <typename Fn, Fn fn, typename... Args>
checked_cast_call_container<typename std::result_of<Fn(Args...)>::type>
checked_cast_call(Args... args)
{
    return checked_cast_call_container(fn(checked_cast_call_container(std::forward<Args>(args))...));
}
  • 样本测试:
static char my_write(void* ptr, char size, char nmemb, FILE* stream)
{
    return fwrite(ptr, size, nmemb, stream);
}

int main(int argc, char** argv)
{
    // Input overflow: input argument nmemb is larger than 127
    try {
        char str[256] = "Hello!\n";
        volatile size_t size = sizeof(str);
        const char b = checked_cast_call<decltype(&my_write), &my_write>(str, 1, size, stdout);
        (void)b;
    } catch (const std::runtime_error& e) {
        std::cout << e.what() << "\n";
    }

    return 0;
}

关于性能影响的说明

在基本测试中(相当于本文中的示例测试),公共(public)(非错误)路径上的开销是最小的,基本上是一个额外的 cmp + jne对于运行时检查的输入参数。 (注意:错误路径的附加代码,包括throw下面反汇编代码中未显示的冷路径)

--- old.S   2019-03-11 11:14:25.847240916 +0100
+++ new.S   2019-03-11 11:14:27.087238775 +0100
@@ -3 +3 @@
-lea    0x10(%rsp),%rbx
+lea    0x10(%rsp),%rdi
@@ -6 +5,0 @@
-mov    %rbx,%rdi
@@ -9 +8,4 @@
-mov    0x8(%rsp),%rax
+mov    0x8(%rsp),%rdx
+movsbq %dl,%rax
+cmp    %rdx,%rax
+jne    0xXXXXXX <_Z5test3v+82>
@@ -11 +13 @@
-movsbq %al,%rdx
+lea    0x10(%rsp),%rdi
@@ -13 +14,0 @@
-mov    %rbx,%rdi

问题

可以改进这个包装器,以便:

  • 你只需要一个模板参数(函数)而不是 decltype函数的(即直接 checked_cast_call<&my_write>(...) )
  • 您将使用推断的函数参数类型来运行时检查转换,而不依赖于容器包装器

通过将函数作为参数传递给包装器可能有一些解决方案,而不是作为模板,但我想要一个纯模板解决方案。也许这根本不可行,或者太复杂了?

亲爱的读者,提前感谢您提供任何有用的提示!

⟾ 部分解决方案

问题 #1 的解决方案感谢@kmdreko,作者 declaring non-type template arguments with auto :

template <auto fn, typename... Args>
auto checked_cast_call(Args... args)
{
    return checked_cast_call_container(fn(checked_cast_call_container(std::forward<Args>(args))...));
}
const char b = checked_cast_call<&my_write>(str, 1, size, stdout);

允许直接 sed -e 's/my_write/checked_cast_call<&my_write>/g'

最佳答案

一个可能的改进应该是停止将函数作为模板参数,而是将其作为函数参数,这样 C++17 推导规则将能够猜测类型,您将不需要提供模板参数。

这是一个快速而肮脏的版本:

template <class F, class... Args>
decltype(auto) checked_cast_call(F&& f, Args... args)
{
      return checked_cast_call_container(std::forward<F>(f)(checked_cast_call_container(std::forward<Args>(args))...));
}

(灵感来自 https://en.cppreference.com/w/cpp/utility/functional/invoke 的“可能的实现”部分)

它似乎与您的代码具有相同的行为,现在我需要检查我们是否有适当的内联(至少和您的一样好)。可能还有一些 cv 限定类型的细节,也许我们需要一个 std::decay 或任何其他模板类型的东西......

关于c++ - 模板化运行时检查调用包装器以检测输入参数或返回值中的转换溢出,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55039334/

相关文章:

c++ - OpenCV 让用户选择打开图像

C++11:在源代码中将 lambda 转换为函数对象

c++ - Linux 多线程应用程序中的中断生成 SIGSEGV

c++ - 基于策略的模板设计 : How to access certain policies of the class?

用于深层次继承的 C++ 模板单例基类

c++ - "temporary of type ' A ' has protected destructor", 但它的类型是 B

c++ - 继承类模板的参数化构造函数

c++ - 强制 GCC 通知共享库中 undefined reference

javascript - 我的 django 模板 bool 变量在 javascript 中没有按预期工作

c++ - 在析构函数和后续析构函数中引发异常