Шаблонная оболочка вызова с проверкой во время выполнения для обнаружения переполнения приведения во входных аргументах или возвращаемом значении - PullRequest
2 голосов
/ 07 марта 2019

Проблема

Мне нужна универсальная обертка 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'

1 Ответ

1 голос
/ 07 марта 2019

Возможное улучшение должно заключаться в том, чтобы перестать воспринимать функцию в качестве аргумента шаблона, но принимать ее в качестве параметра функции, чтобы правила дедукции 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 или любой другой тип шаблона ...

...