mercredi 3 janvier 2018

C++ calling function with unknown type of arguments?

I am trying to achieve the same as RapidCheck: call any Callable no matter its arguments.

Here is an example from RapidCheck:

#include <rapidcheck.h>

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

  return 0;
}

  • Unlike RapidCheck, which uses random data to call the Callable, I would like to use a source of data. In particular, I am casting a uint8_t array to whatever type I need. (See the example below.)
  • I am okay with using C++17 but would prefer C++11. I currently only have a C++17 example.
  • My example below works for an arbitrary number of arguments to a Callable but not for arbitrary types of argument. And certainly not for mixed types of arguments.
  • I am doing this, so that I can use the awesome API of RapidCheck with libFuzzer from LLVM. Similar to my previous approach here (Example).

What I have so far with some comments (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: Though 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;
}

Aucun commentaire:

Enregistrer un commentaire