summaryrefslogtreecommitdiffstats
path: root/src/fmt/test/fuzzing
diff options
context:
space:
mode:
Diffstat (limited to 'src/fmt/test/fuzzing')
-rw-r--r--src/fmt/test/fuzzing/.gitignore3
-rw-r--r--src/fmt/test/fuzzing/CMakeLists.txt30
-rw-r--r--src/fmt/test/fuzzing/README.md25
-rwxr-xr-xsrc/fmt/test/fuzzing/build.sh90
-rw-r--r--src/fmt/test/fuzzing/chrono-duration.cc136
-rw-r--r--src/fmt/test/fuzzing/chrono-timepoint.cc32
-rw-r--r--src/fmt/test/fuzzing/float.cc39
-rw-r--r--src/fmt/test/fuzzing/fuzzer-common.h77
-rw-r--r--src/fmt/test/fuzzing/main.cc22
-rw-r--r--src/fmt/test/fuzzing/named-arg.cc102
-rw-r--r--src/fmt/test/fuzzing/one-arg.cc92
-rw-r--r--src/fmt/test/fuzzing/two-args.cc106
12 files changed, 754 insertions, 0 deletions
diff --git a/src/fmt/test/fuzzing/.gitignore b/src/fmt/test/fuzzing/.gitignore
new file mode 100644
index 000000000..ea4104026
--- /dev/null
+++ b/src/fmt/test/fuzzing/.gitignore
@@ -0,0 +1,3 @@
+# ignore artifacts from the build.sh script
+build-*/
+
diff --git a/src/fmt/test/fuzzing/CMakeLists.txt b/src/fmt/test/fuzzing/CMakeLists.txt
new file mode 100644
index 000000000..0280c5cdf
--- /dev/null
+++ b/src/fmt/test/fuzzing/CMakeLists.txt
@@ -0,0 +1,30 @@
+# Copyright (c) 2019, Paul Dreik
+# License: see LICENSE.rst in the fmt root directory
+
+# Link in the main function. Useful for reproducing, kcov, gdb, afl, valgrind.
+# (Note that libFuzzer can also reproduce, just pass it the files.)
+option(FMT_FUZZ_LINKMAIN "Enables the reproduce mode, instead of libFuzzer" On)
+
+# For oss-fuzz - insert $LIB_FUZZING_ENGINE into the link flags, but only for
+# the fuzz targets, otherwise the CMake configuration step fails.
+set(FMT_FUZZ_LDFLAGS "" CACHE STRING "LDFLAGS for the fuzz targets")
+
+# Adds a binary for reproducing, i.e. no fuzzing, just enables replaying data
+# through the fuzzers.
+function(add_fuzzer source)
+ get_filename_component(basename ${source} NAME_WE)
+ set(name ${basename}-fuzzer)
+ add_executable(${name} ${source} fuzzer-common.h)
+ if (FMT_FUZZ_LINKMAIN)
+ target_sources(${name} PRIVATE main.cc)
+ endif ()
+ target_link_libraries(${name} PRIVATE fmt)
+ if (FMT_FUZZ_LDFLAGS)
+ target_link_libraries(${name} PRIVATE ${FMT_FUZZ_LDFLAGS})
+ endif ()
+ target_compile_features(${name} PRIVATE cxx_generic_lambdas)
+endfunction()
+
+foreach (source chrono-duration.cc chrono-timepoint.cc float.cc named-arg.cc one-arg.cc two-args.cc)
+ add_fuzzer(${source})
+endforeach ()
diff --git a/src/fmt/test/fuzzing/README.md b/src/fmt/test/fuzzing/README.md
new file mode 100644
index 000000000..bb3d0e04f
--- /dev/null
+++ b/src/fmt/test/fuzzing/README.md
@@ -0,0 +1,25 @@
+# Running the fuzzers locally
+
+There is a [helper script](build.sh) to build the fuzzers, which has only been
+tested on Debian and Ubuntu linux so far. There should be no problems fuzzing on
+Windows (using clang>=8) or on Mac, but the script will probably not work out of
+the box.
+
+Something along
+```sh
+mkdir build
+cd build
+export CXX=clang++
+export CXXFLAGS="-fsanitize=fuzzer-no-link -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION= -g"
+cmake .. -DFMT_SAFE_DURATION_CAST=On -DFMT_FUZZ=On -DFMT_FUZZ_LINKMAIN=Off -DFMT_FUZZ_LDFLAGS="-fsanitize=fuzzer"
+cmake --build .
+```
+should work to build the fuzzers for all platforms which clang supports.
+
+Execute a fuzzer with for instance
+```sh
+cd build
+export UBSAN_OPTIONS=halt_on_error=1
+mkdir out_chrono
+bin/fuzzer_chrono_duration out_chrono
+```
diff --git a/src/fmt/test/fuzzing/build.sh b/src/fmt/test/fuzzing/build.sh
new file mode 100755
index 000000000..4497b62c1
--- /dev/null
+++ b/src/fmt/test/fuzzing/build.sh
@@ -0,0 +1,90 @@
+#!/bin/sh
+#
+# Creates fuzzer builds of various kinds
+# - oss-fuzz emulated mode (makes sure a simulated invocation by oss-fuzz works)
+# - libFuzzer build (you will need clang)
+# - afl build (you will need afl)
+#
+#
+# Copyright (c) 2019 Paul Dreik
+#
+# For the license information refer to format.h.
+
+set -e
+me=$(basename $0)
+root=$(readlink -f "$(dirname "$0")/../..")
+
+
+echo $me: root=$root
+
+here=$(pwd)
+
+CXXFLAGSALL="-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION= -g"
+CMAKEFLAGSALL="$root -GNinja -DCMAKE_BUILD_TYPE=Debug -DFMT_DOC=Off -DFMT_TEST=Off -DFMT_FUZZ=On -DCMAKE_CXX_STANDARD=17"
+
+CLANG=clang++-11
+
+# For performance analysis of the fuzzers.
+builddir=$here/build-fuzzers-perfanalysis
+mkdir -p $builddir
+cd $builddir
+CXX="ccache g++" CXXFLAGS="$CXXFLAGSALL -g" cmake \
+$CMAKEFLAGSALL \
+-DFMT_FUZZ_LINKMAIN=On \
+-DCMAKE_BUILD_TYPE=Release
+
+cmake --build $builddir
+
+# Builds the fuzzers as oss-fuzz does.
+builddir=$here/build-fuzzers-ossfuzz
+mkdir -p $builddir
+cd $builddir
+CXX=$CLANG \
+CXXFLAGS="$CXXFLAGSALL -fsanitize=fuzzer-no-link" cmake \
+cmake $CMAKEFLAGSALL \
+-DFMT_FUZZ_LINKMAIN=Off \
+-DFMT_FUZZ_LDFLAGS="-fsanitize=fuzzer"
+
+cmake --build $builddir
+
+
+# Builds fuzzers for local fuzzing with libfuzzer with asan+usan.
+builddir=$here/build-fuzzers-libfuzzer
+mkdir -p $builddir
+cd $builddir
+CXX=$CLANG \
+CXXFLAGS="$CXXFLAGSALL -fsanitize=fuzzer-no-link,address,undefined" cmake \
+cmake $CMAKEFLAGSALL \
+-DFMT_FUZZ_LINKMAIN=Off \
+-DFMT_FUZZ_LDFLAGS="-fsanitize=fuzzer"
+
+cmake --build $builddir
+
+# Builds a fast fuzzer for making coverage fast.
+builddir=$here/build-fuzzers-fast
+mkdir -p $builddir
+cd $builddir
+CXX=$CLANG \
+CXXFLAGS="$CXXFLAGSALL -fsanitize=fuzzer-no-link -O3" cmake \
+cmake $CMAKEFLAGSALL \
+-DFMT_FUZZ_LINKMAIN=Off \
+-DFMT_FUZZ_LDFLAGS="-fsanitize=fuzzer" \
+ -DCMAKE_BUILD_TYPE=Release
+
+cmake --build $builddir
+
+
+# Builds fuzzers for local fuzzing with afl.
+builddir=$here/build-fuzzers-afl
+mkdir -p $builddir
+cd $builddir
+CXX="afl-g++" \
+CXXFLAGS="$CXXFLAGSALL -fsanitize=address,undefined" \
+cmake $CMAKEFLAGSALL \
+-DFMT_FUZZ_LINKMAIN=On
+
+cmake --build $builddir
+
+
+echo $me: all good
+
diff --git a/src/fmt/test/fuzzing/chrono-duration.cc b/src/fmt/test/fuzzing/chrono-duration.cc
new file mode 100644
index 000000000..d66068d9c
--- /dev/null
+++ b/src/fmt/test/fuzzing/chrono-duration.cc
@@ -0,0 +1,136 @@
+// Copyright (c) 2019, Paul Dreik
+// For the license information refer to format.h.
+
+#include <fmt/chrono.h>
+
+#include <cstdint>
+
+#include "fuzzer-common.h"
+
+template <typename Period, typename Rep>
+void invoke_inner(fmt::string_view format_str, Rep rep) {
+ auto value = std::chrono::duration<Rep, Period>(rep);
+ try {
+#if FMT_FUZZ_FORMAT_TO_STRING
+ std::string message = fmt::format(format_str, value);
+#else
+ auto buf = fmt::memory_buffer();
+ fmt::format_to(std::back_inserter(buf), format_str, value);
+#endif
+ } catch (std::exception&) {
+ }
+}
+
+// Rep is a duration's representation type.
+template <typename Rep>
+void invoke_outer(const uint8_t* data, size_t size, int period) {
+ // Always use a fixed location of the data.
+ static_assert(sizeof(Rep) <= fixed_size, "fixed size is too small");
+ if (size <= fixed_size + 1) return;
+
+ const Rep rep = assign_from_buf<Rep>(data);
+ data += fixed_size;
+ size -= fixed_size;
+
+ // data is already allocated separately in libFuzzer so reading past the end
+ // will most likely be detected anyway.
+ const auto format_str = fmt::string_view(as_chars(data), size);
+
+ // yocto, zepto, zetta and yotta are not handled.
+ switch (period) {
+ case 1:
+ invoke_inner<std::atto>(format_str, rep);
+ break;
+ case 2:
+ invoke_inner<std::femto>(format_str, rep);
+ break;
+ case 3:
+ invoke_inner<std::pico>(format_str, rep);
+ break;
+ case 4:
+ invoke_inner<std::nano>(format_str, rep);
+ break;
+ case 5:
+ invoke_inner<std::micro>(format_str, rep);
+ break;
+ case 6:
+ invoke_inner<std::milli>(format_str, rep);
+ break;
+ case 7:
+ invoke_inner<std::centi>(format_str, rep);
+ break;
+ case 8:
+ invoke_inner<std::deci>(format_str, rep);
+ break;
+ case 9:
+ invoke_inner<std::deca>(format_str, rep);
+ break;
+ case 10:
+ invoke_inner<std::kilo>(format_str, rep);
+ break;
+ case 11:
+ invoke_inner<std::mega>(format_str, rep);
+ break;
+ case 12:
+ invoke_inner<std::giga>(format_str, rep);
+ break;
+ case 13:
+ invoke_inner<std::tera>(format_str, rep);
+ break;
+ case 14:
+ invoke_inner<std::peta>(format_str, rep);
+ break;
+ case 15:
+ invoke_inner<std::exa>(format_str, rep);
+ break;
+ }
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if (size <= 4) return 0;
+
+ const auto representation = data[0];
+ const auto period = data[1];
+ data += 2;
+ size -= 2;
+
+ switch (representation) {
+ case 1:
+ invoke_outer<char>(data, size, period);
+ break;
+ case 2:
+ invoke_outer<signed char>(data, size, period);
+ break;
+ case 3:
+ invoke_outer<unsigned char>(data, size, period);
+ break;
+ case 4:
+ invoke_outer<short>(data, size, period);
+ break;
+ case 5:
+ invoke_outer<unsigned short>(data, size, period);
+ break;
+ case 6:
+ invoke_outer<int>(data, size, period);
+ break;
+ case 7:
+ invoke_outer<unsigned int>(data, size, period);
+ break;
+ case 8:
+ invoke_outer<long>(data, size, period);
+ break;
+ case 9:
+ invoke_outer<unsigned long>(data, size, period);
+ break;
+ case 10:
+ invoke_outer<float>(data, size, period);
+ break;
+ case 11:
+ invoke_outer<double>(data, size, period);
+ break;
+ case 12:
+ invoke_outer<long double>(data, size, period);
+ break;
+ }
+ return 0;
+}
diff --git a/src/fmt/test/fuzzing/chrono-timepoint.cc b/src/fmt/test/fuzzing/chrono-timepoint.cc
new file mode 100644
index 000000000..8a1b24d29
--- /dev/null
+++ b/src/fmt/test/fuzzing/chrono-timepoint.cc
@@ -0,0 +1,32 @@
+// Copyright (c) 2021, Paul Dreik
+// For license information refer to format.h.
+#include <fmt/chrono.h>
+
+#include "fuzzer-common.h"
+
+/*
+ * a fuzzer for the chrono timepoints formatters
+ * C is a clock (std::chrono::system_clock etc)
+ */
+template <typename C> void doit(const uint8_t* data, size_t size) {
+ using Rep = typename C::time_point::rep;
+ constexpr auto N = sizeof(Rep);
+ if (size < N) return;
+
+ const auto x = assign_from_buf<Rep>(data);
+ typename C::duration dur{x};
+ typename C::time_point timepoint{dur};
+ data += N;
+ size -= N;
+ data_to_string format_str(data, size);
+
+ std::string message = fmt::format(format_str.get(), timepoint);
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ try {
+ doit<std::chrono::system_clock>(data, size);
+ } catch (...) {
+ }
+ return 0;
+}
diff --git a/src/fmt/test/fuzzing/float.cc b/src/fmt/test/fuzzing/float.cc
new file mode 100644
index 000000000..d4c9e4f57
--- /dev/null
+++ b/src/fmt/test/fuzzing/float.cc
@@ -0,0 +1,39 @@
+// A fuzzer for floating-point formatter.
+// For the license information refer to format.h.
+
+#include <fmt/format.h>
+
+#include <cstdint>
+#include <cstdlib>
+#include <limits>
+#include <stdexcept>
+
+#include "fuzzer-common.h"
+
+void check_round_trip(fmt::string_view format_str, double value) {
+ auto buffer = fmt::memory_buffer();
+ fmt::format_to(std::back_inserter(buffer), format_str, value);
+
+ if (std::isnan(value)) {
+ auto nan = std::signbit(value) ? "-nan" : "nan";
+ if (fmt::string_view(buffer.data(), buffer.size()) != nan)
+ throw std::runtime_error("round trip failure");
+ return;
+ }
+
+ buffer.push_back('\0');
+ char* ptr = nullptr;
+ if (std::strtod(buffer.data(), &ptr) != value)
+ throw std::runtime_error("round trip failure");
+ if (ptr + 1 != buffer.end()) throw std::runtime_error("unparsed output");
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if (size <= sizeof(double) || !std::numeric_limits<double>::is_iec559)
+ return 0;
+ check_round_trip("{}", assign_from_buf<double>(data));
+ // A larger than necessary precision is used to trigger the fallback
+ // formatter.
+ check_round_trip("{:.50g}", assign_from_buf<double>(data));
+ return 0;
+}
diff --git a/src/fmt/test/fuzzing/fuzzer-common.h b/src/fmt/test/fuzzing/fuzzer-common.h
new file mode 100644
index 000000000..c0a246722
--- /dev/null
+++ b/src/fmt/test/fuzzing/fuzzer-common.h
@@ -0,0 +1,77 @@
+// Copyright (c) 2019, Paul Dreik
+// For the license information refer to format.h.
+
+#ifndef FUZZER_COMMON_H
+#define FUZZER_COMMON_H
+
+#include <fmt/core.h>
+
+#include <cstdint> // std::uint8_t
+#include <cstring> // memcpy
+#include <vector>
+
+// One can format to either a string, or a buffer. The latter is faster, but
+// one may be interested in formatting to a string instead to verify it works
+// as intended. To avoid a combinatoric explosion, select this at compile time
+// instead of dynamically from the fuzz data.
+#define FMT_FUZZ_FORMAT_TO_STRING 0
+
+// If {fmt} is given a buffer that is separately allocated, chances that address
+// sanitizer detects out of bound reads is much higher. However, it slows down
+// the fuzzing.
+#define FMT_FUZZ_SEPARATE_ALLOCATION 1
+
+// The size of the largest possible type in use.
+// To let the the fuzzer mutation be efficient at cross pollinating between
+// different types, use a fixed size format. The same bit pattern, interpreted
+// as another type, is likely interesting.
+constexpr auto fixed_size = 16;
+
+// Casts data to a char pointer.
+template <typename T> inline const char* as_chars(const T* data) {
+ return reinterpret_cast<const char*>(data);
+}
+
+// Casts data to a byte pointer.
+template <typename T> inline const std::uint8_t* as_bytes(const T* data) {
+ return reinterpret_cast<const std::uint8_t*>(data);
+}
+
+// Blits bytes from data to form an (assumed trivially constructible) object
+// of type Item.
+template <class Item> inline Item assign_from_buf(const std::uint8_t* data) {
+ auto item = Item();
+ std::memcpy(&item, data, sizeof(Item));
+ return item;
+}
+
+// Reads a boolean value by looking at the first byte from data.
+template <> inline bool assign_from_buf<bool>(const std::uint8_t* data) {
+ return *data != 0;
+}
+
+struct data_to_string {
+#if FMT_FUZZ_SEPARATE_ALLOCATION
+ std::vector<char> buffer;
+
+ data_to_string(const uint8_t* data, size_t size, bool add_terminator = false)
+ : buffer(size + (add_terminator ? 1 : 0)) {
+ if (size) {
+ std::memcpy(buffer.data(), data, size);
+ }
+ }
+
+ fmt::string_view get() const { return {buffer.data(), buffer.size()}; }
+#else
+ fmt::string_view sv;
+
+ data_to_string(const uint8_t* data, size_t size, bool = false)
+ : str(as_chars(data), size) {}
+
+ fmt::string_view get() const { return sv; }
+#endif
+
+ const char* data() const { return get().data(); }
+};
+
+#endif // FUZZER_COMMON_H
diff --git a/src/fmt/test/fuzzing/main.cc b/src/fmt/test/fuzzing/main.cc
new file mode 100644
index 000000000..8f8c719b7
--- /dev/null
+++ b/src/fmt/test/fuzzing/main.cc
@@ -0,0 +1,22 @@
+#include <cassert>
+#include <fstream>
+#include <vector>
+
+#include "fuzzer-common.h"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
+
+int main(int argc, char** argv) {
+ for (int i = 1; i < argc; ++i) {
+ std::ifstream in(argv[i]);
+ assert(in);
+ in.seekg(0, std::ios_base::end);
+ const auto size = in.tellg();
+ assert(size >= 0);
+ in.seekg(0, std::ios_base::beg);
+ std::vector<char> buf(static_cast<size_t>(size));
+ in.read(buf.data(), size);
+ assert(in.gcount() == size);
+ LLVMFuzzerTestOneInput(as_bytes(buf.data()), buf.size());
+ }
+}
diff --git a/src/fmt/test/fuzzing/named-arg.cc b/src/fmt/test/fuzzing/named-arg.cc
new file mode 100644
index 000000000..3bee1ae3b
--- /dev/null
+++ b/src/fmt/test/fuzzing/named-arg.cc
@@ -0,0 +1,102 @@
+// Copyright (c) 2019, Paul Dreik
+// For the license information refer to format.h.
+
+#include <fmt/chrono.h>
+
+#include <cstdint>
+#include <type_traits>
+#include <vector>
+
+#include "fuzzer-common.h"
+
+template <typename T>
+void invoke_fmt(const uint8_t* data, size_t size, unsigned arg_name_size) {
+ static_assert(sizeof(T) <= fixed_size, "fixed_size too small");
+ if (size <= fixed_size) return;
+ const T value = assign_from_buf<T>(data);
+ data += fixed_size;
+ size -= fixed_size;
+
+ if (arg_name_size <= 0 || arg_name_size >= size) return;
+ data_to_string arg_name(data, arg_name_size, true);
+ data += arg_name_size;
+ size -= arg_name_size;
+
+ data_to_string format_str(data, size);
+ try {
+#if FMT_FUZZ_FORMAT_TO_STRING
+ std::string message =
+ fmt::format(format_str.get(), fmt::arg(arg_name.data(), value));
+#else
+ fmt::memory_buffer out;
+ fmt::format_to(std::back_inserter(out), format_str.get(),
+ fmt::arg(arg_name.data(), value));
+#endif
+ } catch (std::exception&) {
+ }
+}
+
+// For dynamic dispatching to an explicit instantiation.
+template <typename Callback> void invoke(int type, Callback callback) {
+ switch (type) {
+ case 0:
+ callback(bool());
+ break;
+ case 1:
+ callback(char());
+ break;
+ case 2:
+ using sc = signed char;
+ callback(sc());
+ break;
+ case 3:
+ using uc = unsigned char;
+ callback(uc());
+ break;
+ case 4:
+ callback(short());
+ break;
+ case 5:
+ using us = unsigned short;
+ callback(us());
+ break;
+ case 6:
+ callback(int());
+ break;
+ case 7:
+ callback(unsigned());
+ break;
+ case 8:
+ callback(long());
+ break;
+ case 9:
+ using ul = unsigned long;
+ callback(ul());
+ break;
+ case 10:
+ callback(float());
+ break;
+ case 11:
+ callback(double());
+ break;
+ case 12:
+ using LD = long double;
+ callback(LD());
+ break;
+ }
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if (size <= 3) return 0;
+
+ // Switch types depending on the first byte of the input.
+ const auto type = data[0] & 0x0F;
+ const unsigned arg_name_size = (data[0] & 0xF0) >> 4;
+ data++;
+ size--;
+
+ invoke(type, [=](auto arg) {
+ invoke_fmt<decltype(arg)>(data, size, arg_name_size);
+ });
+ return 0;
+}
diff --git a/src/fmt/test/fuzzing/one-arg.cc b/src/fmt/test/fuzzing/one-arg.cc
new file mode 100644
index 000000000..af9787f81
--- /dev/null
+++ b/src/fmt/test/fuzzing/one-arg.cc
@@ -0,0 +1,92 @@
+// Copyright (c) 2019, Paul Dreik
+// For the license information refer to format.h.
+
+#include <fmt/chrono.h>
+
+#include <cstdint>
+#include <exception>
+
+#include "fuzzer-common.h"
+
+template <typename T, typename Repr> const T* from_repr(const Repr& r) {
+ return &r;
+}
+
+template <> const std::tm* from_repr<std::tm>(const std::time_t& t) {
+ return std::localtime(&t);
+}
+
+template <typename T, typename Repr = T>
+void invoke_fmt(const uint8_t* data, size_t size) {
+ static_assert(sizeof(Repr) <= fixed_size, "Nfixed is too small");
+ if (size <= fixed_size) return;
+ auto repr = assign_from_buf<Repr>(data);
+ const T* value = from_repr<T>(repr);
+ if (!value) return;
+ data += fixed_size;
+ size -= fixed_size;
+ data_to_string format_str(data, size);
+ try {
+#if FMT_FUZZ_FORMAT_TO_STRING
+ std::string message = fmt::format(format_str.get(), *value);
+#else
+ auto buf = fmt::memory_buffer();
+ fmt::format_to(std::back_inserter(buf), format_str.get(), *value);
+#endif
+ } catch (std::exception&) {
+ }
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if (size <= 3) return 0;
+
+ const auto first = data[0];
+ data++;
+ size--;
+
+ switch (first) {
+ case 0:
+ invoke_fmt<bool>(data, size);
+ break;
+ case 1:
+ invoke_fmt<char>(data, size);
+ break;
+ case 2:
+ invoke_fmt<unsigned char>(data, size);
+ break;
+ case 3:
+ invoke_fmt<signed char>(data, size);
+ break;
+ case 4:
+ invoke_fmt<short>(data, size);
+ break;
+ case 5:
+ invoke_fmt<unsigned short>(data, size);
+ break;
+ case 6:
+ invoke_fmt<int>(data, size);
+ break;
+ case 7:
+ invoke_fmt<unsigned int>(data, size);
+ break;
+ case 8:
+ invoke_fmt<long>(data, size);
+ break;
+ case 9:
+ invoke_fmt<unsigned long>(data, size);
+ break;
+ case 10:
+ invoke_fmt<float>(data, size);
+ break;
+ case 11:
+ invoke_fmt<double>(data, size);
+ break;
+ case 12:
+ invoke_fmt<long double>(data, size);
+ break;
+ case 13:
+ invoke_fmt<std::tm, std::time_t>(data, size);
+ break;
+ }
+ return 0;
+}
diff --git a/src/fmt/test/fuzzing/two-args.cc b/src/fmt/test/fuzzing/two-args.cc
new file mode 100644
index 000000000..931c64656
--- /dev/null
+++ b/src/fmt/test/fuzzing/two-args.cc
@@ -0,0 +1,106 @@
+// Copyright (c) 2019, Paul Dreik
+// For the license information refer to format.h.
+
+#include <fmt/format.h>
+
+#include <cstdint>
+#include <exception>
+#include <string>
+
+#include "fuzzer-common.h"
+
+template <typename Item1, typename Item2>
+void invoke_fmt(const uint8_t* data, size_t size) {
+ static_assert(sizeof(Item1) <= fixed_size, "size1 exceeded");
+ static_assert(sizeof(Item2) <= fixed_size, "size2 exceeded");
+ if (size <= fixed_size + fixed_size) return;
+
+ const Item1 item1 = assign_from_buf<Item1>(data);
+ data += fixed_size;
+ size -= fixed_size;
+
+ const Item2 item2 = assign_from_buf<Item2>(data);
+ data += fixed_size;
+ size -= fixed_size;
+
+ auto format_str = fmt::string_view(as_chars(data), size);
+#if FMT_FUZZ_FORMAT_TO_STRING
+ std::string message = fmt::format(format_str, item1, item2);
+#else
+ auto buf = fmt::memory_buffer();
+ fmt::format_to(std::back_inserter(buf), format_str, item1, item2);
+#endif
+}
+
+// For dynamic dispatching to an explicit instantiation.
+template <typename Callback> void invoke(int index, Callback callback) {
+ switch (index) {
+ case 0:
+ callback(bool());
+ break;
+ case 1:
+ callback(char());
+ break;
+ case 2:
+ using sc = signed char;
+ callback(sc());
+ break;
+ case 3:
+ using uc = unsigned char;
+ callback(uc());
+ break;
+ case 4:
+ callback(short());
+ break;
+ case 5:
+ using us = unsigned short;
+ callback(us());
+ break;
+ case 6:
+ callback(int());
+ break;
+ case 7:
+ callback(unsigned());
+ break;
+ case 8:
+ callback(long());
+ break;
+ case 9:
+ using ul = unsigned long;
+ callback(ul());
+ break;
+ case 10:
+ callback(float());
+ break;
+ case 11:
+ callback(double());
+ break;
+ case 12:
+ using LD = long double;
+ callback(LD());
+ break;
+ case 13:
+ using ptr = void*;
+ callback(ptr());
+ break;
+ }
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if (size <= 3) return 0;
+
+ // Switch types depending on the first byte of the input.
+ const auto type1 = data[0] & 0x0F;
+ const auto type2 = (data[0] & 0xF0) >> 4;
+ data++;
+ size--;
+ try {
+ invoke(type1, [=](auto param1) {
+ invoke(type2, [=](auto param2) {
+ invoke_fmt<decltype(param1), decltype(param2)>(data, size);
+ });
+ });
+ } catch (std::exception&) {
+ }
+ return 0;
+}