Проблема
Мне нужна универсальная обертка checked_cast_call<function>
для функции, которая бы проверяла во время выполнения любое приведение, вызываемое для вызова функции или для получения значения.
КакНапример, вызов следующей функции с входным буфером размером более 2 ГБ вызовет некоторые проблемы (из-за аргумента int inl
размера входа, который может быть переполнен):
int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out,
int *outl, const unsigned char *in, int inl);
Мое несовершенное решение
Для достижения этой цели, используя полезную помощь из других тем, посвященных стековому стеку, я получил следующее решение, которое, к сожалению, далеко не идеальное:
- Сначала я написал небольшой шаблон.проверять приведение во время выполнения (оно бросит
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>(...)
) - Вы будете использовать типы аргументов выведенной функцииво время выполнения проверяет приведение, не полагаясь на контейнерную оболочку
Могут быть некоторые решения, передавая функцию в качестве аргумента обертке, а не в качестве шаблона, но я хотел чисто шаблонное решение,Может быть, это просто неосуществимо или слишком запутанно?
Уважаемый читатель, заранее благодарю за полезные советы!
⟾ Частичное решение
Решение вопроса № 1благодаря @kmdreko, объявив нетипизированные аргументы шаблона с помощью 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'