Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge the fuzzers #1199

Merged
merged 1 commit into from
Jun 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ matrix:
- env: BUILD=Release STANDARD=14
compiler: clang
os: osx
# clang 6.0 on Linux with C++14
- env: COMPILER=clang++-6.0 BUILD=Debug STANDARD=14
# clang 6.0 on Linux with C++14 (builds the fuzzers as well)
- env: COMPILER=clang++-6.0 BUILD=Debug STANDARD=14 ENABLE_FUZZING=1
compiler: clang
addons:
apt:
Expand Down
11 changes: 10 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ option(FMT_WERROR "Halt the compilation with an error on compiler warnings."
option(FMT_DOC "Generate the doc target." ${MASTER_PROJECT})
option(FMT_INSTALL "Generate the install target." ${MASTER_PROJECT})
option(FMT_TEST "Generate the test target." ${MASTER_PROJECT})
option(FMT_FUZZ "Generate the fuzz target." OFF)

pauldreik marked this conversation as resolved.
Show resolved Hide resolved
project(FMT CXX)

Expand Down Expand Up @@ -151,7 +152,7 @@ endfunction()

# Define the fmt library, its includes and the needed defines.
add_headers(FMT_HEADERS chrono.h color.h core.h format.h format-inl.h locale.h
ostream.h prepare.h printf.h ranges.h)
ostream.h prepare.h printf.h ranges.h safe-duration-cast.h)
set(FMT_SOURCES src/format.cc)
if (HAVE_OPEN)
add_headers(FMT_HEADERS posix.h)
Expand Down Expand Up @@ -190,6 +191,9 @@ if (BUILD_SHARED_LIBS)
endif ()
target_compile_definitions(fmt PRIVATE FMT_EXPORT INTERFACE FMT_SHARED)
endif ()
if (FMT_SAFE_DURATION_CAST)
target_compile_definitions(fmt PUBLIC FMT_SAFE_DURATION_CAST)
endif()

add_library(fmt-header-only INTERFACE)
add_library(fmt::fmt-header-only ALIAS fmt-header-only)
Expand Down Expand Up @@ -271,6 +275,11 @@ if (FMT_TEST)
add_subdirectory(test)
endif ()

# control fuzzing independent of the unit tests
if (FMT_FUZZ)
add_subdirectory(test/fuzzing)
endif ()

set(gitignore ${PROJECT_SOURCE_DIR}/.gitignore)
if (MASTER_PROJECT AND EXISTS ${gitignore})
# Get the list of ignored files from .gitignore.
Expand Down
129 changes: 126 additions & 3 deletions include/fmt/chrono.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@
#include <locale>
#include <sstream>

// enable safe chrono durations, unless explicitly disabled
#ifndef FMT_SAFE_DURATION_CAST
# define FMT_SAFE_DURATION_CAST 1
#endif

#if FMT_SAFE_DURATION_CAST
# include "safe-duration-cast.h"
#endif

FMT_BEGIN_NAMESPACE

// Prevents expansion of a preceding token as a function-style macro.
Expand Down Expand Up @@ -385,6 +394,15 @@ inline bool isnan(T value) {
return std::isnan(value);
}

template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
inline bool isfinite(T) {
return true;
}
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
inline bool isfinite(T value) {
return std::isfinite(value);
}

// Convers value to int and checks that it's in the range [0, upper).
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
inline int to_nonnegative_int(T value, int upper) {
Expand Down Expand Up @@ -421,12 +439,40 @@ template <typename T> struct make_unsigned_or_unchanged<T, true> {
using type = typename std::make_unsigned<T>::type;
};

#if FMT_SAFE_DURATION_CAST
// throwing version of safe_duration_cast
template <typename To, typename FromRep, typename FromPeriod>
To fmt_safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from) {
int ec;
To to= safe_duration_cast::safe_duration_cast<To>(from,ec);
if (ec) {
FMT_THROW(format_error("cannot format duration"));
}
return to;
}
#endif

template <typename Rep, typename Period,
FMT_ENABLE_IF(std::is_integral<Rep>::value)>
inline std::chrono::duration<Rep, std::milli> get_milliseconds(
std::chrono::duration<Rep, Period> d) {
// this may overflow and/or the result may not fit in the
// target type.
#if FMT_SAFE_DURATION_CAST
using CommonSecondsType =
typename std::common_type<decltype(d), std::chrono::seconds>::type;
const auto d_as_common = fmt_safe_duration_cast<CommonSecondsType>(d);
const auto d_as_whole_seconds =
fmt_safe_duration_cast<std::chrono::seconds>(d_as_common);
// this conversion should be nonproblematic
const auto diff = d_as_common - d_as_whole_seconds;
const auto ms =
fmt_safe_duration_cast<std::chrono::duration<Rep, std::milli>>(diff);
return ms;
#else
auto s = std::chrono::duration_cast<std::chrono::seconds>(d);
return std::chrono::duration_cast<std::chrono::milliseconds>(d - s);
#endif
}

template <typename Rep, typename Period,
Expand Down Expand Up @@ -476,8 +522,35 @@ struct chrono_formatter {
val = -val;
negative = true;
}

// this may overflow and/or the result may not fit in the
// target type.
#if FMT_SAFE_DURATION_CAST
// might need checked conversion (rep!=Rep)
auto tmpval = std::chrono::duration<rep, Period>(val);
s = fmt_safe_duration_cast<seconds>(tmpval);
#else
s = std::chrono::duration_cast<seconds>(
std::chrono::duration<rep, Period>(val));
#endif
}

// returns true if nan or inf, writes to out.
bool handle_nan_inf() {
if (isfinite(val)) {
return false;
}
if (isnan(val)) {
write_nan();
return true;
}
// must be +-inf
if (val > 0) {
write_pinf();
} else {
write_ninf();
}
return true;
}

Rep hour() const { return static_cast<Rep>(mod((s.count() / 3600), 24)); }
Expand Down Expand Up @@ -517,6 +590,8 @@ struct chrono_formatter {
}

void write_nan() { std::copy_n("nan", 3, out); }
void write_pinf() { std::copy_n("inf", 3, out); }
void write_ninf() { std::copy_n("-inf", 4, out); }

void format_localized(const tm& time, const char* format) {
if (isnan(val)) return write_nan();
Expand Down Expand Up @@ -549,30 +624,54 @@ struct chrono_formatter {
void on_tz_name() {}

void on_24_hour(numeric_system ns) {
if (handle_nan_inf()) {
return;
}

if (ns == numeric_system::standard) return write(hour(), 2);
auto time = tm();
time.tm_hour = to_nonnegative_int(hour(), 24);
format_localized(time, "%OH");
}

void on_12_hour(numeric_system ns) {
if (handle_nan_inf()) {
return;
}

if (ns == numeric_system::standard) return write(hour12(), 2);
auto time = tm();
time.tm_hour = to_nonnegative_int(hour12(), 12);
format_localized(time, "%OI");
}

void on_minute(numeric_system ns) {
if (handle_nan_inf()) {
return;
}

if (ns == numeric_system::standard) return write(minute(), 2);
auto time = tm();
time.tm_min = to_nonnegative_int(minute(), 60);
format_localized(time, "%OM");
}

void on_second(numeric_system ns) {
if (handle_nan_inf()) {
return;
}

if (ns == numeric_system::standard) {
write(second(), 2);
auto ms = get_milliseconds(std::chrono::duration<Rep, Period>(val));
#if FMT_SAFE_DURATION_CAST
// convert rep->Rep
using duration_rep = std::chrono::duration<rep, Period>;
using duration_Rep = std::chrono::duration<Rep, Period>;
auto tmpval = fmt_safe_duration_cast<duration_Rep>(duration_rep{val});
#else
auto tmpval = std::chrono::duration<Rep, Period>(val);
#endif
auto ms = get_milliseconds(tmpval);
if (ms != std::chrono::milliseconds(0)) {
*out++ = '.';
write(ms.count(), 3);
Expand All @@ -584,9 +683,21 @@ struct chrono_formatter {
format_localized(time, "%OS");
}

void on_12_hour_time() { format_localized(time(), "%r"); }
void on_12_hour_time() {
if (handle_nan_inf()) {
return;
}

format_localized(time(), "%r");
}

void on_24_hour_time() {
if (handle_nan_inf()) {
*out++ = ':';
handle_nan_inf();
return;
}

write(hour(), 2);
*out++ = ':';
write(minute(), 2);
Expand All @@ -595,12 +706,24 @@ struct chrono_formatter {
void on_iso_time() {
on_24_hour_time();
*out++ = ':';
if (handle_nan_inf()) {
return;
}
write(second(), 2);
}

void on_am_pm() { format_localized(time(), "%p"); }
void on_am_pm() {
if (handle_nan_inf()) {
return;
}

format_localized(time(), "%p");
}

void on_duration_value() {
if (handle_nan_inf()) {
return;
}
write_sign();
out = format_chrono_duration_value(out, val, precision);
}
Expand Down
5 changes: 5 additions & 0 deletions include/fmt/format-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,11 @@ template <> FMT_FUNC int count_digits<4>(internal::fallback_uintptr n) {
template <typename T>
int format_float(char* buf, std::size_t size, const char* format, int precision,
T value) {
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
if (precision > 100000) {
throw std::runtime_error("fuzz mode - avoid large allocation inside snprintf");
}
#endif
// Suppress the warning about nonliteral format string.
auto snprintf_ptr = FMT_SNPRINTF;
return precision < 0 ? snprintf_ptr(buf, size, format, value)
Expand Down
10 changes: 10 additions & 0 deletions include/fmt/format.h
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,11 @@ class basic_memory_buffer : private Allocator, public internal::buffer<T> {

template <typename T, std::size_t SIZE, typename Allocator>
void basic_memory_buffer<T, SIZE, Allocator>::grow(std::size_t size) {
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
if (size > 1000) {
throw std::runtime_error("fuzz mode - won't grow that much");
}
#endif
std::size_t old_capacity = this->capacity();
std::size_t new_capacity = old_capacity + old_capacity / 2;
if (size > new_capacity) new_capacity = size;
Expand Down Expand Up @@ -1065,6 +1070,11 @@ It grisu_prettify(const char* digits, int size, int exp, It it,
int num_zeros = (std::max)(params.num_digits - full_exp, 1);
if (params.trailing_zeros) {
*it++ = static_cast<Char>('.');
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
if (num_zeros > 1000) {
throw std::runtime_error("fuzz mode - avoiding excessive cpu use");
}
#endif
it = std::fill_n(it, num_zeros, static_cast<Char>('0'));
}
} else if (full_exp > 0) {
Expand Down
Loading