C ++调用具有未知类型参数的函数?

时间:2018-01-03 20:02:52

标签: c++11 templates c++17 fuzzing

我正在努力实现与RapidCheck相同的效果:无论其参数如何,都可以调用任何 Callable

这是RapidCheck的一个例子:

#include <rapidcheck.h>

int main() {
  rc::check("Addition is commutative.",
            [](int a, int b) {
              RC_ASSERT(a + b == b + a);
            });

  return 0;
}
  • 与RapidCheck 不同,它使用随机数据来调用Callable,I 我想使用数据源。特别是,我正在为我需要的任何类型转换uint8_t数组。 (参见下面的例子。)
  • 我可以使用C ++ 17,但更喜欢C ++ 11。我目前只有一个C ++ 17示例。
  • 我的示例适用于Callable的任意数量的参数,但不适用于任意类型的参数。当然也不适用于混合类型的论点。
  • 我这样做,因此我可以使用来自LLVM的 libFuzzer 的RapidCheck的超棒API。与我之前的方法hereExample)类似。

到目前为止,我发表了一些评论(online):

// Compiles with Clang(trunk)/GCC(7.2) using -std=c++17!

#include <cassert>
#include <functional>
#include <iostream>
#include <mutex>
#include <string>
#include <type_traits>

/** Provides access to a uint8_t array as specific types.
*
* Fulfills thus the LLVMFuzzerTestOneInput-interface, which uses
* (uint8_t *Data, size_t Size) as input.
*/
class RawQueue {
public:
  RawQueue(uint8_t *Data, size_t Size)
      : data_(Data), size_(Size / sizeof(uint8_t)), index_(0){};

  /** Takes one element of type T from queue.
  *
  * Throws if empty.
  *
  * NOTE: Little endianess means that uint8_t {1, 0, 0, 0} == int {1}.
  */
  template <typename T> T pop() {
    assert(data_);
    std::scoped_lock<std::mutex> lock(data_mutex_);

    const size_t new_index = index_ + sizeof(T) / sizeof(uint8_t);
    if (new_index > size_) {
      std::runtime_error(
          "Queue depleted!"); // TODO: Thou shall not use plain runtime_error!
    }

    const T val = *reinterpret_cast<const T *>(&(data_[index_]));
    index_ = new_index;

    return val;
  }

private:
  const uint8_t *data_; ///< Warning: Ownership resides outside of RawQueue.
  std::mutex data_mutex_;
  const size_t size_;
  size_t index_;
};

template <> std::string RawQueue::pop<std::string>() {
  return std::string("Left-out for brevity.");
};

template <typename T, typename F, typename... Args>
decltype(auto) call(RawQueue *Data, F &&f, Args &&... args) {
  if constexpr (std::is_invocable<F, Args...>::value) {
    return std::invoke(f, args...);
  } else {
    assert(Data);
    // Is there a way to deduce T automatically and for each argument
    // independently?
    auto val = Data->pop<T>();
    return call<T>(Data, f, val, args...);
  }
}

int adder(int a, int b, int c) { return a + b + c; }
std::string greeter(const std::string &name) { return "Hello, " + name + "!"; }

int mixed_arguments(int i, float f, const std::string &s) { return 42; }

int main() {
  constexpr size_t Size = 16;
  uint8_t Data[Size] = {1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0};
  RawQueue data(Data, Size);

  auto res_int = call<int>(&data, adder);
  std::cout << "Integer result: " << res_int << std::endl;

  auto res = call<std::string>(&data, greeter);
  std::cout << "String result: " << res << std::endl;

  // Impossible with current approach:
  // std::cout << "Mixed-types: " << call(&data, mixed_arguments) << std::endl;

  return 0;
}

2 个答案:

答案 0 :(得分:0)

您可以使用variadic templates

如果要在运行时调用具有任意参数的任意签名的任意函数,则应考虑使用libffi(一个知道您的ABI和调用约定的外部函数接口库)。

答案 1 :(得分:0)

我找到了使用callable.hpp的解决方案。仍然欢迎不依赖外部图书馆的答案!

相关的新增内容如下:

constexpr size_t pos = sizeof...(args);
typedef typename callable_traits<F>::template argument_type<pos> T;
auto val = Data->pop<T>();

完整示例:

#include <cassert>
#include <functional>
#include <iostream>
#include <mutex>
#include <string>
#include <type_traits>

#include "external/callable/callable.hpp"

class RawQueue {
public:
  RawQueue(uint8_t *Data, size_t Size)
      : data_(Data), size_(Size / sizeof(uint8_t)), index_(0){};

  /** Takes one element of type T from queue.
  *
  * Throws if empty.
  *
  * NOTE: Little endianess means that uint8_t {1, 0, 0, 0} == int {1}.
  */
  template <typename T> T pop() {
    assert(data_);
    std::scoped_lock<std::mutex> lock(data_mutex_);

    const size_t new_index = index_ + sizeof(T) / sizeof(uint8_t);
    if (new_index > size_) {
      // TODO: Thou shall not use plain runtime_error!
      std::runtime_error("Queue depleted!");
    }

    const T val = *reinterpret_cast<const T *>(&(data_[index_]));
    index_ = new_index;

    return val;
  }

private:
  const uint8_t *data_; ///< Warning: Ownership resides outside of RawQueue.
  std::mutex data_mutex_;
  const size_t size_;
  size_t index_;
};

template <> std::string RawQueue::pop<std::string>() {
  std::scoped_lock<std::mutex> lock(data_mutex_);
  assert(data_);
  assert(index_ < size_);
  size_t string_length = data_[index_]; // Up-to 255 ought to be enough.
  const size_t new_index =
      index_ + string_length + 1; // +1 b/c first value is length of string.

  if (new_index > size_) {
    // TODO: Thou shall not use plain runtime_error!
    std::runtime_error("Queue depleted!");
  }

  const std::string val(reinterpret_cast<const char *>(&(data_[index_ + 1])),
                        string_length);
  index_ = new_index;
  return val;
};

template <typename F, typename... Args>
decltype(auto) call(RawQueue *Data, F &&f, Args &&... args) {
  if constexpr (std::is_invocable<F, Args...>::value) {
    return std::invoke(f, args...);
  } else {
    assert(Data);
    constexpr size_t n_already = sizeof...(args);
    constexpr size_t n_needed = callable_traits<F>::argc;
    static_assert(n_needed >= n_already, "Too many arguments!");
    constexpr size_t pos = n_already;
    typedef typename callable_traits<F>::template argument_type<pos> T;
    auto val = Data->pop<T>();
    return call(Data, f, args..., val);
  }
}

int adder(int a, int b, int c) { return a + b + c; }

std::string greeter(std::string a) { return "hello " + a; };

void mixed(int i, float f, std::string s) {
  std::cout << "Mixed: " << i << ", " << f << ", " << s << std::endl;
}

int main() {
  constexpr size_t Size = 28;
  // clang-format off
  uint8_t Data[Size] = {
      3, 'A', 'd', 'a',
      1, 0, 0, 0,
      2, 0, 0, 0,
      4, 0, 0, 0,
      42, 0, 0, 0,
      0xDA, 0x0F, 0x49, 0x40, // 3.141...
      3, 'P', 'i', '!'};
  // clang-format on
  RawQueue data(Data, Size);

  std::cout << "String: " << call(&data, greeter) << std::endl;
  std::cout << "Integers: " << call(&data, adder) << std::endl;
  call(&data, mixed);
  call(&data, []() { std::cout << "Nothing to do!" << std::endl; });

  return 0;
}

打印:

String: hello Ada
Integers: 7
Mixed: 42, 3.14159, Pi!
Nothing to do!