我需要一个函数的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;
}
在基本测试(相当于本文中的示例测试)中,常见(非错误)路径上的开销是最小的,并且基本上是运行时检查的输入参数的一个额外的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>(...)
)可能有一些解决方案将函数作为参数传递给包装器,而不是作为模板,但我想要一个纯模板解决方案。也许这根本不可行,或者太复杂了?
亲爱的读者,请提前感谢您提供任何有用的提示!
通过declaring non-type template arguments with auto的@kmdreko解决问题#1:
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
或任何其他模板类型的东西......