Index: llvm/trunk/lib/Fuzzer/FuzzerFnAdapter.h =================================================================== --- llvm/trunk/lib/Fuzzer/FuzzerFnAdapter.h +++ llvm/trunk/lib/Fuzzer/FuzzerFnAdapter.h @@ -0,0 +1,175 @@ +//===- FuzzerAdapter.h - Arbitrary function Fuzzer adapter -------*- C++ -*===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// W A R N I N G : E X P E R I M E N T A L. +// +// Defines an adapter to fuzz functions with (almost) arbitrary signatures. +//===----------------------------------------------------------------------===// + +#ifndef LLVM_FUZZER_ADAPTER_H +#define LLVM_FUZZER_ADAPTER_H + +#include +#include + +#include +#include +#include + +namespace fuzzer { + +/// Unpacks bytes from \p Data according to \p F argument types +/// and calls the function. +/// Use to automatically adapt LLVMFuzzerTestOneInput interface to +/// a specific function. +/// Supported argument types: primitive types, std::vector. +template bool Adapt(Fn F, const uint8_t *Data, size_t Size); + +// The implementation performs several steps: +// - function argument types are obtained (Args...) +// - data is unpacked into std::tuple one by one +// - function is called with std::tuple containing arguments. +namespace impl { + +// Single argument unpacking. + +template +size_t UnpackPrimitive(const uint8_t *Data, size_t Size, T *Value) { + if (Size < sizeof(T)) + return Size; + *Value = *reinterpret_cast(Data); + return Size - sizeof(T); +} + +/// Unpacks into a given Value and returns the Size - num_consumed_bytes. +/// Return value equal to Size signals inability to unpack the data (typically +/// because there are not enough bytes). +template +size_t UnpackSingle(const uint8_t *Data, size_t Size, T *Value); + +#define UNPACK_SINGLE_PRIMITIVE(Type) \ + template <> \ + size_t UnpackSingle(const uint8_t *Data, size_t Size, Type *Value) { \ + return UnpackPrimitive(Data, Size, Value); \ + } + +UNPACK_SINGLE_PRIMITIVE(char) +UNPACK_SINGLE_PRIMITIVE(signed char) +UNPACK_SINGLE_PRIMITIVE(unsigned char) + +UNPACK_SINGLE_PRIMITIVE(short int) +UNPACK_SINGLE_PRIMITIVE(unsigned short int) + +UNPACK_SINGLE_PRIMITIVE(int) +UNPACK_SINGLE_PRIMITIVE(unsigned int) + +UNPACK_SINGLE_PRIMITIVE(long int) +UNPACK_SINGLE_PRIMITIVE(unsigned long int) + +UNPACK_SINGLE_PRIMITIVE(bool) +UNPACK_SINGLE_PRIMITIVE(wchar_t) + +UNPACK_SINGLE_PRIMITIVE(float) +UNPACK_SINGLE_PRIMITIVE(double) +UNPACK_SINGLE_PRIMITIVE(long double) + +#undef UNPACK_SINGLE_PRIMITIVE + +template <> +size_t UnpackSingle>(const uint8_t *Data, size_t Size, + std::vector *Value) { + if (Size < 1) + return Size; + size_t Len = std::min(static_cast(*Data), Size - 1); + std::vector V(Data + 1, Data + 1 + Len); + Value->swap(V); + return Size - Len - 1; +} + +// Unpacking into arbitrary tuple. + +// Recursion guard. +template +typename std::enable_if::value, bool>::type +UnpackImpl(const uint8_t *Data, size_t Size, TupleT *Tuple) { + return true; +} + +// Unpack tuple elements starting from Nth. +template +typename std::enable_if::value, bool>::type +UnpackImpl(const uint8_t *Data, size_t Size, TupleT *Tuple) { + size_t NewSize = UnpackSingle(Data, Size, &std::get(*Tuple)); + if (NewSize == Size) { + return false; + } + + return UnpackImpl(Data + (Size - NewSize), NewSize, Tuple); +} + +// Unpacks into arbitrary tuple and returns true if successful. +template +bool Unpack(const uint8_t *Data, size_t Size, std::tuple *Tuple) { + return UnpackImpl<0, std::tuple>(Data, Size, Tuple); +} + +// Helper integer sequence templates. + +template struct Seq {}; + +template struct GenSeq : GenSeq {}; + +// GenSeq::type is Seq<0, 1, ..., N-1> +template struct GenSeq<0, S...> { typedef Seq type; }; + +// Function signature introspection. + +template struct FnTraits {}; + +template +struct FnTraits { + enum { Arity = sizeof...(Args) }; + typedef std::tuple ArgsTupleT; +}; + +// Calling a function with arguments in a tuple. + +template +void ApplyImpl(Fn F, const typename FnTraits::ArgsTupleT &Params, + Seq) { + F(std::get(Params)...); +} + +template +void Apply(Fn F, const typename FnTraits::ArgsTupleT &Params) { + // S is Seq<0, ..., Arity-1> + auto S = typename GenSeq::Arity>::type(); + ApplyImpl(F, Params, S); +} + +// Unpacking data into arguments tuple of correct type and calling the function. +template +bool UnpackAndApply(Fn F, const uint8_t *Data, size_t Size) { + typename FnTraits::ArgsTupleT Tuple; + if (!Unpack(Data, Size, &Tuple)) + return false; + + Apply(F, Tuple); + return true; +} + +} // namespace impl + +template bool Adapt(Fn F, const uint8_t *Data, size_t Size) { + return impl::UnpackAndApply(F, Data, Size); +} + +} // namespace fuzzer + +#endif Index: llvm/trunk/lib/Fuzzer/test/CMakeLists.txt =================================================================== --- llvm/trunk/lib/Fuzzer/test/CMakeLists.txt +++ llvm/trunk/lib/Fuzzer/test/CMakeLists.txt @@ -27,6 +27,7 @@ RepeatedMemcmp SimpleCmpTest SimpleDictionaryTest + SimpleFnAdapterTest SimpleHashTest SimpleTest StrcmpTest @@ -90,6 +91,7 @@ add_executable(LLVMFuzzer-Unittest FuzzerUnittest.cpp + FuzzerFnAdapterUnittest.cpp $ ) Index: llvm/trunk/lib/Fuzzer/test/FuzzerFnAdapterUnittest.cpp =================================================================== --- llvm/trunk/lib/Fuzzer/test/FuzzerFnAdapterUnittest.cpp +++ llvm/trunk/lib/Fuzzer/test/FuzzerFnAdapterUnittest.cpp @@ -0,0 +1,98 @@ +#include "FuzzerFnAdapter.h" +#include "gtest/gtest-spi.h" +#include "gtest/gtest.h" + +namespace fuzzer { +namespace impl { + +template +bool Unpack(std::tuple *Tuple, std::initializer_list data) { + std::vector V(data); + return Unpack(V.data(), V.size(), Tuple); +} + +TEST(Unpack, Bool) { + std::tuple T; + EXPECT_TRUE(Unpack(&T, {1})); + EXPECT_TRUE(std::get<0>(T)); + + EXPECT_TRUE(Unpack(&T, {0})); + EXPECT_FALSE(std::get<0>(T)); + + EXPECT_FALSE(Unpack(&T, {})); +} + +TEST(Unpack, BoolBool) { + std::tuple T; + EXPECT_TRUE(Unpack(&T, {1, 0})); + EXPECT_TRUE(std::get<0>(T)); + EXPECT_FALSE(std::get<1>(T)); + + EXPECT_TRUE(Unpack(&T, {0, 1})); + EXPECT_FALSE(std::get<0>(T)); + EXPECT_TRUE(std::get<1>(T)); + + EXPECT_FALSE(Unpack(&T, {})); + EXPECT_FALSE(Unpack(&T, {10})); +} + +TEST(Unpack, BoolInt) { + std::tuple T; + EXPECT_TRUE(Unpack(&T, {1, 16, 2, 0, 0})); + EXPECT_TRUE(std::get<0>(T)); + EXPECT_EQ(528, std::get<1>(T)); + + EXPECT_FALSE(Unpack(&T, {1, 2})); +} + +TEST(Unpack, Vector) { + std::tuple> T; + const auto &V = std::get<0>(T); + + EXPECT_FALSE(Unpack(&T, {})); + + EXPECT_TRUE(Unpack(&T, {0})); + EXPECT_EQ(0ul, V.size()); + + EXPECT_TRUE(Unpack(&T, {0, 1, 2, 3})); + EXPECT_EQ(0ul, V.size()); + + EXPECT_TRUE(Unpack(&T, {2})); + EXPECT_EQ(0ul, V.size()); + + EXPECT_TRUE(Unpack(&T, {2, 3})); + EXPECT_EQ(1ul, V.size()); + EXPECT_EQ(3, V[0]); + + EXPECT_TRUE(Unpack(&T, {2, 9, 8})); + EXPECT_EQ(2ul, V.size()); + EXPECT_EQ(9, V[0]); + EXPECT_EQ(8, V[1]); +} + +template +bool UnpackAndApply(Fn F, std::initializer_list Data) { + std::vector V(Data); + return UnpackAndApply(F, V.data(), V.size()); +} + +static void fnBool(bool b) { EXPECT_TRUE(b); } + +TEST(Apply, Bool) { + EXPECT_FALSE(UnpackAndApply(fnBool, {})); + EXPECT_TRUE(UnpackAndApply(fnBool, {1})); + EXPECT_NONFATAL_FAILURE(UnpackAndApply(fnBool, {0}), + "Actual: false\nExpected: true"); +} + +static void fnInt(int i) { EXPECT_EQ(42, i); } + +TEST(Apply, Int) { + EXPECT_FALSE(UnpackAndApply(fnInt, {})); + EXPECT_TRUE(UnpackAndApply(fnInt, {42, 0, 0, 0})); + EXPECT_NONFATAL_FAILURE(UnpackAndApply(fnInt, {10, 0, 0, 0}), + "Actual: 10\nExpected: 42"); +} + +} // namespace impl +} // namespace fuzzer Index: llvm/trunk/lib/Fuzzer/test/SimpleFnAdapterTest.cpp =================================================================== --- llvm/trunk/lib/Fuzzer/test/SimpleFnAdapterTest.cpp +++ llvm/trunk/lib/Fuzzer/test/SimpleFnAdapterTest.cpp @@ -0,0 +1,21 @@ +// Simple test for a fuzzer Fn adapter. The fuzzer has to find two non-empty +// vectors with the same content. + +#include +#include + +#include "FuzzerFnAdapter.h" + +static void TestFn(std::vector V1, std::vector V2) { + if (V1.size() > 0 && V1 == V2) { + std::cout << "BINGO; Found the target, exiting\n"; + exit(0); + } +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + fuzzer::Adapt(TestFn, Data, Size); + return 0; +} + + Index: llvm/trunk/lib/Fuzzer/test/fuzzer-fn-adapter.test =================================================================== --- llvm/trunk/lib/Fuzzer/test/fuzzer-fn-adapter.test +++ llvm/trunk/lib/Fuzzer/test/fuzzer-fn-adapter.test @@ -0,0 +1,3 @@ +RUN: LLVMFuzzer-SimpleFnAdapterTest 2>&1 | FileCheck %s + +CHECK: BINGO