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