Compare commits

..
Author SHA1 Message Date
Niels Lohmann a2ca07a4b3 Merge branch 'develop' into fix/json_pointer_create_object_5357
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-27 20:58:31 +02:00
Niels Lohmann cda8d2b848 Clarify that the token 0 is a valid array index in operator[] docs
The note described valid array indices as "a nonempty sequence of digits
without a leading 0", which read literally excludes the token "0" itself,
contradicting both the code and the /foo/0/0/0 example in the next
sentence. Spell out the RFC 6901 grammar instead.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-27 18:20:28 +02:00
Niels Lohmann db1d0983e6 create objects for tokens that cannot be array indices (#5357)
When operator[](json_pointer) traverses a level that does not exist yet,
the null value is turned into an array or an object depending on the
reference token. The check only tested whether all characters are digits,
so tokens that can never be a valid array index selected an array and
then failed:

- "01" (and any other token with a leading '0') threw parse_error.106
- the empty token threw out_of_range.404

Both tokens are valid object keys, and both work when the level already
exists as an object, so creating the level changed the outcome. Test the
token against the RFC 6901, Sect. 4 grammar for array indices instead, so
that such tokens create an object. This only affects pointers that threw
before.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-08-04 08:51:54 +02:00
223 changed files with 11404 additions and 1487 deletions
+4
View File
@@ -23,6 +23,10 @@ Files: tests/thirdparty/fifo_map/*
Copyright: 2015-2017 Niels Lohmann
License: MIT
Files: tests/thirdparty/Fuzzer/*
Copyright: 2003-2022 LLVM Project.
License: Apache-2.0
Files: tests/thirdparty/imapdl/*
Copyright: 2017 Georg Sauthoff <mail@gms.tf>
License: GPL-3.0-only
+2 -2
View File
@@ -80,8 +80,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
the end of the file was not reached when `strict` was set to true
- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if unsupported features from CBOR were
used in the given input or if the input is not valid CBOR
- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a map key is not a string (keys of other
types are not supported, as JSON object keys are always strings) or a string is malformed
- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a string was expected as a map key,
but not found
## Complexity
@@ -73,8 +73,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
the end of the file was not reached when `strict` was set to true
- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if unsupported features from
MessagePack were used in the given input or if the input is not valid MessagePack
- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a map key is not a string (keys of other
types are not supported, as JSON object keys are always strings) or a string is malformed
- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a string was expected as a map key,
but not found
## Complexity
@@ -128,10 +128,13 @@ Strong exception safety: if an exception occurs, the original value stays intact
When the JSON pointer traverses intermediate levels that don't exist at all yet (not just a missing
leaf), each missing level is created as an array or an object depending on whether the corresponding
pointer token parses as a non-negative integer: a numeric token creates an array, a non-numeric token
creates an object. For example, on an initially `#!json null` value, `/foo/0/0/0` creates nested arrays,
while `/foo/one/one/one` creates nested objects. This is not specified by the JSON Pointer RFC; it is
this library's own, intentional disambiguation rule. See also [JSON Pointer](../../features/json_pointer.md).
pointer token is a valid array index: the token `0`, a sequence of digits that does not begin with `0`,
or the token `-` creates an array, and every other token creates an object. For example, on an
initially `#!json null` value, `/foo/0/0/0` creates nested arrays, while `/foo/one/one/one` creates
nested objects. Tokens such as `01` or the empty token cannot be array indices (cf. RFC 6901, Sect. 4)
and therefore create objects, just as they would if the level already existed as an object. This is not
specified by the JSON Pointer RFC; it is this library's own, intentional disambiguation rule. See also
[JSON Pointer](../../features/json_pointer.md).
## Examples
@@ -174,20 +174,7 @@ The library maps CBOR types to JSON value types as follows:
!!! warning "Object keys"
CBOR allows map keys of any type, whereas JSON only allows strings as keys in object values. Therefore, CBOR maps
with keys other than text strings (major type 3) are rejected with a
[`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or, with `allow_exceptions` set
to `false`, a discarded value) naming the type of the key that was found, for instance:
```
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found an unsigned integer; last byte: 0x01
```
This applies to the [SAX interface](../parsing/sax_interface.md) as well, as the key is read before it is passed
on. This is a deliberate restriction of the library's JSON value model, not an oversight: formats built on CBOR
maps with integer keys, such as COSE ([RFC 9052](https://www.rfc-editor.org/rfc/rfc9052.html)) or CWT
([RFC 8392](https://www.rfc-editor.org/rfc/rfc8392.html)), cannot be read with this library and need a
general-purpose CBOR library instead.
CBOR allows map keys of any type, whereas JSON only allows strings as keys in object values. Therefore, CBOR maps with keys other than UTF-8 strings are rejected.
!!! warning "UTF-8 validation of text strings"
@@ -138,21 +138,6 @@ The library maps MessagePack types to JSON value types as follows:
Any MessagePack output created by `to_msgpack` can be successfully parsed by `from_msgpack`.
!!! warning "Object keys"
MessagePack allows map keys of any type, whereas JSON only allows strings as keys in object values. Like the
JSON-compatible [profile](https://github.com/msgpack/msgpack/blob/master/spec.md#profile) sketched in the
MessagePack specification, this library restricts map keys to `str` values. Maps with keys of any other type are
rejected with a [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or, with
`allow_exceptions` set to `false`, a discarded value) naming the type of the key that was found, for instance:
```
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack object key: only string keys are supported, but found nil; last byte: 0xC0
```
This applies to the [SAX interface](../parsing/sax_interface.md) as well, as the key is read before it is passed
on. Such input needs a general-purpose MessagePack library instead.
!!! warning "UTF-8 validation of string values"
The MessagePack specification requires `str` values (`fixstr`, `str 8`, `str 16`, `str 32`) to be valid UTF-8.
+2 -9
View File
@@ -343,20 +343,13 @@ A string could not be read from a [binary format](../features/binary_formats/ind
string was read where one was required (for instance as a map key), the string's length specification is invalid, or
the string's bytes are not valid UTF-8.
CBOR and MessagePack allow map keys of any type, but JSON object keys are always strings. Maps with keys of any other
type (for instance integers or `null`) are therefore not supported; see the notes on
[CBOR](../features/binary_formats/cbor.md) and [MessagePack](../features/binary_formats/messagepack.md).
!!! failure "Example messages"
```
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found an unsigned integer; last byte: 0x01
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0xFF
```
```
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack object key: only string keys are supported, but found nil; last byte: 0xC0
```
```
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x7C
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack string: expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0xFF
```
```
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON char: byte after 'C' must be in range 0x00..0x7F; last byte: 0x82
+2 -168
View File
@@ -1324,80 +1324,6 @@ class binary_reader
}
}
/*!
@brief reads a CBOR object key
RFC 8949 allows any data item as a map key, but only strings have a
counterpart in JSON. A key of any other type is rejected with a message
naming that type, rather than the one @ref get_cbor_string gives for a
malformed string.
@param[out] result created key
@return whether key creation completed
*/
bool get_cbor_object_key(string_t& result)
{
// EOF and major type 3 (text string) are left to get_cbor_string
if (current == char_traits<char_type>::eof() || (static_cast<unsigned int>(current) & 0xE0u) == 0x60u)
{
return get_cbor_string(result);
}
const char* found = nullptr;
switch (static_cast<unsigned int>(current) >> 5u)
{
case 0:
found = "an unsigned integer";
break;
case 1:
found = "a negative integer";
break;
case 2:
found = "a byte string";
break;
case 4:
found = "an array";
break;
case 5:
found = "a map";
break;
case 6:
found = "a tag";
break;
default: // major type 7
switch (current)
{
case 0xF4:
case 0xF5:
found = "a boolean";
break;
case 0xF6:
found = "null";
break;
case 0xF7:
found = "undefined";
break;
case 0xF9:
case 0xFA:
case 0xFB:
found = "a floating-point number";
break;
case 0xFF:
found = "a break stop code";
break;
default:
found = "a simple value";
break;
}
break;
}
auto last_token = get_token_string();
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
exception_message(input_format_t::cbor, concat("only string keys are supported, but found ", found, "; last byte: 0x", last_token), "object key"), nullptr));
}
/*!
@brief reads a definite-length CBOR byte array
@@ -1642,7 +1568,7 @@ class binary_reader
if (top.is_object)
{
key.clear();
if (JSON_HEDLEY_UNLIKELY(!get_cbor_object_key(key) || !sax->key(key)))
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string(key) || !sax->key(key)))
{
return false;
}
@@ -2143,98 +2069,6 @@ class binary_reader
}
}
/*!
@brief reads a MessagePack object key
The MessagePack specification allows any type as a map key, but only
strings have a counterpart in JSON. A key of any other type is rejected
with a message naming that type, rather than the one @ref
get_msgpack_string gives for a malformed string.
@param[out] result created key
@return whether key creation completed
*/
bool get_msgpack_object_key(string_t& result)
{
const char* found = nullptr;
switch (current)
{
case 0xC0:
found = "nil";
break;
case 0xC2:
case 0xC3:
found = "a boolean";
break;
case 0xCA:
case 0xCB:
found = "a float";
break;
case 0xC4:
case 0xC5:
case 0xC6:
found = "a bin";
break;
case 0xC7:
case 0xC8:
case 0xC9:
case 0xD4:
case 0xD5:
case 0xD6:
case 0xD7:
case 0xD8:
found = "an ext";
break;
case 0xCC:
case 0xCD:
case 0xCE:
case 0xCF:
case 0xD0:
case 0xD1:
case 0xD2:
case 0xD3:
found = "an integer";
break;
case 0xDC:
case 0xDD:
found = "an array";
break;
case 0xDE:
case 0xDF:
found = "a map";
break;
default:
// fixint, fixmap, and fixarray; strings, EOF, and the unused
// byte 0xC1 are left to get_msgpack_string
if (current == char_traits<char_type>::eof())
{
return get_msgpack_string(result);
}
if (current <= 0x7F || current >= 0xE0)
{
found = "an integer";
}
else if (current <= 0x8F)
{
found = "a map";
}
else if (current <= 0x9F)
{
found = "an array";
}
else
{
return get_msgpack_string(result);
}
break;
}
auto last_token = get_token_string();
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
exception_message(input_format_t::msgpack, concat("only string keys are supported, but found ", found, "; last byte: 0x", last_token), "object key"), nullptr));
}
/*!
@brief reads a MessagePack byte array
@@ -2397,7 +2231,7 @@ class binary_reader
{
get();
key.clear();
if (JSON_HEDLEY_UNLIKELY(!get_msgpack_object_key(key) || !sax->key(key)))
if (JSON_HEDLEY_UNLIKELY(!get_msgpack_string(key) || !sax->key(key)))
{
return false;
}
+39 -76
View File
@@ -206,6 +206,7 @@ class lexer : public lexer_base<BasicJsonType>
explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false, bool discard_number_values_ = false) noexcept
: ia(std::move(adapter))
, ignore_comments(ignore_comments_)
, decimal_point_char(static_cast<char_int_type>(get_decimal_point()))
, discard_number_values(discard_number_values_)
{}
@@ -221,7 +222,8 @@ class lexer : public lexer_base<BasicJsonType>
// locales
/////////////////////
/// return the decimal point of the current locale
/// return the locale-dependent decimal point
JSON_HEDLEY_PURE
static char get_decimal_point() noexcept
{
const auto* loc = localeconv();
@@ -1090,10 +1092,9 @@ class lexer : public lexer_base<BasicJsonType>
token_type::value_float if number could be successfully scanned,
token_type::parse_error otherwise
@note The scanner is independent of the current locale: token_buffer
always holds `.`. Only the std::strtod fallback of convert_number()
depends on the locale, and it looks up the decimal point right
before converting (see convert_float_locale_aware()).
@note The scanner is independent of the current locale. Internally, the
locale's decimal point is used instead of `.` to work with the
locale-dependent converters.
*/
token_type scan_number() // lgtm [cpp/use-of-goto] `goto` is used in this function to implement the number-parsing state machine described above. By design, any finite input will eventually reach the "done" state or return token_type::parse_error. In each intermediate state, 1 byte of the input is appended to the token_buffer vector, and only the already initialized variables token_buffer, number_type, and error_message are manipulated.
{
@@ -1182,7 +1183,7 @@ scan_number_zero:
{
case '.':
{
add(current);
add(decimal_point_char);
decimal_point_position = token_buffer.size() - 1;
goto scan_number_decimal1;
}
@@ -1219,7 +1220,7 @@ scan_number_any1:
case '.':
{
add(current);
add(decimal_point_char);
decimal_point_position = token_buffer.size() - 1;
goto scan_number_decimal1;
}
@@ -1461,9 +1462,9 @@ scan_number_done:
// Only a number below 1 can carry further insignificant zeros, and only
// while the count stays at the limit does removing them change the
// answer - so this loop is skipped for all but a few tokens. The
// fraction is located through decimal_point_position rather than by
// searching '.'.
// answer - so this loop is skipped for all but a few tokens. Note
// token_buffer holds the locale's decimal point, so the fraction is
// located through decimal_point_position rather than by searching '.'.
if (lead_zero != 0)
{
JSON_ASSERT(has_dot != 0); // an integer "0" cannot reach the limit
@@ -1481,8 +1482,8 @@ scan_number_done:
@brief convert the number text in token_buffer to its value and token type
The digit sequence in token_buffer has already been validated (by the
scan_number() state machine or by the contiguous fast path) and holds '.'
as decimal point, independent of the locale. Integers are parsed first and fall
scan_number() state machine or by the contiguous fast path) and holds the
locale decimal point in place of '.'. Integers are parsed first and fall
back to floating point on overflow. This is shared so both scanners produce
identical results.
@@ -1562,7 +1563,7 @@ scan_number_done:
// integer conversion above overflowed. Prefer std::from_chars
// (Eisel-Lemire, locale-independent, correctly rounded) when available;
// otherwise the exact Clinger fast path (double only); otherwise the
// locale-aware strtof/strtod/strtold.
// locale-aware strtof/strtod.
if (parse_float_from_chars(num_begin, num_end, value_float))
{
return token_type::value_float;
@@ -1571,75 +1572,26 @@ scan_number_done:
// extra pass over the token's bytes, which otherwise shows up on
// high-precision inputs such as canada.json
if (mantissa_fits_clinger(mantissa_end)
&& parse_float_fast(num_begin, num_end, value_float))
&& parse_float_fast(num_begin, num_end, decimal_point_char, value_float))
{
return token_type::value_float;
}
convert_float_locale_aware();
char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg)
strtof(value_float, token_buffer.data(), &endptr);
// we checked the number format before
JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size());
return token_type::value_float;
}
/*!
@brief convert the float in token_buffer with strtof/strtod/strtold
These functions expect the decimal point of the *current* locale, so it is
looked up right before the conversion instead of once when the lexer is
constructed: a locale change in between (by a parser callback, a SAX
handler, or another thread) must not truncate the value (#5198). The
token has been validated before, so if the conversion stops early and the
decimal point changed in the meantime, the locale changed between the
lookup and the call, and the conversion is repeated with the new decimal
point. If the decimal point did not change, a retry cannot succeed: the
locale's decimal point is not a single character (e.g., the two-byte
U+066B of ar_EG.UTF-8 or fa_IR.UTF-8) and cannot be substituted in place.
The value strtod parsed up to that point is kept, as before this change.
Note that changing the locale in another thread *while* strtod runs is
undefined behavior of the C library, which this function cannot prevent.
*/
void convert_float_locale_aware()
{
const bool has_dot = decimal_point_position != std::string::npos;
char decimal_point = get_decimal_point();
for (;;)
{
const bool substitute = has_dot && decimal_point != '.';
if (substitute)
{
token_buffer[decimal_point_position] = static_cast<typename string_t::value_type>(decimal_point);
}
char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg)
strtof(value_float, token_buffer.data(), &endptr);
if (substitute)
{
// get_string() hands the token to the SAX interface with '.'
token_buffer[decimal_point_position] = '.';
}
if (JSON_HEDLEY_LIKELY(endptr == token_buffer.data() + token_buffer.size()))
{
return;
}
// retry only if the locale changed; otherwise, this would loop forever
const char current_decimal_point = get_decimal_point();
if (current_decimal_point == decimal_point)
{
return;
}
decimal_point = current_decimal_point;
}
}
/*!
@brief contiguous fast path for scanning a number
Parses the whole number token straight from the input buffer, avoiding the
per-character get()/add() of scan_number(). On success it fills token_buffer
(as scan_number() does) and
(with the locale decimal point substituted, as scan_number() does) and
returns the token type. On anything it does not fully recognize as a
well-formed number it makes no state change and returns
token_type::uninitialized, so the caller falls back to scan_number(), which
@@ -1755,11 +1707,16 @@ scan_number_done:
}
#endif
// materialize the token exactly as scan_number() would. reset() already
// cleared token_buffer, so append() fills it (assign() is avoided
// because custom string_t types need not provide it)
// materialize the token exactly as scan_number() would, substituting the
// locale decimal point so convert_number()'s strtof fallback stays valid.
// reset() already cleared token_buffer, so append() fills it (assign() is
// avoided because custom string_t types need not provide it)
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
decimal_point_position = dot_index;
if (dot_index != std::string::npos)
{
token_buffer[dot_index] = static_cast<typename string_t::value_type>(decimal_point_char);
decimal_point_position = dot_index;
}
ia.bulk_skip(len - 1);
position.chars_read_total += (len - 1);
@@ -2026,7 +1983,11 @@ scan_number_done:
/// return current string value (implicitly resets the token; useful only once)
string_t& get_string()
{
// a number token holds '.' regardless of the locale (#4084)
// translate decimal points from locale back to '.' (#4084)
if (decimal_point_char != '.' && decimal_point_position != std::string::npos)
{
token_buffer[decimal_point_position] = '.';
}
return token_buffer;
}
@@ -2322,7 +2283,9 @@ scan_number_done:
number_unsigned_t value_unsigned = 0;
number_float_t value_float = 0;
/// the position of the decimal point in token_buffer
/// the decimal point
const char_int_type decimal_point_char = '.';
/// the position of the decimal point in the input
std::size_t decimal_point_position = std::string::npos;
/// whether the caller (e.g. accept()/json_sax_acceptor) only needs the
+13 -8
View File
@@ -118,12 +118,14 @@ std::strtod. The parser only activates for number_float_t == double; float and
long double keep the std::strtof/std::strtold paths (see the templated overload
below).
@param[in] first pointer to the first character of the number
@param[in] last pointer past the last character
@param[out] out the parsed value on success
@param[in] first pointer to the first character of the number
@param[in] last pointer past the last character
@param[in] decimal_point the (locale-dependent) decimal point character
@param[out] out the parsed value on success
@return true if the value was parsed exactly; false to fall back to strtod
*/
inline bool parse_float_fast(const char* first, const char* last, double& out) noexcept
template<typename DecimalPointType>
bool parse_float_fast(const char* first, const char* last, DecimalPointType decimal_point, double& out) noexcept
{
#if defined(FLT_EVAL_METHOD) && FLT_EVAL_METHOD != 0
// Clinger's fast path is only exact when double operations are evaluated in
@@ -134,6 +136,7 @@ inline bool parse_float_fast(const char* first, const char* last, double& out) n
// std::from_chars / std::strtod path.
static_cast<void>(first);
static_cast<void>(last);
static_cast<void>(decimal_point);
static_cast<void>(out);
return false;
#else
@@ -172,7 +175,7 @@ inline bool parse_float_fast(const char* first, const char* last, double& out) n
++num_digits;
fractional_digits += static_cast<int>(seen_dot);
}
else if (c == '.')
else if (static_cast<DecimalPointType>(c) == decimal_point)
{
if (JSON_HEDLEY_UNLIKELY(seen_dot))
{
@@ -257,8 +260,8 @@ inline bool parse_float_fast(const char* first, const char* last, double& out) n
}
/// fast float path is only exact for `double`; decline for float/long double
template<typename FloatType>
bool parse_float_fast(const char* /*first*/, const char* /*last*/, FloatType& /*out*/) noexcept
template<typename DecimalPointType, typename FloatType>
bool parse_float_fast(const char* /*first*/, const char* /*last*/, DecimalPointType /*decimal_point*/, FloatType& /*out*/) noexcept
{
return false;
}
@@ -270,7 +273,9 @@ std::from_chars is locale-independent, correctly rounded, and - via the
Eisel-Lemire algorithm in modern standard libraries - much faster than strtod
over the whole value range (not just the Clinger subset). It is used only when
__cpp_lib_to_chars indicates full floating-point support and only when it
consumes the entire token ([first, last)). An under-/overflow (result_out_of_range) also declines, so
consumes the entire token ([first, last)); a partial parse means the buffer
uses a non-'.' locale decimal point, in which case the caller falls back to the
locale-aware path. An under-/overflow (result_out_of_range) also declines, so
the caller's strtod fallback supplies the well-defined ±inf/0 result the parser
expects (side-stepping the P4168 divergence between implementations).
+9 -5
View File
@@ -416,15 +416,19 @@ class json_pointer
// convert null values to arrays or objects before continuing
if (ptr->is_null())
{
// check if the reference token is a number
const bool nums =
std::all_of(reference_token.begin(), reference_token.end(),
[](const unsigned char x)
// check if the reference token is a valid array index, that is
// a nonempty sequence of digits without a leading '0'
// (cf. RFC 6901, Sect. 4); tokens that could never be a valid
// array index (such as "01" or "") are treated as object keys
const bool nums = !reference_token.empty()
&& (reference_token.size() == 1 || reference_token[0] != '0')
&& std::all_of(reference_token.begin(), reference_token.end(),
[](const unsigned char x)
{
return std::isdigit(x);
});
// change value to an array for numbers or "-" or to object otherwise
// change value to an array for array indices or "-" or to object otherwise
*ptr = (nums || reference_token == "-")
? detail::value_t::array
: detail::value_t::object;
+63 -257
View File
@@ -8605,12 +8605,14 @@ std::strtod. The parser only activates for number_float_t == double; float and
long double keep the std::strtof/std::strtold paths (see the templated overload
below).
@param[in] first pointer to the first character of the number
@param[in] last pointer past the last character
@param[out] out the parsed value on success
@param[in] first pointer to the first character of the number
@param[in] last pointer past the last character
@param[in] decimal_point the (locale-dependent) decimal point character
@param[out] out the parsed value on success
@return true if the value was parsed exactly; false to fall back to strtod
*/
inline bool parse_float_fast(const char* first, const char* last, double& out) noexcept
template<typename DecimalPointType>
bool parse_float_fast(const char* first, const char* last, DecimalPointType decimal_point, double& out) noexcept
{
#if defined(FLT_EVAL_METHOD) && FLT_EVAL_METHOD != 0
// Clinger's fast path is only exact when double operations are evaluated in
@@ -8621,6 +8623,7 @@ inline bool parse_float_fast(const char* first, const char* last, double& out) n
// std::from_chars / std::strtod path.
static_cast<void>(first);
static_cast<void>(last);
static_cast<void>(decimal_point);
static_cast<void>(out);
return false;
#else
@@ -8659,7 +8662,7 @@ inline bool parse_float_fast(const char* first, const char* last, double& out) n
++num_digits;
fractional_digits += static_cast<int>(seen_dot);
}
else if (c == '.')
else if (static_cast<DecimalPointType>(c) == decimal_point)
{
if (JSON_HEDLEY_UNLIKELY(seen_dot))
{
@@ -8744,8 +8747,8 @@ inline bool parse_float_fast(const char* first, const char* last, double& out) n
}
/// fast float path is only exact for `double`; decline for float/long double
template<typename FloatType>
bool parse_float_fast(const char* /*first*/, const char* /*last*/, FloatType& /*out*/) noexcept
template<typename DecimalPointType, typename FloatType>
bool parse_float_fast(const char* /*first*/, const char* /*last*/, DecimalPointType /*decimal_point*/, FloatType& /*out*/) noexcept
{
return false;
}
@@ -8757,7 +8760,9 @@ std::from_chars is locale-independent, correctly rounded, and - via the
Eisel-Lemire algorithm in modern standard libraries - much faster than strtod
over the whole value range (not just the Clinger subset). It is used only when
__cpp_lib_to_chars indicates full floating-point support and only when it
consumes the entire token ([first, last)). An under-/overflow (result_out_of_range) also declines, so
consumes the entire token ([first, last)); a partial parse means the buffer
uses a non-'.' locale decimal point, in which case the caller falls back to the
locale-aware path. An under-/overflow (result_out_of_range) also declines, so
the caller's strtod fallback supplies the well-defined ±inf/0 result the parser
expects (side-stepping the P4168 divergence between implementations).
@@ -9298,6 +9303,7 @@ class lexer : public lexer_base<BasicJsonType>
explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false, bool discard_number_values_ = false) noexcept
: ia(std::move(adapter))
, ignore_comments(ignore_comments_)
, decimal_point_char(static_cast<char_int_type>(get_decimal_point()))
, discard_number_values(discard_number_values_)
{}
@@ -9313,7 +9319,8 @@ class lexer : public lexer_base<BasicJsonType>
// locales
/////////////////////
/// return the decimal point of the current locale
/// return the locale-dependent decimal point
JSON_HEDLEY_PURE
static char get_decimal_point() noexcept
{
const auto* loc = localeconv();
@@ -10182,10 +10189,9 @@ class lexer : public lexer_base<BasicJsonType>
token_type::value_float if number could be successfully scanned,
token_type::parse_error otherwise
@note The scanner is independent of the current locale: token_buffer
always holds `.`. Only the std::strtod fallback of convert_number()
depends on the locale, and it looks up the decimal point right
before converting (see convert_float_locale_aware()).
@note The scanner is independent of the current locale. Internally, the
locale's decimal point is used instead of `.` to work with the
locale-dependent converters.
*/
token_type scan_number() // lgtm [cpp/use-of-goto] `goto` is used in this function to implement the number-parsing state machine described above. By design, any finite input will eventually reach the "done" state or return token_type::parse_error. In each intermediate state, 1 byte of the input is appended to the token_buffer vector, and only the already initialized variables token_buffer, number_type, and error_message are manipulated.
{
@@ -10274,7 +10280,7 @@ scan_number_zero:
{
case '.':
{
add(current);
add(decimal_point_char);
decimal_point_position = token_buffer.size() - 1;
goto scan_number_decimal1;
}
@@ -10311,7 +10317,7 @@ scan_number_any1:
case '.':
{
add(current);
add(decimal_point_char);
decimal_point_position = token_buffer.size() - 1;
goto scan_number_decimal1;
}
@@ -10553,9 +10559,9 @@ scan_number_done:
// Only a number below 1 can carry further insignificant zeros, and only
// while the count stays at the limit does removing them change the
// answer - so this loop is skipped for all but a few tokens. The
// fraction is located through decimal_point_position rather than by
// searching '.'.
// answer - so this loop is skipped for all but a few tokens. Note
// token_buffer holds the locale's decimal point, so the fraction is
// located through decimal_point_position rather than by searching '.'.
if (lead_zero != 0)
{
JSON_ASSERT(has_dot != 0); // an integer "0" cannot reach the limit
@@ -10573,8 +10579,8 @@ scan_number_done:
@brief convert the number text in token_buffer to its value and token type
The digit sequence in token_buffer has already been validated (by the
scan_number() state machine or by the contiguous fast path) and holds '.'
as decimal point, independent of the locale. Integers are parsed first and fall
scan_number() state machine or by the contiguous fast path) and holds the
locale decimal point in place of '.'. Integers are parsed first and fall
back to floating point on overflow. This is shared so both scanners produce
identical results.
@@ -10654,7 +10660,7 @@ scan_number_done:
// integer conversion above overflowed. Prefer std::from_chars
// (Eisel-Lemire, locale-independent, correctly rounded) when available;
// otherwise the exact Clinger fast path (double only); otherwise the
// locale-aware strtof/strtod/strtold.
// locale-aware strtof/strtod.
if (parse_float_from_chars(num_begin, num_end, value_float))
{
return token_type::value_float;
@@ -10663,75 +10669,26 @@ scan_number_done:
// extra pass over the token's bytes, which otherwise shows up on
// high-precision inputs such as canada.json
if (mantissa_fits_clinger(mantissa_end)
&& parse_float_fast(num_begin, num_end, value_float))
&& parse_float_fast(num_begin, num_end, decimal_point_char, value_float))
{
return token_type::value_float;
}
convert_float_locale_aware();
char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg)
strtof(value_float, token_buffer.data(), &endptr);
// we checked the number format before
JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size());
return token_type::value_float;
}
/*!
@brief convert the float in token_buffer with strtof/strtod/strtold
These functions expect the decimal point of the *current* locale, so it is
looked up right before the conversion instead of once when the lexer is
constructed: a locale change in between (by a parser callback, a SAX
handler, or another thread) must not truncate the value (#5198). The
token has been validated before, so if the conversion stops early and the
decimal point changed in the meantime, the locale changed between the
lookup and the call, and the conversion is repeated with the new decimal
point. If the decimal point did not change, a retry cannot succeed: the
locale's decimal point is not a single character (e.g., the two-byte
U+066B of ar_EG.UTF-8 or fa_IR.UTF-8) and cannot be substituted in place.
The value strtod parsed up to that point is kept, as before this change.
Note that changing the locale in another thread *while* strtod runs is
undefined behavior of the C library, which this function cannot prevent.
*/
void convert_float_locale_aware()
{
const bool has_dot = decimal_point_position != std::string::npos;
char decimal_point = get_decimal_point();
for (;;)
{
const bool substitute = has_dot && decimal_point != '.';
if (substitute)
{
token_buffer[decimal_point_position] = static_cast<typename string_t::value_type>(decimal_point);
}
char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg)
strtof(value_float, token_buffer.data(), &endptr);
if (substitute)
{
// get_string() hands the token to the SAX interface with '.'
token_buffer[decimal_point_position] = '.';
}
if (JSON_HEDLEY_LIKELY(endptr == token_buffer.data() + token_buffer.size()))
{
return;
}
// retry only if the locale changed; otherwise, this would loop forever
const char current_decimal_point = get_decimal_point();
if (current_decimal_point == decimal_point)
{
return;
}
decimal_point = current_decimal_point;
}
}
/*!
@brief contiguous fast path for scanning a number
Parses the whole number token straight from the input buffer, avoiding the
per-character get()/add() of scan_number(). On success it fills token_buffer
(as scan_number() does) and
(with the locale decimal point substituted, as scan_number() does) and
returns the token type. On anything it does not fully recognize as a
well-formed number it makes no state change and returns
token_type::uninitialized, so the caller falls back to scan_number(), which
@@ -10847,11 +10804,16 @@ scan_number_done:
}
#endif
// materialize the token exactly as scan_number() would. reset() already
// cleared token_buffer, so append() fills it (assign() is avoided
// because custom string_t types need not provide it)
// materialize the token exactly as scan_number() would, substituting the
// locale decimal point so convert_number()'s strtof fallback stays valid.
// reset() already cleared token_buffer, so append() fills it (assign() is
// avoided because custom string_t types need not provide it)
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
decimal_point_position = dot_index;
if (dot_index != std::string::npos)
{
token_buffer[dot_index] = static_cast<typename string_t::value_type>(decimal_point_char);
decimal_point_position = dot_index;
}
ia.bulk_skip(len - 1);
position.chars_read_total += (len - 1);
@@ -11118,7 +11080,11 @@ scan_number_done:
/// return current string value (implicitly resets the token; useful only once)
string_t& get_string()
{
// a number token holds '.' regardless of the locale (#4084)
// translate decimal points from locale back to '.' (#4084)
if (decimal_point_char != '.' && decimal_point_position != std::string::npos)
{
token_buffer[decimal_point_position] = '.';
}
return token_buffer;
}
@@ -11414,7 +11380,9 @@ scan_number_done:
number_unsigned_t value_unsigned = 0;
number_float_t value_float = 0;
/// the position of the decimal point in token_buffer
/// the decimal point
const char_int_type decimal_point_char = '.';
/// the position of the decimal point in the input
std::size_t decimal_point_position = std::string::npos;
/// whether the caller (e.g. accept()/json_sax_acceptor) only needs the
@@ -14091,80 +14059,6 @@ class binary_reader
}
}
/*!
@brief reads a CBOR object key
RFC 8949 allows any data item as a map key, but only strings have a
counterpart in JSON. A key of any other type is rejected with a message
naming that type, rather than the one @ref get_cbor_string gives for a
malformed string.
@param[out] result created key
@return whether key creation completed
*/
bool get_cbor_object_key(string_t& result)
{
// EOF and major type 3 (text string) are left to get_cbor_string
if (current == char_traits<char_type>::eof() || (static_cast<unsigned int>(current) & 0xE0u) == 0x60u)
{
return get_cbor_string(result);
}
const char* found = nullptr;
switch (static_cast<unsigned int>(current) >> 5u)
{
case 0:
found = "an unsigned integer";
break;
case 1:
found = "a negative integer";
break;
case 2:
found = "a byte string";
break;
case 4:
found = "an array";
break;
case 5:
found = "a map";
break;
case 6:
found = "a tag";
break;
default: // major type 7
switch (current)
{
case 0xF4:
case 0xF5:
found = "a boolean";
break;
case 0xF6:
found = "null";
break;
case 0xF7:
found = "undefined";
break;
case 0xF9:
case 0xFA:
case 0xFB:
found = "a floating-point number";
break;
case 0xFF:
found = "a break stop code";
break;
default:
found = "a simple value";
break;
}
break;
}
auto last_token = get_token_string();
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
exception_message(input_format_t::cbor, concat("only string keys are supported, but found ", found, "; last byte: 0x", last_token), "object key"), nullptr));
}
/*!
@brief reads a definite-length CBOR byte array
@@ -14409,7 +14303,7 @@ class binary_reader
if (top.is_object)
{
key.clear();
if (JSON_HEDLEY_UNLIKELY(!get_cbor_object_key(key) || !sax->key(key)))
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string(key) || !sax->key(key)))
{
return false;
}
@@ -14910,98 +14804,6 @@ class binary_reader
}
}
/*!
@brief reads a MessagePack object key
The MessagePack specification allows any type as a map key, but only
strings have a counterpart in JSON. A key of any other type is rejected
with a message naming that type, rather than the one @ref
get_msgpack_string gives for a malformed string.
@param[out] result created key
@return whether key creation completed
*/
bool get_msgpack_object_key(string_t& result)
{
const char* found = nullptr;
switch (current)
{
case 0xC0:
found = "nil";
break;
case 0xC2:
case 0xC3:
found = "a boolean";
break;
case 0xCA:
case 0xCB:
found = "a float";
break;
case 0xC4:
case 0xC5:
case 0xC6:
found = "a bin";
break;
case 0xC7:
case 0xC8:
case 0xC9:
case 0xD4:
case 0xD5:
case 0xD6:
case 0xD7:
case 0xD8:
found = "an ext";
break;
case 0xCC:
case 0xCD:
case 0xCE:
case 0xCF:
case 0xD0:
case 0xD1:
case 0xD2:
case 0xD3:
found = "an integer";
break;
case 0xDC:
case 0xDD:
found = "an array";
break;
case 0xDE:
case 0xDF:
found = "a map";
break;
default:
// fixint, fixmap, and fixarray; strings, EOF, and the unused
// byte 0xC1 are left to get_msgpack_string
if (current == char_traits<char_type>::eof())
{
return get_msgpack_string(result);
}
if (current <= 0x7F || current >= 0xE0)
{
found = "an integer";
}
else if (current <= 0x8F)
{
found = "a map";
}
else if (current <= 0x9F)
{
found = "an array";
}
else
{
return get_msgpack_string(result);
}
break;
}
auto last_token = get_token_string();
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
exception_message(input_format_t::msgpack, concat("only string keys are supported, but found ", found, "; last byte: 0x", last_token), "object key"), nullptr));
}
/*!
@brief reads a MessagePack byte array
@@ -15164,7 +14966,7 @@ class binary_reader
{
get();
key.clear();
if (JSON_HEDLEY_UNLIKELY(!get_msgpack_object_key(key) || !sax->key(key)))
if (JSON_HEDLEY_UNLIKELY(!get_msgpack_string(key) || !sax->key(key)))
{
return false;
}
@@ -19258,15 +19060,19 @@ class json_pointer
// convert null values to arrays or objects before continuing
if (ptr->is_null())
{
// check if the reference token is a number
const bool nums =
std::all_of(reference_token.begin(), reference_token.end(),
[](const unsigned char x)
// check if the reference token is a valid array index, that is
// a nonempty sequence of digits without a leading '0'
// (cf. RFC 6901, Sect. 4); tokens that could never be a valid
// array index (such as "01" or "") are treated as object keys
const bool nums = !reference_token.empty()
&& (reference_token.size() == 1 || reference_token[0] != '0')
&& std::all_of(reference_token.begin(), reference_token.end(),
[](const unsigned char x)
{
return std::isdigit(x);
});
// change value to an array for numbers or "-" or to object otherwise
// change value to an array for array indices or "-" or to object otherwise
*ptr = (nums || reference_token == "-")
? detail::value_t::array
: detail::value_t::object;
+2 -9
View File
@@ -10,6 +10,7 @@ set(JSON_32bitTest AUTO CACHE STRING "Enable the 32bit unit test (ON/OFF/AUT
set(JSON_TestStandards "" CACHE STRING "The list of standards to test explicitly.")
# using an env var, since this will also affect targets executing cmake (such as "ci_test_compiler_default")
set(JSON_FORCED_GLOBAL_COMPILE_OPTIONS $ENV{JSON_FORCED_GLOBAL_COMPILE_OPTIONS})
if (NOT "" STREQUAL "$ENV{JSON_FORCED_GLOBAL_COMPILE_OPTIONS}")
add_compile_options($ENV{JSON_FORCED_GLOBAL_COMPILE_OPTIONS})
endif()
@@ -94,15 +95,10 @@ target_compile_options(test_main PUBLIC
$<$<CXX_COMPILER_ID:Intel>:-diag-disable=1786>)
target_include_directories(test_main PUBLIC
thirdparty/doctest
thirdparty/fifo_map
${PROJECT_BINARY_DIR}/include)
target_link_libraries(test_main PUBLIC ${NLOHMANN_JSON_TARGET_NAME})
# thirdparty/fifo_map is only used by the #972 regression test, so only
# test-regression1 needs it on its include path (see json_test_set_test_options
# below), rather than every test-* target via test_main.
add_library(fifo_map_include INTERFACE)
target_include_directories(fifo_map_include INTERFACE thirdparty/fifo_map)
#############################################################################
# define test- and standard-specific build settings
#############################################################################
@@ -136,9 +132,6 @@ json_test_set_test_options(test-disabled_exceptions
# raise timeout of expensive Unicode test
json_test_set_test_options(test-unicode4 TEST_PROPERTIES TIMEOUT 3000)
# only the #972 regression test needs thirdparty/fifo_map on its include path
json_test_set_test_options(test-regression1 LINK_LIBRARIES fifo_map_include)
#############################################################################
# add unit tests
#############################################################################
@@ -1,8 +1,6 @@
add_test(NAME cmake_add_subdirectory_configure
COMMAND ${CMAKE_COMMAND}
-G "${CMAKE_GENERATOR}"
-A "${CMAKE_GENERATOR_PLATFORM}"
-T "${CMAKE_GENERATOR_TOOLSET}"
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}
-Dnlohmann_json_source=${PROJECT_SOURCE_DIR}
+17 -16
View File
@@ -1,20 +1,21 @@
add_test(NAME cmake_fetch_content_configure
COMMAND ${CMAKE_COMMAND}
if (${CMAKE_VERSION} VERSION_GREATER "3.11.0")
add_test(NAME cmake_fetch_content_configure
COMMAND ${CMAKE_COMMAND}
-G "${CMAKE_GENERATOR}"
-A "${CMAKE_GENERATOR_PLATFORM}"
-T "${CMAKE_GENERATOR_TOOLSET}"
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}
-Dnlohmann_json_source=${PROJECT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/project
)
add_test(NAME cmake_fetch_content_build
COMMAND ${CMAKE_COMMAND} --build .
)
set_tests_properties(cmake_fetch_content_configure PROPERTIES
FIXTURES_SETUP cmake_fetch_content
LABELS "git_required;not_reproducible"
)
set_tests_properties(cmake_fetch_content_build PROPERTIES
FIXTURES_REQUIRED cmake_fetch_content
LABELS "git_required;not_reproducible"
)
)
add_test(NAME cmake_fetch_content_build
COMMAND ${CMAKE_COMMAND} --build .
)
set_tests_properties(cmake_fetch_content_configure PROPERTIES
FIXTURES_SETUP cmake_fetch_content
LABELS "git_required;not_reproducible"
)
set_tests_properties(cmake_fetch_content_build PROPERTIES
FIXTURES_REQUIRED cmake_fetch_content
LABELS "git_required;not_reproducible"
)
endif()
@@ -4,14 +4,6 @@ project(DummyImport CXX)
include(FetchContent)
# This test deliberately covers the pre-3.14 FetchContent_Populate pattern
# (see FetchContent_MakeAvailable in ../../cmake_fetch_content2/project for
# the 3.14+ alternative), which CMake 3.30 deprecated as CMP0169. Silence the
# deprecation warning on purpose instead of switching to the new pattern.
if(POLICY CMP0169)
cmake_policy(SET CMP0169 OLD)
endif()
get_filename_component(GIT_REPOSITORY_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../../.. ABSOLUTE)
FetchContent_Declare(json GIT_REPOSITORY ${GIT_REPOSITORY_DIRECTORY} GIT_TAG HEAD)
+2 -3
View File
@@ -1,11 +1,10 @@
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.14")
if (${CMAKE_VERSION} VERSION_GREATER "3.14.0")
add_test(NAME cmake_fetch_content2_configure
COMMAND ${CMAKE_COMMAND}
-G "${CMAKE_GENERATOR}"
-A "${CMAKE_GENERATOR_PLATFORM}"
-T "${CMAKE_GENERATOR_TOOLSET}"
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}
-Dnlohmann_json_source=${PROJECT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/project
)
add_test(NAME cmake_fetch_content2_build
-1
View File
@@ -2,7 +2,6 @@ add_test(NAME cmake_import_configure
COMMAND ${CMAKE_COMMAND}
-G "${CMAKE_GENERATOR}"
-A "${CMAKE_GENERATOR_PLATFORM}"
-T "${CMAKE_GENERATOR_TOOLSET}"
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}
-Dnlohmann_json_DIR=${PROJECT_BINARY_DIR}
-1
View File
@@ -2,7 +2,6 @@ add_test(NAME cmake_import_minver_configure
COMMAND ${CMAKE_COMMAND}
-G "${CMAKE_GENERATOR}"
-A "${CMAKE_GENERATOR_PLATFORM}"
-T "${CMAKE_GENERATOR_TOOLSET}"
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}
-Dnlohmann_json_DIR=${PROJECT_BINARY_DIR}
@@ -1,8 +1,6 @@
add_test(NAME cmake_target_include_directories_configure
COMMAND ${CMAKE_COMMAND}
-G "${CMAKE_GENERATOR}"
-A "${CMAKE_GENERATOR_PLATFORM}"
-T "${CMAKE_GENERATOR_TOOLSET}"
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}
-Dnlohmann_json_source=${PROJECT_SOURCE_DIR}
+3 -4
View File
@@ -93,10 +93,9 @@ conventions:
add it as a regression test to the unit test of the affected format (e.g., `tests/src/unit-bjdata.cpp`), with a
comment naming the OSS-Fuzz issue. This way the input is checked by every CI run rather than only by OSS-Fuzz, and
it stays covered even if OSS-Fuzz later closes the report as not reproducible.
- **Keep the fuzzer drivers and the unit tests in sync.** The round-trip checks of the BJData, BON8, BSON, CBOR,
MessagePack and UBJSON drivers are also run on a fixed corpus in the unit tests (see
`tests/src/round_trip_corpus.hpp` and the "round-trip invariants" test cases), so a regression shows up in CI
first. When a driver's checks change, change the unit tests with them.
- **Keep the fuzzer drivers and the unit tests in sync.** The round-trip checks of the UBJSON and BJData drivers are
also run on a fixed corpus in the unit tests (see `tests/src/round_trip_corpus.hpp` and the "round-trip invariants"
test cases), so a regression shows up in CI first. When a driver's checks change, change the unit tests with them.
- **Record in the report whether the bug shipped.** OSS-Fuzz asks whether a crash was a short-lived regression or
affects a released version; answer it when the fix is merged, as it decides whether the fix needs a release note or
a security advisory (see the [security policy](../.github/SECURITY.md)).
Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

+10
View File
@@ -0,0 +1,10 @@
<table style="font-family: 'Trebuchet MS', 'Tahoma', 'Arial', 'Helvetica'">
<tr><td style="width: 18ex"><b>Banner:</b></td><td>fuzz</td></tr>
<tr><td><b>Directory:</b></td><td>fuzz-testing/out</td></tr>
<tr><td><b>Generated on:</b></td><td>Mo 29 Aug 2016 22:14:22 CEST</td></tr>
</table>
<p>
<img src="high_freq.png" width=1000 height=300><p>
<img src="low_freq.png" width=1000 height=200><p>
<img src="exec_speed.png" width=1000 height=200>
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@@ -0,0 +1,31 @@
Results of the latest benchmark from <https://github.com/miloyip/nativejson-benchmark>.
See <https://github.com/nlohmann/json/issues/307> for discussion.
Original post at 2016-09-09 to <json@yahoogroups.com>:
> Hi,
>
> This benchmark evaluated conformance, parse/stringify speed/memory, and
> code size. It can also be viewed as a long list of open source C/C++ JSON
> libraries.
>
> You can run the benchmark on your own machine by checkout this project.
>
> https://github.com/miloyip/nativejson-benchmark
>
> You can also view some sample results here:
>
> https://rawgit.com/miloyip/nativejson-benchmark/master/sample/conformance.html
> https://rawgit.com/miloyip/nativejson-benchmark/master/sample/performance_Corei7-4980HQ@2.80GHz_mac64_clang7.0.html
>
> If you make a new library, you may use this for testing conformance and
> performance. Afterwards, please submit a pull request.
>
> Enjoy!
>
> --
> Milo Yip
>
> https://github.com/miloyip/
> http://twitter.com/miloyip/
@@ -0,0 +1,670 @@
<!DOCTYPE html>
<html lang="en" class="">
<head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# object: http://ogp.me/ns/object# article: http://ogp.me/ns/article# profile: http://ogp.me/ns/profile#">
<meta charset='utf-8'>
<link crossorigin="anonymous" href="https://assets-cdn.github.com/assets/frameworks-3a71f36dec04358c4f2f42280fb2cf5c38856f935a3f609eab0a1ae31b1d635a.css" media="all" rel="stylesheet" />
<link crossorigin="anonymous" href="https://assets-cdn.github.com/assets/github-5c885e880e980dc7f119393287b84906d930ccaff83f6bff8ae2c086b87ca4d8.css" media="all" rel="stylesheet" />
<link crossorigin="anonymous" href="https://assets-cdn.github.com/assets/site-4ef7bbe907458c89cb1f7f5c5e6c4cd87e03acf66b1817325e644920d2a83330.css" media="all" rel="stylesheet" />
<link as="script" href="https://assets-cdn.github.com/assets/frameworks-88471af1fec40ff9418efbe2ddd15b6896af8d772f8179004c254dffc25ea490.js" rel="preload" />
<link as="script" href="https://assets-cdn.github.com/assets/github-e18e11a943ff2eb9394c72d4ec8b76592c454915b5839ae177d422777a046e29.js" rel="preload" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv="Content-Language" content="en">
<meta name="viewport" content="width=device-width">
<title>nativejson-benchmark/conformance_Nlohmann (C++11).md at master · miloyip/nativejson-benchmark · GitHub</title>
<link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="GitHub">
<link rel="fluid-icon" href="https://github.com/fluidicon.png" title="GitHub">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<link rel="apple-touch-icon" sizes="57x57" href="/apple-touch-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/apple-touch-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/apple-touch-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/apple-touch-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/apple-touch-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/apple-touch-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/apple-touch-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/apple-touch-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-180x180.png">
<meta property="fb:app_id" content="1401488693436528">
<meta content="https://avatars2.githubusercontent.com/u/1195774?v=3&amp;s=400" name="twitter:image:src" /><meta content="@github" name="twitter:site" /><meta content="summary" name="twitter:card" /><meta content="miloyip/nativejson-benchmark" name="twitter:title" /><meta content="nativejson-benchmark - C/C++ JSON parser/generator benchmark" name="twitter:description" />
<meta content="https://avatars2.githubusercontent.com/u/1195774?v=3&amp;s=400" property="og:image" /><meta content="GitHub" property="og:site_name" /><meta content="object" property="og:type" /><meta content="miloyip/nativejson-benchmark" property="og:title" /><meta content="https://github.com/miloyip/nativejson-benchmark" property="og:url" /><meta content="nativejson-benchmark - C/C++ JSON parser/generator benchmark" property="og:description" />
<meta name="browser-stats-url" content="https://api.github.com/_private/browser/stats">
<meta name="browser-errors-url" content="https://api.github.com/_private/browser/errors">
<link rel="assets" href="https://assets-cdn.github.com/">
<meta name="pjax-timeout" content="1000">
<meta name="request-id" content="D4563DA7:75ED:525C921:57D6FEBB" data-pjax-transient>
<meta name="msapplication-TileImage" content="/windows-tile.png">
<meta name="msapplication-TileColor" content="#ffffff">
<meta name="selected-link" value="repo_source" data-pjax-transient>
<meta name="google-site-verification" content="KT5gs8h0wvaagLKAVWq8bbeNwnZZK1r1XQysX3xurLU">
<meta name="google-site-verification" content="ZzhVyEFwb7w3e0-uOTltm8Jsck2F5StVihD0exw2fsA">
<meta name="google-analytics" content="UA-3769691-2">
<meta content="collector.githubapp.com" name="octolytics-host" /><meta content="github" name="octolytics-app-id" /><meta content="D4563DA7:75ED:525C921:57D6FEBB" name="octolytics-dimension-request_id" />
<meta content="/&lt;user-name&gt;/&lt;repo-name&gt;/blob/show" data-pjax-transient="true" name="analytics-location" />
<meta class="js-ga-set" name="dimension1" content="Logged Out">
<meta name="hostname" content="github.com">
<meta name="user-login" content="">
<meta name="expected-hostname" content="github.com">
<meta name="js-proxy-site-detection-payload" content="N2RkMmY1ZjE1MTA4MzRhYTA5NDNkMjliNDU3OTA3ZTdlMGNmNDZjN2QyODBiZjM3MmYzMjFjNzY1ZjIwNjY4NHx7InJlbW90ZV9hZGRyZXNzIjoiMjEyLjg2LjYxLjE2NyIsInJlcXVlc3RfaWQiOiJENDU2M0RBNzo3NUVEOjUyNUM5MjE6NTdENkZFQkIiLCJ0aW1lc3RhbXAiOjE0NzM3MDc3MDh9">
<link rel="mask-icon" href="https://assets-cdn.github.com/pinned-octocat.svg" color="#4078c0">
<link rel="icon" type="image/x-icon" href="https://assets-cdn.github.com/favicon.ico">
<meta name="html-safe-nonce" content="deea0f406df2fb95709865c5ee80a255d8c47fa9">
<meta content="736eea9d74cf34fe850d2180e8a5f0a1cc7bc0be" name="form-nonce" />
<meta http-equiv="x-pjax-version" content="f302977937abe3fe1c9a4d4bed913565">
<meta name="description" content="nativejson-benchmark - C/C++ JSON parser/generator benchmark">
<meta name="go-import" content="github.com/miloyip/nativejson-benchmark git https://github.com/miloyip/nativejson-benchmark.git">
<meta content="1195774" name="octolytics-dimension-user_id" /><meta content="miloyip" name="octolytics-dimension-user_login" /><meta content="22976798" name="octolytics-dimension-repository_id" /><meta content="miloyip/nativejson-benchmark" name="octolytics-dimension-repository_nwo" /><meta content="true" name="octolytics-dimension-repository_public" /><meta content="false" name="octolytics-dimension-repository_is_fork" /><meta content="22976798" name="octolytics-dimension-repository_network_root_id" /><meta content="miloyip/nativejson-benchmark" name="octolytics-dimension-repository_network_root_nwo" />
<link href="https://github.com/miloyip/nativejson-benchmark/commits/master.atom" rel="alternate" title="Recent Commits to nativejson-benchmark:master" type="application/atom+xml">
<link rel="canonical" href="https://github.com/miloyip/nativejson-benchmark/blob/master/sample/conformance_Nlohmann%20(C%2B%2B11).md" data-pjax-transient>
</head>
<body class="logged-out env-production vis-public page-blob">
<div id="js-pjax-loader-bar" class="pjax-loader-bar"><div class="progress"></div></div>
<a href="#start-of-content" tabindex="1" class="accessibility-aid js-skip-to-content">Skip to content</a>
<header class="site-header js-details-container" role="banner">
<div class="container-responsive">
<a class="header-logo-invertocat" href="https://github.com/" aria-label="Homepage" data-ga-click="(Logged out) Header, go to homepage, icon:logo-wordmark">
<svg aria-hidden="true" class="octicon octicon-mark-github" height="32" version="1.1" viewBox="0 0 16 16" width="32"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"></path></svg>
</a>
<button class="btn-link float-right site-header-toggle js-details-target" type="button" aria-label="Toggle navigation">
<svg aria-hidden="true" class="octicon octicon-three-bars" height="24" version="1.1" viewBox="0 0 12 16" width="18"><path d="M11.41 9H.59C0 9 0 8.59 0 8c0-.59 0-1 .59-1H11.4c.59 0 .59.41.59 1 0 .59 0 1-.59 1h.01zm0-4H.59C0 5 0 4.59 0 4c0-.59 0-1 .59-1H11.4c.59 0 .59.41.59 1 0 .59 0 1-.59 1h.01zM.59 11H11.4c.59 0 .59.41.59 1 0 .59 0 1-.59 1H.59C0 13 0 12.59 0 12c0-.59 0-1 .59-1z"></path></svg>
</button>
<div class="site-header-menu">
<nav class="site-header-nav site-header-nav-main">
<a href="/personal" class="js-selected-navigation-item nav-item nav-item-personal" data-ga-click="Header, click, Nav menu - item:personal" data-selected-links="/personal /personal">
Personal
</a> <a href="/open-source" class="js-selected-navigation-item nav-item nav-item-opensource" data-ga-click="Header, click, Nav menu - item:opensource" data-selected-links="/open-source /open-source">
Open source
</a> <a href="/business" class="js-selected-navigation-item nav-item nav-item-business" data-ga-click="Header, click, Nav menu - item:business" data-selected-links="/business /business/partners /business/features /business/customers /business">
Business
</a> <a href="/explore" class="js-selected-navigation-item nav-item nav-item-explore" data-ga-click="Header, click, Nav menu - item:explore" data-selected-links="/explore /trending /trending/developers /integrations /integrations/feature/code /integrations/feature/collaborate /integrations/feature/ship /explore">
Explore
</a> </nav>
<div class="site-header-actions">
<a class="btn btn-primary site-header-actions-btn" href="/join?source=header-repo" data-ga-click="(Logged out) Header, clicked Sign up, text:sign-up">Sign up</a>
<a class="btn site-header-actions-btn mr-2" href="/login?return_to=%2Fmiloyip%2Fnativejson-benchmark%2Fblob%2Fmaster%2Fsample%2Fconformance_Nlohmann%2520%28C%252B%252B11%29.md" data-ga-click="(Logged out) Header, clicked Sign in, text:sign-in">Sign in</a>
</div>
<nav class="site-header-nav site-header-nav-secondary">
<a class="nav-item" href="/pricing">Pricing</a>
<a class="nav-item" href="/blog">Blog</a>
<a class="nav-item" href="https://help.github.com">Support</a>
<a class="nav-item header-search-link" href="https://github.com/search">Search GitHub</a>
<div class="header-search scoped-search site-scoped-search js-site-search" role="search">
<!-- </textarea> --><!-- '"` --><form accept-charset="UTF-8" action="/miloyip/nativejson-benchmark/search" class="js-site-search-form" data-scoped-search-url="/miloyip/nativejson-benchmark/search" data-unscoped-search-url="/search" method="get"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="&#x2713;" /></div>
<label class="form-control header-search-wrapper js-chromeless-input-container">
<div class="header-search-scope">This repository</div>
<input type="text"
class="form-control header-search-input js-site-search-focus js-site-search-field is-clearable"
data-hotkey="s"
name="q"
placeholder="Search"
aria-label="Search this repository"
data-unscoped-placeholder="Search GitHub"
data-scoped-placeholder="Search"
autocapitalize="off">
</label>
</form></div>
</nav>
</div>
</div>
</header>
<div id="start-of-content" class="accessibility-aid"></div>
<div id="js-flash-container">
</div>
<div role="main">
<div itemscope itemtype="http://schema.org/SoftwareSourceCode">
<div id="js-repo-pjax-container" data-pjax-container>
<div class="pagehead repohead instapaper_ignore readability-menu experiment-repo-nav">
<div class="container repohead-details-container">
<ul class="pagehead-actions">
<li>
<a href="/login?return_to=%2Fmiloyip%2Fnativejson-benchmark"
class="btn btn-sm btn-with-count tooltipped tooltipped-n"
aria-label="You must be signed in to watch a repository" rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-eye" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M8.06 2C3 2 0 8 0 8s3 6 8.06 6C13 14 16 8 16 8s-3-6-7.94-6zM8 12c-2.2 0-4-1.78-4-4 0-2.2 1.8-4 4-4 2.22 0 4 1.8 4 4 0 2.22-1.78 4-4 4zm2-4c0 1.11-.89 2-2 2-1.11 0-2-.89-2-2 0-1.11.89-2 2-2 1.11 0 2 .89 2 2z"></path></svg>
Watch
</a>
<a class="social-count" href="/miloyip/nativejson-benchmark/watchers"
aria-label="40 users are watching this repository">
40
</a>
</li>
<li>
<a href="/login?return_to=%2Fmiloyip%2Fnativejson-benchmark"
class="btn btn-sm btn-with-count tooltipped tooltipped-n"
aria-label="You must be signed in to star a repository" rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-star" height="16" version="1.1" viewBox="0 0 14 16" width="14"><path d="M14 6l-4.9-.64L7 1 4.9 5.36 0 6l3.6 3.26L2.67 14 7 11.67 11.33 14l-.93-4.74z"></path></svg>
Star
</a>
<a class="social-count js-social-count" href="/miloyip/nativejson-benchmark/stargazers"
aria-label="352 users starred this repository">
352
</a>
</li>
<li>
<a href="/login?return_to=%2Fmiloyip%2Fnativejson-benchmark"
class="btn btn-sm btn-with-count tooltipped tooltipped-n"
aria-label="You must be signed in to fork a repository" rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-repo-forked" height="16" version="1.1" viewBox="0 0 10 16" width="10"><path d="M8 1a1.993 1.993 0 0 0-1 3.72V6L5 8 3 6V4.72A1.993 1.993 0 0 0 2 1a1.993 1.993 0 0 0-1 3.72V6.5l3 3v1.78A1.993 1.993 0 0 0 5 15a1.993 1.993 0 0 0 1-3.72V9.5l3-3V4.72A1.993 1.993 0 0 0 8 1zM2 4.2C1.34 4.2.8 3.65.8 3c0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zm3 10c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zm3-10c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2z"></path></svg>
Fork
</a>
<a href="/miloyip/nativejson-benchmark/network" class="social-count"
aria-label="59 users are forked this repository">
59
</a>
</li>
</ul>
<h1 class="public ">
<svg aria-hidden="true" class="octicon octicon-repo" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path d="M4 9H3V8h1v1zm0-3H3v1h1V6zm0-2H3v1h1V4zm0-2H3v1h1V2zm8-1v12c0 .55-.45 1-1 1H6v2l-1.5-1.5L3 16v-2H1c-.55 0-1-.45-1-1V1c0-.55.45-1 1-1h10c.55 0 1 .45 1 1zm-1 10H1v2h2v-1h3v1h5v-2zm0-10H2v9h9V1z"></path></svg>
<span class="author" itemprop="author"><a href="/miloyip" class="url fn" rel="author">miloyip</a></span><!--
--><span class="path-divider">/</span><!--
--><strong itemprop="name"><a href="/miloyip/nativejson-benchmark" data-pjax="#js-repo-pjax-container">nativejson-benchmark</a></strong>
</h1>
</div>
<div class="container">
<nav class="reponav js-repo-nav js-sidenav-container-pjax"
itemscope
itemtype="http://schema.org/BreadcrumbList"
role="navigation"
data-pjax="#js-repo-pjax-container">
<span itemscope itemtype="http://schema.org/ListItem" itemprop="itemListElement">
<a href="/miloyip/nativejson-benchmark" aria-selected="true" class="js-selected-navigation-item selected reponav-item" data-hotkey="g c" data-selected-links="repo_source repo_downloads repo_commits repo_releases repo_tags repo_branches /miloyip/nativejson-benchmark" itemprop="url">
<svg aria-hidden="true" class="octicon octicon-code" height="16" version="1.1" viewBox="0 0 14 16" width="14"><path d="M9.5 3L8 4.5 11.5 8 8 11.5 9.5 13 14 8 9.5 3zm-5 0L0 8l4.5 5L6 11.5 2.5 8 6 4.5 4.5 3z"></path></svg>
<span itemprop="name">Code</span>
<meta itemprop="position" content="1">
</a> </span>
<span itemscope itemtype="http://schema.org/ListItem" itemprop="itemListElement">
<a href="/miloyip/nativejson-benchmark/issues" class="js-selected-navigation-item reponav-item" data-hotkey="g i" data-selected-links="repo_issues repo_labels repo_milestones /miloyip/nativejson-benchmark/issues" itemprop="url">
<svg aria-hidden="true" class="octicon octicon-issue-opened" height="16" version="1.1" viewBox="0 0 14 16" width="14"><path d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg>
<span itemprop="name">Issues</span>
<span class="counter">8</span>
<meta itemprop="position" content="2">
</a> </span>
<span itemscope itemtype="http://schema.org/ListItem" itemprop="itemListElement">
<a href="/miloyip/nativejson-benchmark/pulls" class="js-selected-navigation-item reponav-item" data-hotkey="g p" data-selected-links="repo_pulls /miloyip/nativejson-benchmark/pulls" itemprop="url">
<svg aria-hidden="true" class="octicon octicon-git-pull-request" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path d="M11 11.28V5c-.03-.78-.34-1.47-.94-2.06C9.46 2.35 8.78 2.03 8 2H7V0L4 3l3 3V4h1c.27.02.48.11.69.31.21.2.3.42.31.69v6.28A1.993 1.993 0 0 0 10 15a1.993 1.993 0 0 0 1-3.72zm-1 2.92c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zM4 3c0-1.11-.89-2-2-2a1.993 1.993 0 0 0-1 3.72v6.56A1.993 1.993 0 0 0 2 15a1.993 1.993 0 0 0 1-3.72V4.72c.59-.34 1-.98 1-1.72zm-.8 10c0 .66-.55 1.2-1.2 1.2-.65 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2zM2 4.2C1.34 4.2.8 3.65.8 3c0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2z"></path></svg>
<span itemprop="name">Pull requests</span>
<span class="counter">1</span>
<meta itemprop="position" content="3">
</a> </span>
<a href="/miloyip/nativejson-benchmark/pulse" class="js-selected-navigation-item reponav-item" data-selected-links="pulse /miloyip/nativejson-benchmark/pulse">
<svg aria-hidden="true" class="octicon octicon-pulse" height="16" version="1.1" viewBox="0 0 14 16" width="14"><path d="M11.5 8L8.8 5.4 6.6 8.5 5.5 1.6 2.38 8H0v2h3.6l.9-1.8.9 5.4L9 8.5l1.6 1.5H14V8z"></path></svg>
Pulse
</a>
<a href="/miloyip/nativejson-benchmark/graphs" class="js-selected-navigation-item reponav-item" data-selected-links="repo_graphs repo_contributors /miloyip/nativejson-benchmark/graphs">
<svg aria-hidden="true" class="octicon octicon-graph" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M16 14v1H0V0h1v14h15zM5 13H3V8h2v5zm4 0H7V3h2v10zm4 0h-2V6h2v7z"></path></svg>
Graphs
</a>
</nav>
</div>
</div>
<div class="container new-discussion-timeline experiment-repo-nav">
<div class="repository-content">
<a href="/miloyip/nativejson-benchmark/blob/95f27ebcf9a96c7ca4cee26467ed5420140090fb/sample/conformance_Nlohmann%20(C%2B%2B11).md" class="d-none js-permalink-shortcut" data-hotkey="y">Permalink</a>
<!-- blob contrib key: blob_contributors:v21:0bf9e3593dedd91db6c9dc69e13b7f95 -->
<div class="file-navigation js-zeroclipboard-container">
<div class="select-menu branch-select-menu js-menu-container js-select-menu float-left">
<button class="btn btn-sm select-menu-button js-menu-target css-truncate" data-hotkey="w"
type="button" aria-label="Switch branches or tags" tabindex="0" aria-haspopup="true">
<i>Branch:</i>
<span class="js-select-button css-truncate-target">master</span>
</button>
<div class="select-menu-modal-holder js-menu-content js-navigation-container" data-pjax aria-hidden="true">
<div class="select-menu-modal">
<div class="select-menu-header">
<svg aria-label="Close" class="octicon octicon-x js-menu-close" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48z"></path></svg>
<span class="select-menu-title">Switch branches/tags</span>
</div>
<div class="select-menu-filters">
<div class="select-menu-text-filter">
<input type="text" aria-label="Filter branches/tags" id="context-commitish-filter-field" class="form-control js-filterable-field js-navigation-enable" placeholder="Filter branches/tags">
</div>
<div class="select-menu-tabs">
<ul>
<li class="select-menu-tab">
<a href="#" data-tab-filter="branches" data-filter-placeholder="Filter branches/tags" class="js-select-menu-tab" role="tab">Branches</a>
</li>
<li class="select-menu-tab">
<a href="#" data-tab-filter="tags" data-filter-placeholder="Find a tag…" class="js-select-menu-tab" role="tab">Tags</a>
</li>
</ul>
</div>
</div>
<div class="select-menu-list select-menu-tab-bucket js-select-menu-tab-bucket" data-tab-filter="branches" role="menu">
<div data-filterable-for="context-commitish-filter-field" data-filterable-type="substring">
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/miloyip/nativejson-benchmark/blob/Stixjson/sample/conformance_Nlohmann%20(C++11).md"
data-name="Stixjson"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"></path></svg>
<span class="select-menu-item-text css-truncate-target js-select-menu-filter-text">
Stixjson
</span>
</a>
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/miloyip/nativejson-benchmark/blob/ccan/sample/conformance_Nlohmann%20(C++11).md"
data-name="ccan"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"></path></svg>
<span class="select-menu-item-text css-truncate-target js-select-menu-filter-text">
ccan
</span>
</a>
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/miloyip/nativejson-benchmark/blob/jbson/sample/conformance_Nlohmann%20(C++11).md"
data-name="jbson"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"></path></svg>
<span class="select-menu-item-text css-truncate-target js-select-menu-filter-text">
jbson
</span>
</a>
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/miloyip/nativejson-benchmark/blob/jute/sample/conformance_Nlohmann%20(C++11).md"
data-name="jute"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"></path></svg>
<span class="select-menu-item-text css-truncate-target js-select-menu-filter-text">
jute
</span>
</a>
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/miloyip/nativejson-benchmark/blob/lastjson/sample/conformance_Nlohmann%20(C++11).md"
data-name="lastjson"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"></path></svg>
<span class="select-menu-item-text css-truncate-target js-select-menu-filter-text">
lastjson
</span>
</a>
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/miloyip/nativejson-benchmark/blob/libjson/sample/conformance_Nlohmann%20(C++11).md"
data-name="libjson"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"></path></svg>
<span class="select-menu-item-text css-truncate-target js-select-menu-filter-text">
libjson
</span>
</a>
<a class="select-menu-item js-navigation-item js-navigation-open selected"
href="/miloyip/nativejson-benchmark/blob/master/sample/conformance_Nlohmann%20(C++11).md"
data-name="master"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"></path></svg>
<span class="select-menu-item-text css-truncate-target js-select-menu-filter-text">
master
</span>
</a>
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/miloyip/nativejson-benchmark/blob/qt/sample/conformance_Nlohmann%20(C++11).md"
data-name="qt"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"></path></svg>
<span class="select-menu-item-text css-truncate-target js-select-menu-filter-text">
qt
</span>
</a>
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/miloyip/nativejson-benchmark/blob/tunnuz/sample/conformance_Nlohmann%20(C++11).md"
data-name="tunnuz"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"></path></svg>
<span class="select-menu-item-text css-truncate-target js-select-menu-filter-text">
tunnuz
</span>
</a>
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/miloyip/nativejson-benchmark/blob/ujson/sample/conformance_Nlohmann%20(C++11).md"
data-name="ujson"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"></path></svg>
<span class="select-menu-item-text css-truncate-target js-select-menu-filter-text">
ujson
</span>
</a>
</div>
<div class="select-menu-no-results">Nothing to show</div>
</div>
<div class="select-menu-list select-menu-tab-bucket js-select-menu-tab-bucket" data-tab-filter="tags">
<div data-filterable-for="context-commitish-filter-field" data-filterable-type="substring">
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/miloyip/nativejson-benchmark/tree/v1.0.0/sample/conformance_Nlohmann%20(C%2B%2B11).md"
data-name="v1.0.0"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"></path></svg>
<span class="select-menu-item-text css-truncate-target" title="v1.0.0">
v1.0.0
</span>
</a>
</div>
<div class="select-menu-no-results">Nothing to show</div>
</div>
</div>
</div>
</div>
<div class="btn-group float-right">
<a href="/miloyip/nativejson-benchmark/find/master"
class="js-pjax-capture-input btn btn-sm"
data-pjax
data-hotkey="t">
Find file
</a>
<button aria-label="Copy file path to clipboard" class="js-zeroclipboard btn btn-sm zeroclipboard-button tooltipped tooltipped-s" data-copied-hint="Copied!" type="button">Copy path</button>
</div>
<div class="breadcrumb js-zeroclipboard-target">
<span class="repo-root js-repo-root"><span class="js-path-segment"><a href="/miloyip/nativejson-benchmark"><span>nativejson-benchmark</span></a></span></span><span class="separator">/</span><span class="js-path-segment"><a href="/miloyip/nativejson-benchmark/tree/master/sample"><span>sample</span></a></span><span class="separator">/</span><strong class="final-path">conformance_Nlohmann (C++11).md</strong>
</div>
</div>
<div class="commit-tease">
<span class="float-right">
<a class="commit-tease-sha" href="/miloyip/nativejson-benchmark/commit/a4a9f10f41c515d6abb0f019ab9f5d021ed4bb9e" data-pjax>
a4a9f10
</a>
<relative-time datetime="2016-09-09T03:15:21Z">Sep 9, 2016</relative-time>
</span>
<div>
<img alt="@miloyip" class="avatar" height="20" src="https://avatars3.githubusercontent.com/u/1195774?v=3&amp;s=40" width="20" />
<a href="/miloyip" class="user-mention" rel="author">miloyip</a>
<a href="/miloyip/nativejson-benchmark/commit/a4a9f10f41c515d6abb0f019ab9f5d021ed4bb9e" class="message" data-pjax="true" title="Update sample result for 41 libraries
Fixed #43">Update sample result for 41 libraries</a>
</div>
<div class="commit-tease-contributors">
<button type="button" class="btn-link muted-link contributors-toggle" data-facebox="#blob_contributors_box">
<strong>1</strong>
contributor
</button>
</div>
<div id="blob_contributors_box" style="display:none">
<h2 class="facebox-header" data-facebox-id="facebox-header">Users who have contributed to this file</h2>
<ul class="facebox-user-list" data-facebox-id="facebox-description">
<li class="facebox-user-list-item">
<img alt="@miloyip" height="24" src="https://avatars1.githubusercontent.com/u/1195774?v=3&amp;s=48" width="24" />
<a href="/miloyip">miloyip</a>
</li>
</ul>
</div>
</div>
<div class="file">
<div class="file-header">
<div class="file-actions">
<div class="btn-group">
<a href="/miloyip/nativejson-benchmark/raw/master/sample/conformance_Nlohmann%20(C%2B%2B11).md" class="btn btn-sm " id="raw-url">Raw</a>
<a href="/miloyip/nativejson-benchmark/blame/master/sample/conformance_Nlohmann%20(C%2B%2B11).md" class="btn btn-sm js-update-url-with-hash">Blame</a>
<a href="/miloyip/nativejson-benchmark/commits/master/sample/conformance_Nlohmann%20(C%2B%2B11).md" class="btn btn-sm " rel="nofollow">History</a>
</div>
<button type="button" class="btn-octicon disabled tooltipped tooltipped-nw"
aria-label="You must be signed in to make or propose changes">
<svg aria-hidden="true" class="octicon octicon-pencil" height="16" version="1.1" viewBox="0 0 14 16" width="14"><path d="M0 12v3h3l8-8-3-3-8 8zm3 2H1v-2h1v1h1v1zm10.3-9.3L12 6 9 3l1.3-1.3a.996.996 0 0 1 1.41 0l1.59 1.59c.39.39.39 1.02 0 1.41z"></path></svg>
</button>
<button type="button" class="btn-octicon btn-octicon-danger disabled tooltipped tooltipped-nw"
aria-label="You must be signed in to make or propose changes">
<svg aria-hidden="true" class="octicon octicon-trashcan" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path d="M11 2H9c0-.55-.45-1-1-1H5c-.55 0-1 .45-1 1H2c-.55 0-1 .45-1 1v1c0 .55.45 1 1 1v9c0 .55.45 1 1 1h7c.55 0 1-.45 1-1V5c.55 0 1-.45 1-1V3c0-.55-.45-1-1-1zm-1 12H3V5h1v8h1V5h1v8h1V5h1v8h1V5h1v9zm1-10H2V3h9v1z"></path></svg>
</button>
</div>
<div class="file-info">
59 lines (37 sloc)
<span class="file-info-divider"></span>
545 Bytes
</div>
</div>
<div id="readme" class="readme blob instapaper_body">
<article class="markdown-body entry-content" itemprop="text"><h1><a id="user-content-conformance-of-nlohmann-c11" class="anchor" href="#conformance-of-nlohmann-c11" aria-hidden="true"><svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Conformance of Nlohmann (C++11)</h1>
<h2><a id="user-content-1-parse-validation" class="anchor" href="#1-parse-validation" aria-hidden="true"><svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>1. Parse Validation</h2>
<p>Summary: 34 of 34 are correct.</p>
<h2><a id="user-content-2-parse-double" class="anchor" href="#2-parse-double" aria-hidden="true"><svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>2. Parse Double</h2>
<p>Summary: 66 of 66 are correct.</p>
<h2><a id="user-content-3-parse-string" class="anchor" href="#3-parse-string" aria-hidden="true"><svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>3. Parse String</h2>
<p>Summary: 9 of 9 are correct.</p>
<h2><a id="user-content-4-roundtrip" class="anchor" href="#4-roundtrip" aria-hidden="true"><svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>4. Roundtrip</h2>
<ul>
<li>Fail:</li>
</ul>
<div class="highlight highlight-source-js"><pre>[<span class="pl-c1">5e-324</span>]</pre></div>
<div class="highlight highlight-source-js"><pre>[<span class="pl-c1">4.94065645841247e-324</span>]</pre></div>
<ul>
<li>Fail:</li>
</ul>
<div class="highlight highlight-source-js"><pre>[<span class="pl-c1">2.225073858507201e-308</span>]</pre></div>
<div class="highlight highlight-source-js"><pre>[<span class="pl-c1">2.2250738585072e-308</span>]</pre></div>
<ul>
<li>Fail:</li>
</ul>
<div class="highlight highlight-source-js"><pre>[<span class="pl-c1">2.2250738585072014e-308</span>]</pre></div>
<div class="highlight highlight-source-js"><pre>[<span class="pl-c1">2.2250738585072e-308</span>]</pre></div>
<ul>
<li>Fail:</li>
</ul>
<div class="highlight highlight-source-js"><pre>[<span class="pl-c1">1.7976931348623157e308</span>]</pre></div>
<div class="highlight highlight-source-js"><pre>[<span class="pl-c1">1.79769313486232e+308</span>]</pre></div>
<p>Summary: 23 of 27 are correct.</p>
</article>
</div>
</div>
<button type="button" data-facebox="#jump-to-line" data-facebox-class="linejump" data-hotkey="l" class="d-none">Jump to Line</button>
<div id="jump-to-line" style="display:none">
<!-- </textarea> --><!-- '"` --><form accept-charset="UTF-8" action="" class="js-jump-to-line-form" method="get"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="&#x2713;" /></div>
<input class="form-control linejump-input js-jump-to-line-field" type="text" placeholder="Jump to line&hellip;" aria-label="Jump to line" autofocus>
<button type="submit" class="btn">Go</button>
</form></div>
</div>
<div class="modal-backdrop js-touch-events"></div>
</div>
</div>
</div>
</div>
<div class="container site-footer-container">
<div class="site-footer" role="contentinfo">
<ul class="site-footer-links float-right">
<li><a href="https://github.com/contact" data-ga-click="Footer, go to contact, text:contact">Contact GitHub</a></li>
<li><a href="https://developer.github.com" data-ga-click="Footer, go to api, text:api">API</a></li>
<li><a href="https://training.github.com" data-ga-click="Footer, go to training, text:training">Training</a></li>
<li><a href="https://shop.github.com" data-ga-click="Footer, go to shop, text:shop">Shop</a></li>
<li><a href="https://github.com/blog" data-ga-click="Footer, go to blog, text:blog">Blog</a></li>
<li><a href="https://github.com/about" data-ga-click="Footer, go to about, text:about">About</a></li>
</ul>
<a href="https://github.com" aria-label="Homepage" class="site-footer-mark" title="GitHub">
<svg aria-hidden="true" class="octicon octicon-mark-github" height="24" version="1.1" viewBox="0 0 16 16" width="24"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"></path></svg>
</a>
<ul class="site-footer-links">
<li>&copy; 2016 <span title="0.18611s from github-fe151-cp1-prd.iad.github.net">GitHub</span>, Inc.</li>
<li><a href="https://github.com/site/terms" data-ga-click="Footer, go to terms, text:terms">Terms</a></li>
<li><a href="https://github.com/site/privacy" data-ga-click="Footer, go to privacy, text:privacy">Privacy</a></li>
<li><a href="https://github.com/security" data-ga-click="Footer, go to security, text:security">Security</a></li>
<li><a href="https://status.github.com/" data-ga-click="Footer, go to status, text:status">Status</a></li>
<li><a href="https://help.github.com" data-ga-click="Footer, go to help, text:help">Help</a></li>
</ul>
</div>
</div>
<div id="ajax-error-message" class="ajax-error-message flash flash-error">
<svg aria-hidden="true" class="octicon octicon-alert" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M8.865 1.52c-.18-.31-.51-.5-.87-.5s-.69.19-.87.5L.275 13.5c-.18.31-.18.69 0 1 .19.31.52.5.87.5h13.7c.36 0 .69-.19.86-.5.17-.31.18-.69.01-1L8.865 1.52zM8.995 13h-2v-2h2v2zm0-3h-2V6h2v4z"></path></svg>
<button type="button" class="flash-close js-flash-close js-ajax-error-dismiss" aria-label="Dismiss error">
<svg aria-hidden="true" class="octicon octicon-x" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48z"></path></svg>
</button>
You can't perform that action at this time.
</div>
<script crossorigin="anonymous" src="https://assets-cdn.github.com/assets/compat-40e365359d1c4db1e36a55be458e60f2b7c24d58b5a00ae13398480e7ba768e0.js"></script>
<script crossorigin="anonymous" src="https://assets-cdn.github.com/assets/frameworks-88471af1fec40ff9418efbe2ddd15b6896af8d772f8179004c254dffc25ea490.js"></script>
<script async="async" crossorigin="anonymous" src="https://assets-cdn.github.com/assets/github-e18e11a943ff2eb9394c72d4ec8b76592c454915b5839ae177d422777a046e29.js"></script>
<div class="js-stale-session-flash stale-session-flash flash flash-warn flash-banner d-none">
<svg aria-hidden="true" class="octicon octicon-alert" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M8.865 1.52c-.18-.31-.51-.5-.87-.5s-.69.19-.87.5L.275 13.5c-.18.31-.18.69 0 1 .19.31.52.5.87.5h13.7c.36 0 .69-.19.86-.5.17-.31.18-.69.01-1L8.865 1.52zM8.995 13h-2v-2h2v2zm0-3h-2V6h2v4z"></path></svg>
<span class="signed-in-tab-flash">You signed in with another tab or window. <a href="">Reload</a> to refresh your session.</span>
<span class="signed-out-tab-flash">You signed out in another tab or window. <a href="">Reload</a> to refresh your session.</span>
</div>
<div class="facebox" id="facebox" style="display:none;">
<div class="facebox-popup">
<div class="facebox-content" role="dialog" aria-labelledby="facebox-header" aria-describedby="facebox-description">
</div>
<button type="button" class="facebox-close js-facebox-close" aria-label="Close modal">
<svg aria-hidden="true" class="octicon octicon-x" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48z"></path></svg>
</button>
</div>
</div>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

+10
View File
@@ -0,0 +1,10 @@
<table style="font-family: 'Trebuchet MS', 'Tahoma', 'Arial', 'Helvetica'">
<tr><td style="width: 18ex"><b>Banner:</b></td><td>fuzz</td></tr>
<tr><td><b>Directory:</b></td><td>fuzz-testing/out</td></tr>
<tr><td><b>Generated on:</b></td><td>Sun Oct 2 08:51:02 CEST 2016</td></tr>
</table>
<p>
<img src="high_freq.png" width=1000 height=300><p>
<img src="low_freq.png" width=1000 height=200><p>
<img src="exec_speed.png" width=1000 height=200>
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+11 -9
View File
@@ -11,15 +11,15 @@ This file implements a parser test suitable for fuzz testing. Given a byte
array data, it performs the following steps:
- j1 = from_bjdata(data)
- vec2 = to_bjdata(j1, use_size = false, use_type = false)
- vec3 = to_bjdata(j1, use_size = true, use_type = false)
- vec4 = to_bjdata(j1, use_size = true, use_type = true)
- j2 = from_bjdata(vec2)
- j3 = from_bjdata(vec3)
- j4 = from_bjdata(vec4)
- assert(from_bjdata(to_bjdata(j2, use_size = false, use_type = false)) is value-stable with j2)
- assert(from_bjdata(to_bjdata(j3, use_size = true, use_type = false)) is value-stable with j3)
- assert(from_bjdata(to_bjdata(j4, use_size = true, use_type = true)) is value-stable with j4)
- vec = to_bjdata(j1)
- j2 = from_bjdata(vec)
- assert(j1 == j2)
- vec2 = to_bjdata(j1, use_size = true, use_type = false)
- j3 = from_bjdata(vec2)
- assert(j1 == j3)
- vec3 = to_bjdata(j1, use_size = true, use_type = true)
- j4 = from_bjdata(vec3)
- assert(j1 == j4)
Re-serializing j2/j3/j4 with the same use_size/use_type settings is checked
for value-stability rather than byte-exact stability: from_bjdata(to_bjdata(j2))
@@ -50,6 +50,8 @@ drivers.
*/
#include <cassert>
#include <iostream>
#include <sstream>
#include <nlohmann/json.hpp>
// the round-trip checks below are assertions; NDEBUG would compile them away
+2 -1
View File
@@ -13,7 +13,7 @@ array data, it performs the following steps:
- j1 = from_bon8(data)
- vec = to_bon8(j1)
- j2 = from_bon8(vec)
- assert(to_bon8(j2) == vec)
- assert(j1 == j2)
It also checks that reading the data from a stream, which reads strings byte by
byte, gives the same value or error as reading it from contiguous memory, which
@@ -24,6 +24,7 @@ drivers.
*/
#include <cassert>
#include <iostream>
#include <sstream>
#include <nlohmann/json.hpp>
+8 -1
View File
@@ -13,13 +13,15 @@ array data, it performs the following steps:
- j1 = from_bson(data)
- vec = to_bson(j1)
- j2 = from_bson(vec)
- assert(to_bson(j2) == vec)
- assert(j1 == j2)
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
drivers.
*/
#include <cassert>
#include <iostream>
#include <sstream>
#include <nlohmann/json.hpp>
// the round-trip checks below are assertions; NDEBUG would compile them away
@@ -38,6 +40,11 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
std::vector<uint8_t> const vec1(data, data + size);
json const j1 = json::from_bson(vec1);
if (j1.is_discarded())
{
return 0;
}
try
{
// step 2: round trip
+3 -1
View File
@@ -13,13 +13,15 @@ array data, it performs the following steps:
- j1 = from_cbor(data)
- vec = to_cbor(j1)
- j2 = from_cbor(vec)
- assert(to_cbor(j2) == vec)
- assert(j1 == j2)
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
drivers.
*/
#include <cassert>
#include <iostream>
#include <sstream>
#include <nlohmann/json.hpp>
// the round-trip checks below are assertions; NDEBUG would compile them away
+2
View File
@@ -21,6 +21,8 @@ drivers.
*/
#include <cassert>
#include <iostream>
#include <sstream>
#include <nlohmann/json.hpp>
// the round-trip checks below are assertions; NDEBUG would compile them away
+3 -1
View File
@@ -13,13 +13,15 @@ array data, it performs the following steps:
- j1 = from_msgpack(data)
- vec = to_msgpack(j1)
- j2 = from_msgpack(vec)
- assert(to_msgpack(j2) == vec)
- assert(j1 == j2)
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
drivers.
*/
#include <cassert>
#include <iostream>
#include <sstream>
#include <nlohmann/json.hpp>
// the round-trip checks below are assertions; NDEBUG would compile them away
+11 -9
View File
@@ -11,15 +11,15 @@ This file implements a parser test suitable for fuzz testing. Given a byte
array data, it performs the following steps:
- j1 = from_ubjson(data)
- vec2 = to_ubjson(j1, use_size = false, use_type = false)
- vec3 = to_ubjson(j1, use_size = true, use_type = false)
- vec4 = to_ubjson(j1, use_size = true, use_type = true)
- j2 = from_ubjson(vec2)
- j3 = from_ubjson(vec3)
- j4 = from_ubjson(vec4)
- assert(to_ubjson(j2, use_size = false, use_type = false) == vec2)
- assert(to_ubjson(j3, use_size = true, use_type = false) == vec3)
- assert(to_ubjson(j4, use_size = true, use_type = true) == vec4)
- vec = to_ubjson(j1)
- j2 = from_ubjson(vec)
- assert(j1 == j2)
- vec2 = to_ubjson(j1, use_size = true, use_type = false)
- j3 = from_ubjson(vec2)
- assert(j1 == j3)
- vec3 = to_ubjson(j1, use_size = true, use_type = true)
- j4 = from_ubjson(vec3)
- assert(j1 == j4)
The unit tests run the same checks on a fixed corpus (see the "UBJSON round-trip
invariants" test case), so keep both in sync.
@@ -29,6 +29,8 @@ drivers.
*/
#include <cassert>
#include <iostream>
#include <sstream>
#include <nlohmann/json.hpp>
// the round-trip checks below are assertions; NDEBUG would compile them away
+6 -7
View File
@@ -19,14 +19,13 @@
#include <nlohmann/json.hpp>
// Values for the round-trip property tests of the binary format writers
// (BJData, BON8, BSON, CBOR, MessagePack and UBJSON).
// Values for the round-trip property tests of the UBJSON and BJData writers.
//
// The fuzzer drivers (tests/src/fuzzer-parse_*.cpp) check that anything the
// library parses can be serialized, parsed back, and serialized again
// without loss. Those checks only run at OSS-Fuzz, so a regression used to
// surface days later as an external report. The unit tests run the same
// checks on this corpus in CI.
// The fuzzer drivers (tests/src/fuzzer-parse_ubjson.cpp and
// fuzzer-parse_bjdata.cpp) check that anything the library parses can be
// serialized, parsed back, and serialized again without loss. Those checks
// only run at OSS-Fuzz, so a regression used to surface days later as an
// external report. The unit tests run the same checks on this corpus in CI.
//
// The corpus is deterministic: std::mt19937's output sequence is fixed by
// the standard, and it is used directly rather than through a distribution
-99
View File
@@ -1,99 +0,0 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++ (supporting code)
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#pragma once
#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>
#include <nlohmann/json.hpp>
namespace utils
{
/// a SAX event consumer that stops accepting events after a fixed count,
/// used by the binary-format tests to check behavior when the SAX consumer
/// rejects an event partway through parsing
class SaxCountdown
{
public:
using json = nlohmann::json;
explicit SaxCountdown(const int count) : events_left(count)
{}
bool null()
{
return events_left-- > 0;
}
bool boolean(bool /*unused*/)
{
return events_left-- > 0;
}
bool number_integer(json::number_integer_t /*unused*/)
{
return events_left-- > 0;
}
bool number_unsigned(json::number_unsigned_t /*unused*/)
{
return events_left-- > 0;
}
bool number_float(json::number_float_t /*unused*/, const std::string& /*unused*/)
{
return events_left-- > 0;
}
bool string(std::string& /*unused*/)
{
return events_left-- > 0;
}
bool binary(std::vector<std::uint8_t>& /*unused*/)
{
return events_left-- > 0;
}
bool start_object(std::size_t /*unused*/)
{
return events_left-- > 0;
}
bool key(std::string& /*unused*/)
{
return events_left-- > 0;
}
bool end_object()
{
return events_left-- > 0;
}
bool start_array(std::size_t /*unused*/)
{
return events_left-- > 0;
}
bool end_array()
{
return events_left-- > 0;
}
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const json::exception& /*unused*/) // NOLINT(readability-convert-member-functions-to-static)
{
return false;
}
private:
int events_left = 0;
};
} // namespace utils
+73 -4
View File
@@ -12,11 +12,80 @@
using nlohmann::json;
#include <climits> // SIZE_MAX
#include <limits> // numeric_limits
// JSON_32bitTest=ONLY builds only this file, so it must keep its own
// include of the shared trait/TEST_CASE_TEMPLATE_DEFINE rather than relying
// on unit-bjdata.cpp to provide it
#include "value_in_range_of_test.hpp"
template <typename OfType, typename T, bool MinInRange, bool MaxInRange>
struct trait_test_arg
{
using of_type = OfType;
using type = T;
static constexpr bool min_in_range = MinInRange;
static constexpr bool max_in_range = MaxInRange;
};
TEST_CASE_TEMPLATE_DEFINE("value_in_range_of trait", T, value_in_range_of_test) // NOLINT(readability-math-missing-parentheses)
{
using nlohmann::detail::value_in_range_of;
using of_type = typename T::of_type;
using type = typename T::type;
constexpr bool min_in_range = T::min_in_range;
constexpr bool max_in_range = T::max_in_range;
type const val_min = std::numeric_limits<type>::min();
type const val_min2 = val_min + 1;
type const val_max = std::numeric_limits<type>::max();
type const val_max2 = val_max - 1;
REQUIRE(CHAR_BIT == 8);
std::string of_type_str;
if (std::is_unsigned<of_type>::value)
{
of_type_str += "u";
}
of_type_str += "int";
of_type_str += std::to_string(sizeof(of_type) * 8);
INFO("of_type := ", of_type_str);
std::string type_str;
if (std::is_unsigned<type>::value)
{
type_str += "u";
}
type_str += "int";
type_str += std::to_string(sizeof(type) * 8);
INFO("type := ", type_str);
CAPTURE(val_min);
CAPTURE(min_in_range);
CAPTURE(val_max);
CAPTURE(max_in_range);
if (min_in_range)
{
CHECK(value_in_range_of<of_type>(val_min));
CHECK(value_in_range_of<of_type>(val_min2));
}
else
{
CHECK_FALSE(value_in_range_of<of_type>(val_min));
CHECK_FALSE(value_in_range_of<of_type>(val_min2));
}
if (max_in_range)
{
CHECK(value_in_range_of<of_type>(val_max));
CHECK(value_in_range_of<of_type>(val_max2));
}
else
{
CHECK_FALSE(value_in_range_of<of_type>(val_max));
CHECK_FALSE(value_in_range_of<of_type>(val_max2));
}
}
TEST_CASE("32bit")
{
+150 -5
View File
@@ -21,13 +21,158 @@ using nlohmann::json;
#include "make_test_data_available.hpp"
#include "round_trip_corpus.hpp"
#include "test_utils.hpp"
#include "sax_countdown.hpp"
using utils::SaxCountdown;
namespace
{
class SaxCountdown
{
public:
explicit SaxCountdown(const int count) : events_left(count)
{}
// trait_test_arg and the "value_in_range_of trait" TEST_CASE_TEMPLATE_DEFINE
// are shared with unit-32bit.cpp
#include "value_in_range_of_test.hpp"
bool null()
{
return events_left-- > 0;
}
bool boolean(bool /*unused*/)
{
return events_left-- > 0;
}
bool number_integer(json::number_integer_t /*unused*/)
{
return events_left-- > 0;
}
bool number_unsigned(json::number_unsigned_t /*unused*/)
{
return events_left-- > 0;
}
bool number_float(json::number_float_t /*unused*/, const std::string& /*unused*/)
{
return events_left-- > 0;
}
bool string(std::string& /*unused*/)
{
return events_left-- > 0;
}
bool binary(std::vector<std::uint8_t>& /*unused*/)
{
return events_left-- > 0;
}
bool start_object(std::size_t /*unused*/)
{
return events_left-- > 0;
}
bool key(std::string& /*unused*/)
{
return events_left-- > 0;
}
bool end_object()
{
return events_left-- > 0;
}
bool start_array(std::size_t /*unused*/)
{
return events_left-- > 0;
}
bool end_array()
{
return events_left-- > 0;
}
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const json::exception& /*unused*/) // NOLINT(readability-convert-member-functions-to-static)
{
return false;
}
private:
int events_left = 0;
};
} // namespace
// at some point in the future, a unit test dedicated to type traits might be a good idea
template <typename OfType, typename T, bool MinInRange, bool MaxInRange>
struct trait_test_arg
{
using of_type = OfType;
using type = T;
static constexpr bool min_in_range = MinInRange;
static constexpr bool max_in_range = MaxInRange;
};
TEST_CASE_TEMPLATE_DEFINE("value_in_range_of trait", T, value_in_range_of_test) // NOLINT(readability-math-missing-parentheses)
{
using nlohmann::detail::value_in_range_of;
using of_type = typename T::of_type;
using type = typename T::type;
constexpr bool min_in_range = T::min_in_range;
constexpr bool max_in_range = T::max_in_range;
type const val_min = std::numeric_limits<type>::min();
type const val_min2 = val_min + 1;
type const val_max = std::numeric_limits<type>::max();
type const val_max2 = val_max - 1;
REQUIRE(CHAR_BIT == 8);
std::string of_type_str;
if (std::is_unsigned<of_type>::value)
{
of_type_str += "u";
}
of_type_str += "int";
of_type_str += std::to_string(sizeof(of_type) * 8);
INFO("of_type := ", of_type_str);
std::string type_str;
if (std::is_unsigned<type>::value)
{
type_str += "u";
}
type_str += "int";
type_str += std::to_string(sizeof(type) * 8);
INFO("type := ", type_str);
CAPTURE(val_min);
CAPTURE(min_in_range);
CAPTURE(val_max);
CAPTURE(max_in_range);
if (min_in_range)
{
CHECK(value_in_range_of<of_type>(val_min));
CHECK(value_in_range_of<of_type>(val_min2));
}
else
{
CHECK_FALSE(value_in_range_of<of_type>(val_min));
CHECK_FALSE(value_in_range_of<of_type>(val_min2));
}
if (max_in_range)
{
CHECK(value_in_range_of<of_type>(val_max));
CHECK(value_in_range_of<of_type>(val_max2));
}
else
{
CHECK_FALSE(value_in_range_of<of_type>(val_max));
CHECK_FALSE(value_in_range_of<of_type>(val_max2));
}
}
// NOLINTNEXTLINE(bugprone-throwing-static-initialization)
TEST_CASE_TEMPLATE_INVOKE(value_in_range_of_test, \
+75 -36
View File
@@ -21,13 +21,85 @@ using nlohmann::json;
#include <string>
#include <vector>
#include "make_test_data_available.hpp"
#include "round_trip_corpus.hpp"
#include "test_utils.hpp"
#include "sax_countdown.hpp"
using utils::SaxCountdown;
namespace
{
class SaxCountdown
{
public:
explicit SaxCountdown(const int count) : events_left(count)
{}
bool null()
{
return events_left-- > 0;
}
bool boolean(bool /*unused*/)
{
return events_left-- > 0;
}
bool number_integer(json::number_integer_t /*unused*/)
{
return events_left-- > 0;
}
bool number_unsigned(json::number_unsigned_t /*unused*/)
{
return events_left-- > 0;
}
bool number_float(json::number_float_t /*unused*/, const std::string& /*unused*/)
{
return events_left-- > 0;
}
bool string(std::string& /*unused*/)
{
return events_left-- > 0;
}
bool binary(std::vector<std::uint8_t>& /*unused*/)
{
return events_left-- > 0;
}
bool start_object(std::size_t /*unused*/)
{
return events_left-- > 0;
}
bool key(std::string& /*unused*/)
{
return events_left-- > 0;
}
bool end_object()
{
return events_left-- > 0;
}
bool start_array(std::size_t /*unused*/)
{
return events_left-- > 0;
}
bool end_array()
{
return events_left-- > 0;
}
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const json::exception& /*unused*/) // NOLINT(readability-convert-member-functions-to-static)
{
return false;
}
private:
int events_left = 0;
};
using bytes = std::vector<std::uint8_t>;
/// @return the string with the given bytes
@@ -745,39 +817,6 @@ TEST_CASE("Parse BON8 directly from a file using iterator and sentinel")
CHECK((parsed.is_object() || parsed.is_array()));
}
TEST_CASE("BON8 round-trip invariants")
{
// This checks what the parse_bon8_fuzzer driver checks (see
// tests/src/fuzzer-parse_bon8.cpp), so that a regression shows up in CI
// rather than as an OSS-Fuzz report: anything from_bon8() returns (j1)
// can be serialized, parsed back (j2), and serialized again to reproduce
// the exact bytes. The stream-versus-contiguous input check the driver
// also performs is not covered here (see #5601).
for (const auto& j0 : utils::round_trip_corpus::values())
{
json j1;
try
{
// turn the corpus value into a value as from_bon8() returns it
j1 = json::from_bon8(json::to_bon8(j0));
}
catch (const json::exception&)
{
// BON8 cannot represent an unsigned integer above INT64_MAX, and
// the fuzzer driver only ever sees values from_bon8() actually
// produced, so skip such corpus values here, too
continue;
}
INFO("j1 = " << j1.dump());
const std::vector<std::uint8_t> vec = json::to_bon8(j1);
json j2;
// anything the library writes must be parsable by the library
REQUIRE_NOTHROW(j2 = json::from_bon8(vec));
CHECK(json::to_bon8(j2) == vec);
}
}
TEST_CASE("BON8 roundtrips" * doctest::skip())
{
SECTION("input from HikoGUI")
+77 -41
View File
@@ -17,10 +17,7 @@ using nlohmann::json;
#include <sstream>
#include <vector>
#include "make_test_data_available.hpp"
#include "round_trip_corpus.hpp"
#include "test_utils.hpp"
#include "sax_countdown.hpp"
using utils::SaxCountdown;
namespace
{
@@ -860,6 +857,83 @@ TEST_CASE("BSON input/output_adapters")
}
}
namespace
{
class SaxCountdown
{
public:
explicit SaxCountdown(const int count) : events_left(count)
{}
bool null()
{
return events_left-- > 0;
}
bool boolean(bool /*unused*/)
{
return events_left-- > 0;
}
bool number_integer(json::number_integer_t /*unused*/)
{
return events_left-- > 0;
}
bool number_unsigned(json::number_unsigned_t /*unused*/)
{
return events_left-- > 0;
}
bool number_float(json::number_float_t /*unused*/, const std::string& /*unused*/)
{
return events_left-- > 0;
}
bool string(std::string& /*unused*/)
{
return events_left-- > 0;
}
bool binary(std::vector<std::uint8_t>& /*unused*/)
{
return events_left-- > 0;
}
bool start_object(std::size_t /*unused*/)
{
return events_left-- > 0;
}
bool key(std::string& /*unused*/)
{
return events_left-- > 0;
}
bool end_object()
{
return events_left-- > 0;
}
bool start_array(std::size_t /*unused*/)
{
return events_left-- > 0;
}
bool end_array()
{
return events_left-- > 0;
}
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const json::exception& /*unused*/) // NOLINT(readability-convert-member-functions-to-static)
{
return false;
}
private:
int events_left = 0;
};
} // namespace
TEST_CASE("Incomplete BSON Input")
{
@@ -1614,44 +1688,6 @@ TEST_CASE("Parse BSON directly from a file using iterator and sentinel")
CHECK(parsed == expected);
}
TEST_CASE("BSON round-trip invariants")
{
// This checks what the parse_bson_fuzzer driver checks (see
// tests/src/fuzzer-parse_bson.cpp), so that a regression shows up in CI
// rather than as an OSS-Fuzz report: anything from_bson() returns (j1)
// can be serialized, parsed back (j2), and serialized again to reproduce
// the exact bytes. BSON only serializes objects, so non-object corpus
// values are skipped.
for (const auto& j0 : utils::round_trip_corpus::values())
{
if (!j0.is_object())
{
continue;
}
json j1;
try
{
// turn the corpus value into a value as from_bson() returns it
j1 = json::from_bson(json::to_bson(j0));
}
catch (const json::exception&)
{
// the fuzzer driver only ever sees values from_bson() actually
// produced, so skip corpus values that do not survive the
// round trip here, too
continue;
}
INFO("j1 = " << j1.dump());
const std::vector<std::uint8_t> vec = json::to_bson(j1);
json j2;
// anything the library writes must be parsable by the library
REQUIRE_NOTHROW(j2 = json::from_bson(vec));
CHECK(json::to_bson(j2) == vec);
}
}
TEST_CASE("BSON roundtrips" * doctest::skip())
{
SECTION("reference files")
+96 -84
View File
@@ -18,11 +18,85 @@ using nlohmann::json;
#include <list>
#include <set>
#include "make_test_data_available.hpp"
#include "round_trip_corpus.hpp"
#include "test_utils.hpp"
#include "sax_countdown.hpp"
using utils::SaxCountdown;
namespace
{
class SaxCountdown
{
public:
explicit SaxCountdown(const int count) : events_left(count)
{}
bool null()
{
return events_left-- > 0;
}
bool boolean(bool /*unused*/)
{
return events_left-- > 0;
}
bool number_integer(json::number_integer_t /*unused*/)
{
return events_left-- > 0;
}
bool number_unsigned(json::number_unsigned_t /*unused*/)
{
return events_left-- > 0;
}
bool number_float(json::number_float_t /*unused*/, const std::string& /*unused*/)
{
return events_left-- > 0;
}
bool string(std::string& /*unused*/)
{
return events_left-- > 0;
}
bool binary(std::vector<std::uint8_t>& /*unused*/)
{
return events_left-- > 0;
}
bool start_object(std::size_t /*unused*/)
{
return events_left-- > 0;
}
bool key(std::string& /*unused*/)
{
return events_left-- > 0;
}
bool end_object()
{
return events_left-- > 0;
}
bool start_array(std::size_t /*unused*/)
{
return events_left-- > 0;
}
bool end_array()
{
return events_left-- > 0;
}
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const json::exception& /*unused*/) // NOLINT(readability-convert-member-functions-to-static)
{
return false;
}
private:
int events_left = 0;
};
} // namespace
TEST_CASE("CBOR")
{
@@ -1756,51 +1830,10 @@ TEST_CASE("CBOR")
SECTION("invalid string in map")
{
json _;
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xa1, 0xff, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found a break stop code; last byte: 0xFF", json::parse_error&);
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xa1, 0xff, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0xFF", json::parse_error&);
CHECK(json::from_cbor(std::vector<uint8_t>({0xa1, 0xff, 0x01}), true, false).is_discarded());
}
SECTION("non-string key (see #2766 and #3381)")
{
// only text strings map to JSON object keys; any other key is
// rejected with a message naming its type
const std::vector<std::pair<std::vector<std::uint8_t>, std::string>> cases =
{
{{0xA1, 0x01, 0x01}, "an unsigned integer; last byte: 0x01"},
{{0xA1, 0x20, 0x01}, "a negative integer; last byte: 0x20"},
{{0xA1, 0x41, 0x61, 0x01}, "a byte string; last byte: 0x41"},
{{0xA1, 0x80, 0x01}, "an array; last byte: 0x80"},
{{0xA1, 0xA0, 0x01}, "a map; last byte: 0xA0"},
{{0xA1, 0xC0, 0x61, 0x61, 0x01}, "a tag; last byte: 0xC0"},
{{0xA1, 0xF4, 0x01}, "a boolean; last byte: 0xF4"},
{{0xA1, 0xF5, 0x01}, "a boolean; last byte: 0xF5"},
{{0xA1, 0xF6, 0x01}, "null; last byte: 0xF6"},
{{0xA1, 0xF7, 0x01}, "undefined; last byte: 0xF7"},
{{0xA1, 0xF9, 0x3C, 0x00, 0x01}, "a floating-point number; last byte: 0xF9"},
{{0xA1, 0xFA, 0x3F, 0x80, 0x00, 0x00, 0x01}, "a floating-point number; last byte: 0xFA"},
{{0xA1, 0xFB, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, "a floating-point number; last byte: 0xFB"},
{{0xA1, 0xE0, 0x01}, "a simple value; last byte: 0xE0"},
{{0xA1, 0xF8, 0x20, 0x01}, "a simple value; last byte: 0xF8"},
// indefinite-length map
{{0xBF, 0x01, 0x01, 0xFF}, "an unsigned integer; last byte: 0x01"},
};
for (const auto& c : cases)
{
CAPTURE(c.first)
const std::string expected = "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found " + c.second;
json _;
CHECK_THROWS_WITH_AS(_ = json::from_cbor(c.first), expected.c_str(), json::parse_error&);
CHECK(json::from_cbor(c.first, true, false).is_discarded());
}
// a key of major type 3 with a reserved length is still reported as
// a malformed string, and a missing key as the end of input
json _;
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xA1})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xA1, 0x7C, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x7C", json::parse_error&);
}
SECTION("invalid UTF-8 in string (see #5529)")
{
// a two-character text string (major type 3) whose bytes are not
@@ -2251,7 +2284,7 @@ TEST_CASE("CBOR indefinite-length strings do not recurse per chunk")
SECTION("a break marker outside an indefinite-length string is not a string")
{
// 0xFF only closes a string that was opened; on its own it is not one
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xA1, 0xFF, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found a break stop code; last byte: 0xFF", json::parse_error&);
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xA1, 0xFF, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0xFF", json::parse_error&);
}
}
@@ -2341,39 +2374,6 @@ TEST_CASE("issue #5405 - array reserve for definite-length CBOR arrays")
}
}
TEST_CASE("CBOR round-trip invariants")
{
// This checks what the parse_cbor_fuzzer driver checks (see
// tests/src/fuzzer-parse_cbor.cpp), so that a regression shows up in CI
// rather than as an OSS-Fuzz report: anything from_cbor() returns (j1)
// can be serialized, parsed back (j2), and serialized again to reproduce
// the exact bytes.
for (const auto& j0 : utils::round_trip_corpus::values())
{
json j1;
try
{
// turn the corpus value into a value as from_cbor() returns it
j1 = json::from_cbor(json::to_cbor(j0));
}
catch (const json::exception&)
{
// not every corpus value survives a CBOR round trip (e.g., a
// binary subtype is written with a tag the default tag handler
// then rejects); the fuzzer driver only ever sees values
// from_cbor() actually produced, so skip those here, too
continue;
}
INFO("j1 = " << j1.dump());
const std::vector<std::uint8_t> vec = json::to_cbor(j1);
json j2;
// anything the library writes must be parsable by the library
REQUIRE_NOTHROW(j2 = json::from_cbor(vec));
CHECK(json::to_cbor(j2) == vec);
}
}
TEST_CASE("CBOR roundtrips" * doctest::skip())
{
SECTION("input from flynn")
@@ -2733,21 +2733,33 @@ TEST_CASE("examples from RFC 8949 Appendix A")
CHECK(json::to_cbor(json::parse("1.1")) == std::vector<uint8_t>({0xfb, 0x3f, 0xf1, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9a}));
CHECK(json::parse("1.1") == json::from_cbor(std::vector<uint8_t>({0xfb, 0x3f, 0xf1, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9a})));
// the writer never emits half-precision floats, so these can only be decoded, not encoded
// half-precision float
//CHECK(json::to_cbor(json::parse("1.5")) == std::vector<uint8_t>({0xf9, 0x3e, 0x00}));
CHECK(json::parse("1.5") == json::from_cbor(std::vector<uint8_t>({0xf9, 0x3e, 0x00})));
// half-precision float
//CHECK(json::to_cbor(json::parse("65504.0")) == std::vector<uint8_t>({0xf9, 0x7b, 0xff}));
CHECK(json::parse("65504.0") == json::from_cbor(std::vector<uint8_t>({0xf9, 0x7b, 0xff})));
CHECK(json::to_cbor(json::parse("100000.0")) == std::vector<uint8_t>({0xfa, 0x47, 0xc3, 0x50, 0x00}));
//CHECK(json::to_cbor(json::parse("100000.0")) == std::vector<uint8_t>({0xfa, 0x47, 0xc3, 0x50, 0x00}));
CHECK(json::parse("100000.0") == json::from_cbor(std::vector<uint8_t>({0xfa, 0x47, 0xc3, 0x50, 0x00})));
CHECK(json::to_cbor(json::parse("3.4028234663852886e+38")) == std::vector<uint8_t>({0xfa, 0x7f, 0x7f, 0xff, 0xff}));
//CHECK(json::to_cbor(json::parse("3.4028234663852886e+38")) == std::vector<uint8_t>({0xfa, 0x7f, 0x7f, 0xff, 0xff}));
CHECK(json::parse("3.4028234663852886e+38") == json::from_cbor(std::vector<uint8_t>({0xfa, 0x7f, 0x7f, 0xff, 0xff})));
CHECK(json::to_cbor(json::parse("1.0e+300")) == std::vector<uint8_t>({0xfb, 0x7e, 0x37, 0xe4, 0x3c, 0x88, 0x00, 0x75, 0x9c}));
CHECK(json::parse("1.0e+300") == json::from_cbor(std::vector<uint8_t>({0xfb, 0x7e, 0x37, 0xe4, 0x3c, 0x88, 0x00, 0x75, 0x9c})));
CHECK(json::parse("5.960464477539063e-8") == json::from_cbor(std::vector<uint8_t>({0xf9, 0x00, 0x01})));
CHECK(json::parse("0.00006103515625") == json::from_cbor(std::vector<uint8_t>({0xf9, 0x04, 0x00})));
// half-precision float
//CHECK(json::to_cbor(json::parse("5.960464477539063e-8")) == std::vector<uint8_t>({0xf9, 0x00, 0x01}));
CHECK(json::parse("-4.0") == json::from_cbor(std::vector<uint8_t>({0xf9, 0xc4, 0x00})));
// half-precision float
//CHECK(json::to_cbor(json::parse("0.00006103515625")) == std::vector<uint8_t>({0xf9, 0x04, 0x00}));
CHECK(json::parse("-4.0") == json::from_cbor(std::vector<uint8_t>({0xf9, 0xc4, 0x00})));
// half-precision float
//CHECK(json::to_cbor(json::parse("-4.0")) == std::vector<uint8_t>({0xf9, 0xc4, 0x00}));
CHECK(json::parse("-4.0") == json::from_cbor(std::vector<uint8_t>({0xf9, 0xc4, 0x00})));
CHECK(json::to_cbor(json::parse("-4.1")) == std::vector<uint8_t>({0xfb, 0xc0, 0x10, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66}));
+1 -1
View File
@@ -666,7 +666,7 @@ TEST_CASE("parse_float_fast declines what it cannot convert exactly")
// always safe: the caller then falls back to a slower, exact conversion.
const auto fast = [](const std::string & s, double & out)
{
return nlohmann::detail::parse_float_fast(s.data(), s.data() + s.size(), out);
return nlohmann::detail::parse_float_fast(s.data(), s.data() + s.size(), '.', out);
};
double out = 0;
+63 -73
View File
@@ -1372,22 +1372,21 @@ TEST_CASE("value conversion")
SECTION("std::map")
{
CHECK(j1.get<std::map<std::string, int>>() == (std::map<std::string, int> {{"one", 1}, {"two", 2}, {"three", 3}}));
CHECK(j2.get<std::map<std::string, unsigned int>>() == (std::map<std::string, unsigned int> {{"one", 1u}, {"two", 2u}, {"three", 3u}}));
CHECK(j3.get<std::map<std::string, double>>() == (std::map<std::string, double> {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}}));
CHECK(j4.get<std::map<std::string, bool>>() == (std::map<std::string, bool> {{"one", true}, {"two", false}, {"three", true}}));
CHECK(j5.get<std::map<std::string, std::string>>() == (std::map<std::string, std::string> {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}}));
j1.get<std::map<std::string, int>>();
j2.get<std::map<std::string, unsigned int>>();
j3.get<std::map<std::string, double>>();
j4.get<std::map<std::string, bool>>();
j5.get<std::map<std::string, std::string>>();
}
SECTION("std::unordered_map")
{
CHECK(j1.get<std::unordered_map<std::string, int>>() == (std::unordered_map<std::string, int> {{"one", 1}, {"two", 2}, {"three", 3}}));
CHECK(j2.get<std::unordered_map<std::string, unsigned int>>() == (std::unordered_map<std::string, unsigned int> {{"one", 1u}, {"two", 2u}, {"three", 3u}}));
CHECK(j3.get<std::unordered_map<std::string, double>>() == (std::unordered_map<std::string, double> {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}}));
CHECK(j4.get<std::unordered_map<std::string, bool>>() == (std::unordered_map<std::string, bool> {{"one", true}, {"two", false}, {"three", true}}));
const auto m5 = j5.get<std::unordered_map<std::string, std::string>>();
CHECK(m5 == (std::unordered_map<std::string, std::string> {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}}));
CHECK(m5.at("one") == "eins");
j1.get<std::unordered_map<std::string, int>>();
j2.get<std::unordered_map<std::string, unsigned int>>();
j3.get<std::unordered_map<std::string, double>>();
j4.get<std::unordered_map<std::string, bool>>();
j5.get<std::unordered_map<std::string, std::string>>();
// CHECK(m5["one"] == "eins");
}
SECTION("reserve is called on containers that support it (#5406)")
@@ -1423,24 +1422,22 @@ TEST_CASE("value conversion")
SECTION("std::multimap")
{
CHECK(j1.get<std::multimap<std::string, int>>() == (std::multimap<std::string, int> {{"one", 1}, {"two", 2}, {"three", 3}}));
CHECK(j2.get<std::multimap<std::string, unsigned int>>() == (std::multimap<std::string, unsigned int> {{"one", 1u}, {"two", 2u}, {"three", 3u}}));
CHECK(j3.get<std::multimap<std::string, double>>() == (std::multimap<std::string, double> {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}}));
CHECK(j4.get<std::multimap<std::string, bool>>() == (std::multimap<std::string, bool> {{"one", true}, {"two", false}, {"three", true}}));
const auto m5 = j5.get<std::multimap<std::string, std::string>>();
CHECK(m5 == (std::multimap<std::string, std::string> {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}}));
CHECK(m5.find("one")->second == "eins");
j1.get<std::multimap<std::string, int>>();
j2.get<std::multimap<std::string, unsigned int>>();
j3.get<std::multimap<std::string, double>>();
j4.get<std::multimap<std::string, bool>>();
j5.get<std::multimap<std::string, std::string>>();
// CHECK(m5["one"] == "eins");
}
SECTION("std::unordered_multimap")
{
CHECK(j1.get<std::unordered_multimap<std::string, int>>() == (std::unordered_multimap<std::string, int> {{"one", 1}, {"two", 2}, {"three", 3}}));
CHECK(j2.get<std::unordered_multimap<std::string, unsigned int>>() == (std::unordered_multimap<std::string, unsigned int> {{"one", 1u}, {"two", 2u}, {"three", 3u}}));
CHECK(j3.get<std::unordered_multimap<std::string, double>>() == (std::unordered_multimap<std::string, double> {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}}));
CHECK(j4.get<std::unordered_multimap<std::string, bool>>() == (std::unordered_multimap<std::string, bool> {{"one", true}, {"two", false}, {"three", true}}));
const auto m5 = j5.get<std::unordered_multimap<std::string, std::string>>();
CHECK(m5 == (std::unordered_multimap<std::string, std::string> {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}}));
CHECK(m5.find("one")->second == "eins");
j1.get<std::unordered_multimap<std::string, int>>();
j2.get<std::unordered_multimap<std::string, unsigned int>>();
j3.get<std::unordered_multimap<std::string, double>>();
j4.get<std::unordered_multimap<std::string, bool>>();
j5.get<std::unordered_multimap<std::string, std::string>>();
// CHECK(m5["one"] == "eins");
}
SECTION("exception in case of a non-object type")
@@ -1461,30 +1458,29 @@ TEST_CASE("value conversion")
SECTION("std::list")
{
CHECK(j1.get<std::list<int>>() == (std::list<int> {1, 2, 3, 4}));
CHECK(j2.get<std::list<unsigned int>>() == (std::list<unsigned int> {1u, 2u, 3u, 4u}));
CHECK(j3.get<std::list<double>>() == (std::list<double> {1.2, 2.3, 3.4, 4.5}));
CHECK(j4.get<std::list<bool>>() == (std::list<bool> {true, false, true}));
CHECK(j5.get<std::list<std::string>>() == (std::list<std::string> {"one", "two", "three"}));
j1.get<std::list<int>>();
j2.get<std::list<unsigned int>>();
j3.get<std::list<double>>();
j4.get<std::list<bool>>();
j5.get<std::list<std::string>>();
}
SECTION("std::forward_list")
{
CHECK(j1.get<std::forward_list<int>>() == (std::forward_list<int> {1, 2, 3, 4}));
CHECK(j2.get<std::forward_list<unsigned int>>() == (std::forward_list<unsigned int> {1u, 2u, 3u, 4u}));
CHECK(j3.get<std::forward_list<double>>() == (std::forward_list<double> {1.2, 2.3, 3.4, 4.5}));
CHECK(j4.get<std::forward_list<bool>>() == (std::forward_list<bool> {true, false, true}));
CHECK(j5.get<std::forward_list<std::string>>() == (std::forward_list<std::string> {"one", "two", "three"}));
j1.get<std::forward_list<int>>();
j2.get<std::forward_list<unsigned int>>();
j3.get<std::forward_list<double>>();
j4.get<std::forward_list<bool>>();
j5.get<std::forward_list<std::string>>();
}
SECTION("std::array")
{
CHECK(j1.get<std::array<int, 4>>() == (std::array<int, 4> {{1, 2, 3, 4}}));
// only the first 3 elements of j2 are converted, since the target array is smaller
CHECK(j2.get<std::array<unsigned int, 3>>() == (std::array<unsigned int, 3> {{1u, 2u, 3u}}));
CHECK(j3.get<std::array<double, 4>>() == (std::array<double, 4> {{1.2, 2.3, 3.4, 4.5}}));
CHECK(j4.get<std::array<bool, 3>>() == (std::array<bool, 3> {{true, false, true}}));
CHECK(j5.get<std::array<std::string, 3>>() == (std::array<std::string, 3> {{"one", "two", "three"}}));
j1.get<std::array<int, 4>>();
j2.get<std::array<unsigned int, 3>>();
j3.get<std::array<double, 4>>();
j4.get<std::array<bool, 3>>();
j5.get<std::array<std::string, 3>>();
SECTION("std::array is larger than JSON")
{
@@ -1504,53 +1500,47 @@ TEST_CASE("value conversion")
SECTION("std::valarray")
{
// valarray has no operator== that returns bool, so compare via a vector copy
const auto v1 = j1.get<std::valarray<int>>();
CHECK((std::vector<int>(std::begin(v1), std::end(v1)) == std::vector<int> {1, 2, 3, 4}));
const auto v2 = j2.get<std::valarray<unsigned int>>();
CHECK((std::vector<unsigned int>(std::begin(v2), std::end(v2)) == std::vector<unsigned int> {1u, 2u, 3u, 4u}));
const auto v3 = j3.get<std::valarray<double>>();
CHECK((std::vector<double>(std::begin(v3), std::end(v3)) == std::vector<double> {1.2, 2.3, 3.4, 4.5}));
const auto v4 = j4.get<std::valarray<bool>>();
CHECK((std::vector<bool>(std::begin(v4), std::end(v4)) == std::vector<bool> {true, false, true}));
const auto v5 = j5.get<std::valarray<std::string>>();
CHECK((std::vector<std::string>(std::begin(v5), std::end(v5)) == std::vector<std::string> {"one", "two", "three"}));
j1.get<std::valarray<int>>();
j2.get<std::valarray<unsigned int>>();
j3.get<std::valarray<double>>();
j4.get<std::valarray<bool>>();
j5.get<std::valarray<std::string>>();
}
SECTION("std::vector")
{
CHECK(j1.get<std::vector<int>>() == (std::vector<int> {1, 2, 3, 4}));
CHECK(j2.get<std::vector<unsigned int>>() == (std::vector<unsigned int> {1u, 2u, 3u, 4u}));
CHECK(j3.get<std::vector<double>>() == (std::vector<double> {1.2, 2.3, 3.4, 4.5}));
CHECK(j4.get<std::vector<bool>>() == (std::vector<bool> {true, false, true}));
CHECK(j5.get<std::vector<std::string>>() == (std::vector<std::string> {"one", "two", "three"}));
j1.get<std::vector<int>>();
j2.get<std::vector<unsigned int>>();
j3.get<std::vector<double>>();
j4.get<std::vector<bool>>();
j5.get<std::vector<std::string>>();
}
SECTION("std::deque")
{
CHECK(j1.get<std::deque<int>>() == (std::deque<int> {1, 2, 3, 4}));
CHECK(j2.get<std::deque<unsigned int>>() == (std::deque<unsigned int> {1u, 2u, 3u, 4u}));
CHECK(j3.get<std::deque<double>>() == (std::deque<double> {1.2, 2.3, 3.4, 4.5}));
CHECK(j4.get<std::deque<bool>>() == (std::deque<bool> {true, false, true}));
CHECK(j5.get<std::deque<std::string>>() == (std::deque<std::string> {"one", "two", "three"}));
j1.get<std::deque<int>>();
j2.get<std::deque<unsigned int>>();
j2.get<std::deque<double>>();
j4.get<std::deque<bool>>();
j5.get<std::deque<std::string>>();
}
SECTION("std::set")
{
CHECK(j1.get<std::set<int>>() == (std::set<int> {1, 2, 3, 4}));
CHECK(j2.get<std::set<unsigned int>>() == (std::set<unsigned int> {1u, 2u, 3u, 4u}));
CHECK(j3.get<std::set<double>>() == (std::set<double> {1.2, 2.3, 3.4, 4.5}));
CHECK(j4.get<std::set<bool>>() == (std::set<bool> {true, false, true}));
CHECK(j5.get<std::set<std::string>>() == (std::set<std::string> {"one", "two", "three"}));
j1.get<std::set<int>>();
j2.get<std::set<unsigned int>>();
j3.get<std::set<double>>();
j4.get<std::set<bool>>();
j5.get<std::set<std::string>>();
}
SECTION("std::unordered_set")
{
CHECK(j1.get<std::unordered_set<int>>() == (std::unordered_set<int> {1, 2, 3, 4}));
CHECK(j2.get<std::unordered_set<unsigned int>>() == (std::unordered_set<unsigned int> {1u, 2u, 3u, 4u}));
CHECK(j3.get<std::unordered_set<double>>() == (std::unordered_set<double> {1.2, 2.3, 3.4, 4.5}));
CHECK(j4.get<std::unordered_set<bool>>() == (std::unordered_set<bool> {true, false, true}));
CHECK(j5.get<std::unordered_set<std::string>>() == (std::unordered_set<std::string> {"one", "two", "three"}));
j1.get<std::unordered_set<int>>();
j2.get<std::unordered_set<unsigned int>>();
j3.get<std::unordered_set<double>>();
j4.get<std::unordered_set<bool>>();
j5.get<std::unordered_set<std::string>>();
}
SECTION("std::map (array of pairs)")
+31 -9
View File
@@ -28,6 +28,11 @@ using nlohmann::json;
#include <string>
#include <valarray>
#if defined(_WIN32)
#define NOMINMAX
#include <windows.h> // for GetACP()
#endif
namespace
{
struct SaxEventLogger : public nlohmann::json_sax<json>
@@ -223,6 +228,24 @@ class proxy_iterator
iterator* m_it = nullptr;
};
// JSON_HAS_CPP_20
#if defined(__cpp_char8_t)
bool check_utf8()
{
#if defined(_WIN32)
// Runtime check of the active ANSI code page
// 65001 == UTF-8
return GetACP() == 65001;
#elif defined(__ICC) || defined(__INTEL_COMPILER)
// classic Intel ICC does not encode narrow string literals containing
// non-ASCII source characters as UTF-8, so comparing a decoded u8 literal
// against a narrow string literal containing the same characters fails
return false;
#else
return true;
#endif
}
#endif
} // namespace
TEST_CASE("deserialization")
@@ -1274,15 +1297,14 @@ TEST_CASE("deserialization")
CHECK(j1["key"] == "value");
CHECK(j1["num"] == 42);
// UTF-8 prefixed literal (C++20 and later); the emoji is written as a
// \U escape rather than a raw multibyte character so this does not
// depend on the compiler's source-file encoding (e.g., MSVC without
// /utf-8, or classic ICC, which does not encode non-ASCII narrow
// string literals as UTF-8 - compare against a \x-escaped expectation
// for the same reason)
const auto j2 = u8"{\"emoji\": \"\U0001F600\", \"msg\": \"hello\"}"_json;
CHECK(j2["emoji"] == "\xF0\x9F\x98\x80");
CHECK(j2["msg"] == "hello");
// UTF-8 prefixed literal (C++20 and later);
// MSVC may not set /utf-8, so we need to check
if (check_utf8())
{
const auto j2 = u8R"({"emoji": "😀", "msg": "hello"})"_json;
CHECK(j2["emoji"] == "😀");
CHECK(j2["msg"] == "hello");
}
const auto j3 = u8R"({"key": "value", "num": 42})"_json;
CHECK(j3["key"] == "value");
+7
View File
@@ -893,6 +893,8 @@ TEST_CASE("iterators 2")
CHECK(std::ranges::input_range<items_type>);
}
// libstdc++ algorithms don't work with Clang 15 (04/2022)
#if !DOCTEST_CLANG || (DOCTEST_CLANG && defined(__GLIBCXX__))
SECTION("algorithms")
{
SECTION("copy")
@@ -927,7 +929,11 @@ TEST_CASE("iterators 2")
CHECK(*it == 2);
}
}
#endif
// libstdc++ views don't work with Clang 15 (04/2022)
// libc++ hides limited ranges implementation behind guard macro
#if !(DOCTEST_CLANG && (defined(__GLIBCXX__) || defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES)))
SECTION("views")
{
SECTION("reverse")
@@ -960,6 +966,7 @@ TEST_CASE("iterators 2")
CHECK(j_transformed == j_expected);
}
}
#endif
}
#endif
}
+66
View File
@@ -438,6 +438,72 @@ TEST_CASE("JSON pointers")
}
}
SECTION("creating intermediate levels")
{
SECTION("tokens that are valid array indices create arrays")
{
json j;
j["/0"_json_pointer] = 1;
CHECK(j == json({1}));
json j2;
j2["/2"_json_pointer] = 1;
CHECK(j2 == json({nullptr, nullptr, 1}));
json j3;
j3["/-"_json_pointer] = 1;
CHECK(j3 == json({1}));
json j4;
j4["/foo/0/0"_json_pointer] = 1;
CHECK(j4 == json({{"foo", {{1}}}}));
}
SECTION("tokens that are no valid array indices create objects")
{
json j;
j["/one"_json_pointer] = 1;
CHECK(j == json({{"one", 1}}));
// leading '0' can never be a valid array index (RFC 6901, Sect. 4)
json j2;
j2["/01"_json_pointer] = 1;
CHECK(j2 == json({{"01", 1}}));
// the empty token is a valid object key, but no valid array index
json j3;
j3["/"_json_pointer] = 1;
CHECK(j3 == json({{"", 1}}));
}
SECTION("creating a level yields the same result as reusing it (#5357)")
{
json j;
j["/a/b/01/d"_json_pointer] = "value";
json j_init = json::object();
j_init["/a/b"_json_pointer] = json::object();
j_init["/a/b/01/d"_json_pointer] = "value";
const json expected = json::parse(R"({"a":{"b":{"01":{"d":"value"}}}})");
CHECK(j == expected);
CHECK(j_init == expected);
// unflatten uses the same key
const json flat = {{"/a/b/01/d", "value"}};
CHECK(flat.unflatten() == expected);
}
SECTION("existing arrays still reject invalid indices")
{
json j = {1, 2, 3};
CHECK_THROWS_WITH_AS(j["/01"_json_pointer],
"[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'", json::parse_error&);
CHECK_THROWS_WITH_AS(j.at("/01"_json_pointer),
"[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'", json::parse_error&);
}
}
SECTION("flatten")
{
json j =
-210
View File
@@ -12,12 +12,7 @@
#include <nlohmann/json.hpp>
using nlohmann::json;
#include <array>
#include <clocale>
#include <map>
#include <string>
#include <utility>
#include <vector>
struct ParserImpl final: public nlohmann::json_sax<json>
{
@@ -180,208 +175,3 @@ TEST_CASE("locale-dependent test (LC_NUMERIC=de_DE)")
MESSAGE("locale de_DE is not usable");
}
}
namespace
{
// records the numbers of a flat array and switches LC_NUMERIC to the given
// locale once the array opens - after the lexer was constructed, but before
// any number in the array is lexed
struct LocaleSwitchingSax final: public nlohmann::json_sax<json>
{
explicit LocaleSwitchingSax(const char* switch_to)
: locale_after_open(switch_to)
{}
bool null() override
{
return true;
}
bool boolean(bool /*val*/) override
{
return true;
}
bool number_integer(json::number_integer_t /*val*/) override
{
return true;
}
bool number_unsigned(json::number_unsigned_t /*val*/) override
{
return true;
}
bool number_float(json::number_float_t val, const json::string_t& s) override
{
values.push_back(val);
strings.push_back(s);
return true;
}
bool string(json::string_t& /*val*/) override
{
return true;
}
bool binary(json::binary_t& /*val*/) override
{
return true;
}
bool start_object(std::size_t /*val*/) override
{
return true;
}
bool key(json::string_t& /*val*/) override
{
return true;
}
bool end_object() override
{
return true;
}
bool start_array(std::size_t /*val*/) override
{
switched = std::setlocale(LC_NUMERIC, locale_after_open.c_str()) != nullptr;
return true;
}
bool end_array() override
{
return true;
}
bool parse_error(std::size_t /*val*/, const std::string& /*val*/, const nlohmann::detail::exception& /*val*/) override
{
return false;
}
std::string locale_after_open;
bool switched = false;
std::vector<json::number_float_t> values {}; // NOLINT(readability-redundant-member-init)
std::vector<json::string_t> strings {}; // NOLINT(readability-redundant-member-init)
};
} // namespace
TEST_CASE("locale changes between lexer construction and number conversion (#5198)")
{
// The numbers are chosen so that the conversion also takes the strtod
// fallback, which honors the locale that is current at conversion time:
// too many significant digits for Clinger's fast path, an underflow that
// std::from_chars rejects, and a plain value.
const std::vector<std::string> numbers = {"3.14159265358979323846", "1.5e-400", "12.34", "-0.000123456789012345678"};
std::string text = "[";
for (const auto& n : numbers)
{
text += (text.size() == 1 ? "" : ",") + n;
}
text += "]";
using long_double_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, long double>;
// reference values, parsed without a locale switch
REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr);
const json expected = json::parse(text);
const long_double_json expected_ld = long_double_json::parse(text);
const std::array<std::pair<const char*, const char*>, 2> transitions =
{
{
{"C", "de_DE"},
{"de_DE", "C"}
}
};
for (const auto& transition : transitions)
{
CAPTURE(transition.first);
CAPTURE(transition.second);
if (std::setlocale(LC_NUMERIC, transition.first) == nullptr)
{
MESSAGE("locale is not usable");
continue;
}
// SAX parsing
{
LocaleSwitchingSax sax(transition.second);
CHECK(json::sax_parse(text, &sax));
if (sax.switched)
{
CHECK(sax.values == expected.get<std::vector<json::number_float_t>>());
CHECK(sax.strings == numbers);
}
}
// DOM parsing with a callback
{
bool switched = false;
const auto cb = [&](int /*depth*/, json::parse_event_t event, json& /*parsed*/) noexcept
{
if (event == json::parse_event_t::array_start)
{
switched = std::setlocale(LC_NUMERIC, transition.second) != nullptr;
}
return true;
};
const json j = json::parse(text, cb);
if (switched)
{
CHECK(j == expected);
}
}
// a long double goes through std::strtold unless std::from_chars supports it
{
bool switched = false;
const auto cb = [&](int /*depth*/, long_double_json::parse_event_t event, long_double_json& /*parsed*/) noexcept
{
if (event == long_double_json::parse_event_t::array_start)
{
switched = std::setlocale(LC_NUMERIC, transition.second) != nullptr;
}
return true;
};
const long_double_json j = long_double_json::parse(text, cb);
if (switched)
{
CHECK(j == expected_ld);
}
}
}
CHECK(std::setlocale(LC_NUMERIC, "C") != nullptr);
}
TEST_CASE("locale with a multi-byte decimal point")
{
// Some locales use a decimal point that is not a single character, e.g.
// U+066B ARABIC DECIMAL SEPARATOR (two bytes in UTF-8). It cannot be
// substituted in place for '.', so the strtod fallback stops early. The
// conversion must still terminate rather than retry forever.
const std::array<const char*, 6> names = {{"ar_EG.UTF-8", "ar_SA.UTF-8", "fa_IR.UTF-8", "ps_AF.UTF-8", "ar_EG", "fa_IR"}};
bool tested = false;
for (const char* name : names)
{
if (std::setlocale(LC_NUMERIC, name) == nullptr)
{
continue;
}
const std::string decimal_point = std::localeconv()->decimal_point;
if (decimal_point.size() < 2)
{
continue;
}
CAPTURE(name);
tested = true;
// too many significant digits for Clinger's fast path, and an underflow
// that std::from_chars rejects: both reach the strtod fallback
json j;
CHECK_NOTHROW(j = json::parse("[3.14159265358979323846, 1.5e-400, -0.000123456789012345678]"));
CHECK(j.is_array());
CHECK(json::accept("3.14159265358979323846"));
// a value the locale-independent paths convert is not affected
CHECK(json::parse("12.5") == 12.5);
}
if (!tested)
{
MESSAGE("no locale with a multi-byte decimal point is usable");
}
CHECK(std::setlocale(LC_NUMERIC, "C") != nullptr);
}
+78 -95
View File
@@ -21,11 +21,85 @@ using nlohmann::json;
#include <limits>
#include <set>
#include "make_test_data_available.hpp"
#include "round_trip_corpus.hpp"
#include "test_utils.hpp"
#include "sax_countdown.hpp"
using utils::SaxCountdown;
namespace
{
class SaxCountdown
{
public:
explicit SaxCountdown(const int count) : events_left(count)
{}
bool null()
{
return events_left-- > 0;
}
bool boolean(bool /*unused*/)
{
return events_left-- > 0;
}
bool number_integer(json::number_integer_t /*unused*/)
{
return events_left-- > 0;
}
bool number_unsigned(json::number_unsigned_t /*unused*/)
{
return events_left-- > 0;
}
bool number_float(json::number_float_t /*unused*/, const std::string& /*unused*/)
{
return events_left-- > 0;
}
bool string(std::string& /*unused*/)
{
return events_left-- > 0;
}
bool binary(std::vector<std::uint8_t>& /*unused*/)
{
return events_left-- > 0;
}
bool start_object(std::size_t /*unused*/)
{
return events_left-- > 0;
}
bool key(std::string& /*unused*/)
{
return events_left-- > 0;
}
bool end_object()
{
return events_left-- > 0;
}
bool start_array(std::size_t /*unused*/)
{
return events_left-- > 0;
}
bool end_array()
{
return events_left-- > 0;
}
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const json::exception& /*unused*/) // NOLINT(readability-convert-member-functions-to-static)
{
return false;
}
private:
int events_left = 0;
};
} // namespace
TEST_CASE("MessagePack")
{
@@ -1477,69 +1551,10 @@ TEST_CASE("MessagePack")
SECTION("invalid string in map")
{
json _;
CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0x81, 0xff, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack object key: only string keys are supported, but found an integer; last byte: 0xFF", json::parse_error&);
CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0x81, 0xff, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack string: expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0xFF", json::parse_error&);
CHECK(json::from_msgpack(std::vector<uint8_t>({0x81, 0xff, 0x01}), true, false).is_discarded());
}
SECTION("non-string key (see #3381)")
{
// only strings map to JSON object keys; any other key is rejected
// with a message naming its type
const std::vector<std::pair<std::vector<std::uint8_t>, std::string>> cases =
{
{{0x81, 0xC0, 0x01}, "nil; last byte: 0xC0"},
{{0x81, 0xC2, 0x01}, "a boolean; last byte: 0xC2"},
{{0x81, 0xC3, 0x01}, "a boolean; last byte: 0xC3"},
{{0x81, 0xCA, 0x3F, 0x80, 0x00, 0x00, 0x01}, "a float; last byte: 0xCA"},
{{0x81, 0xCB, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, "a float; last byte: 0xCB"},
{{0x81, 0xC4, 0x00, 0x01}, "a bin; last byte: 0xC4"},
{{0x81, 0xC5, 0x00, 0x00, 0x01}, "a bin; last byte: 0xC5"},
{{0x81, 0xC6, 0x00, 0x00, 0x00, 0x00, 0x01}, "a bin; last byte: 0xC6"},
{{0x81, 0xC7, 0x00, 0x01, 0x01}, "an ext; last byte: 0xC7"},
{{0x81, 0xC8, 0x00, 0x00, 0x01, 0x01}, "an ext; last byte: 0xC8"},
{{0x81, 0xC9, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01}, "an ext; last byte: 0xC9"},
{{0x81, 0xD4, 0x01, 0x00, 0x01}, "an ext; last byte: 0xD4"},
{{0x81, 0xD5, 0x01, 0x00, 0x00, 0x01}, "an ext; last byte: 0xD5"},
{{0x81, 0xD6, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01}, "an ext; last byte: 0xD6"},
{{0x81, 0xD7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, "an ext; last byte: 0xD7"},
{{0x81, 0xD8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, "an ext; last byte: 0xD8"},
{{0x81, 0xCC, 0x01, 0x01}, "an integer; last byte: 0xCC"},
{{0x81, 0xCD, 0x00, 0x01, 0x01}, "an integer; last byte: 0xCD"},
{{0x81, 0xCE, 0x00, 0x00, 0x00, 0x01, 0x01}, "an integer; last byte: 0xCE"},
{{0x81, 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01}, "an integer; last byte: 0xCF"},
{{0x81, 0xD0, 0x01, 0x01}, "an integer; last byte: 0xD0"},
{{0x81, 0xD1, 0x00, 0x01, 0x01}, "an integer; last byte: 0xD1"},
{{0x81, 0xD2, 0x00, 0x00, 0x00, 0x01, 0x01}, "an integer; last byte: 0xD2"},
{{0x81, 0xD3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01}, "an integer; last byte: 0xD3"},
{{0x81, 0x00, 0x01}, "an integer; last byte: 0x00"},
{{0x81, 0x7F, 0x01}, "an integer; last byte: 0x7F"},
{{0x81, 0xE0, 0x01}, "an integer; last byte: 0xE0"},
{{0x81, 0x80, 0x01}, "a map; last byte: 0x80"},
{{0x81, 0x8F, 0x01}, "a map; last byte: 0x8F"},
{{0x81, 0xDE, 0x00, 0x00, 0x01}, "a map; last byte: 0xDE"},
{{0x81, 0xDF, 0x00, 0x00, 0x00, 0x00, 0x01}, "a map; last byte: 0xDF"},
{{0x81, 0x90, 0x01}, "an array; last byte: 0x90"},
{{0x81, 0x9F, 0x01}, "an array; last byte: 0x9F"},
{{0x81, 0xDC, 0x00, 0x00, 0x01}, "an array; last byte: 0xDC"},
{{0x81, 0xDD, 0x00, 0x00, 0x00, 0x00, 0x01}, "an array; last byte: 0xDD"},
};
for (const auto& c : cases)
{
CAPTURE(c.first)
const std::string expected = "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack object key: only string keys are supported, but found " + c.second;
json _;
CHECK_THROWS_WITH_AS(_ = json::from_msgpack(c.first), expected.c_str(), json::parse_error&);
CHECK(json::from_msgpack(c.first, true, false).is_discarded());
}
json _;
// the unused byte 0xC1 is still reported as a malformed string
CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0x81, 0xC1, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack string: expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0xC1", json::parse_error&);
// a missing key is still reported as the end of input
CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0x81})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing MessagePack string: unexpected end of input", json::parse_error&);
}
SECTION("invalid UTF-8 in string (see #5529)")
{
// a fixstr of length 2 (0xA0 | 2) whose bytes are not valid UTF-8
@@ -1856,38 +1871,6 @@ TEST_CASE("Parse MessagePack directly from a file using iterator and sentinel")
CHECK((parsed.is_object() || parsed.is_array()));
}
TEST_CASE("MessagePack round-trip invariants")
{
// This checks what the parse_msgpack_fuzzer driver checks (see
// tests/src/fuzzer-parse_msgpack.cpp), so that a regression shows up in
// CI rather than as an OSS-Fuzz report: anything from_msgpack() returns
// (j1) can be serialized, parsed back (j2), and serialized again to
// reproduce the exact bytes.
for (const auto& j0 : utils::round_trip_corpus::values())
{
json j1;
try
{
// turn the corpus value into a value as from_msgpack() returns it
j1 = json::from_msgpack(json::to_msgpack(j0));
}
catch (const json::exception&)
{
// the fuzzer driver only ever sees values from_msgpack() actually
// produced, so skip corpus values that do not survive the
// round trip here, too
continue;
}
INFO("j1 = " << j1.dump());
const std::vector<std::uint8_t> vec = json::to_msgpack(j1);
json j2;
// anything the library writes must be parsable by the library
REQUIRE_NOTHROW(j2 = json::from_msgpack(vec));
CHECK(json::to_msgpack(j2) == vec);
}
}
TEST_CASE("MessagePack roundtrips" * doctest::skip())
{
SECTION("input from msgpack-python")
+1 -1
View File
@@ -28,7 +28,7 @@ using nlohmann::json;
DOCTEST_MSVC_SUPPRESS_WARNING_PUSH
DOCTEST_MSVC_SUPPRESS_WARNING(4189)
TEST_CASE("README")
TEST_CASE("README" * doctest::skip())
{
{
// redirect std::cout for the README file
+3 -19
View File
@@ -1018,7 +1018,7 @@ TEST_CASE("regression tests 1")
};
json _;
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found an array; last byte: 0x98", json::parse_error&);
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x98", json::parse_error&);
// related test case: nonempty UTF-8 string (indefinite length)
std::vector<uint8_t> const vec1 {0x7f, 0x61, 0x61};
@@ -1065,7 +1065,7 @@ TEST_CASE("regression tests 1")
};
json _;
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec1), "[json.exception.parse_error.113] parse error at byte 13: syntax error while parsing CBOR object key: only string keys are supported, but found a map; last byte: 0xB4", json::parse_error&);
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec1), "[json.exception.parse_error.113] parse error at byte 13: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0xB4", json::parse_error&);
// related test case: double-precision
std::vector<uint8_t> const vec2
@@ -1077,7 +1077,7 @@ TEST_CASE("regression tests 1")
0x96, 0x96, 0xb4, 0xb4, 0xfa, 0x94, 0x94, 0x61,
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0xfb
};
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec2), "[json.exception.parse_error.113] parse error at byte 13: syntax error while parsing CBOR object key: only string keys are supported, but found a map; last byte: 0xB4", json::parse_error&);
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec2), "[json.exception.parse_error.113] parse error at byte 13: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0xB4", json::parse_error&);
}
SECTION("issue #452 - Heap-buffer-overflow (OSS-Fuzz issue 585)")
@@ -1482,23 +1482,7 @@ TEST_CASE("regression tests 1")
SECTION("issue #972 - Segmentation fault on G++ when trying to assign json string literal to custom json type")
{
// this assignment used to crash outright
my_json const foo = R"([1, 2, 3])"_json;
// fifo_map is the adapter the docs recommend for keeping object keys
// in insertion order (see docs/mkdocs/docs/features/object_order.md
// and docs/mkdocs/docs/features/types/template_parameters.md); check
// that recommendation actually holds, including through erase() and
// inserting a new key. The comparator is stateful, so this avoids
// deep copies of "order" (see #1763, #5649).
my_json order = my_json::parse(R"({"z":1,"a":2,"m":{"y":1,"b":2}})");
CHECK(order.dump() == R"({"z":1,"a":2,"m":{"y":1,"b":2}})");
order.erase("z");
CHECK(order.dump() == R"({"a":2,"m":{"y":1,"b":2}})");
order["new_key"] = 3;
CHECK(order.dump() == R"({"a":2,"m":{"y":1,"b":2},"new_key":3})");
}
SECTION("issue #977 - Assigning between different json types")
+32
View File
@@ -199,6 +199,28 @@ struct adl_serializer<NonDefaultConstructible>
};
} // namespace nlohmann
/////////////////////////////////////////////////////////////////////
// for #2824
/////////////////////////////////////////////////////////////////////
class sax_no_exception : public nlohmann::detail::json_sax_dom_parser<json, nlohmann::detail::string_input_adapter_type>
{
public:
explicit sax_no_exception(json& j)
: nlohmann::detail::json_sax_dom_parser<json, nlohmann::detail::string_input_adapter_type>(j, false)
{}
static bool parse_error(std::size_t /*position*/, const std::string& /*last_token*/, const json::exception& ex)
{
error_string = new std::string(ex.what()); // NOLINT(cppcoreguidelines-owning-memory)
return false;
}
static std::string* error_string;
};
std::string* sax_no_exception::error_string = nullptr;
/////////////////////////////////////////////////////////////////////
// for #2982
/////////////////////////////////////////////////////////////////////
@@ -701,6 +723,16 @@ TEST_CASE("regression tests 2")
}
}
SECTION("issue #2824 - encoding of json::exception::what()")
{
json j;
sax_no_exception sax(j);
CHECK(!json::sax_parse("xyz", &sax));
CHECK(*sax_no_exception::error_string == "[json.exception.parse_error.101] parse error at line 1, column 1: syntax error while parsing value - invalid literal; last read: 'x'");
delete sax_no_exception::error_string; // NOLINT(cppcoreguidelines-owning-memory)
}
SECTION("issue #2825 - Properly constrain the basic_json conversion operator")
{
static_assert(std::is_copy_assignable<nlohmann::ordered_json>::value, "ordered_json must be copy assignable");
+77 -2
View File
@@ -17,9 +17,84 @@ using nlohmann::json;
#include "make_test_data_available.hpp"
#include "round_trip_corpus.hpp"
#include "test_utils.hpp"
#include "sax_countdown.hpp"
using utils::SaxCountdown;
namespace
{
class SaxCountdown
{
public:
explicit SaxCountdown(const int count) : events_left(count)
{}
bool null()
{
return events_left-- > 0;
}
bool boolean(bool /*unused*/)
{
return events_left-- > 0;
}
bool number_integer(json::number_integer_t /*unused*/)
{
return events_left-- > 0;
}
bool number_unsigned(json::number_unsigned_t /*unused*/)
{
return events_left-- > 0;
}
bool number_float(json::number_float_t /*unused*/, const std::string& /*unused*/)
{
return events_left-- > 0;
}
bool string(std::string& /*unused*/)
{
return events_left-- > 0;
}
bool binary(std::vector<std::uint8_t>& /*unused*/)
{
return events_left-- > 0;
}
bool start_object(std::size_t /*unused*/)
{
return events_left-- > 0;
}
bool key(std::string& /*unused*/)
{
return events_left-- > 0;
}
bool end_object()
{
return events_left-- > 0;
}
bool start_array(std::size_t /*unused*/)
{
return events_left-- > 0;
}
bool end_array()
{
return events_left-- > 0;
}
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const json::exception& /*unused*/) // NOLINT(readability-convert-member-functions-to-static)
{
return false;
}
private:
int events_left = 0;
};
} // namespace
TEST_CASE("UBJSON")
{
+101 -63
View File
@@ -11,97 +11,135 @@
#include <nlohmann/json.hpp>
using nlohmann::json;
// ICPC errors out on multibyte character sequences in source files
#ifndef __INTEL_COMPILER
namespace
{
bool wstring_is_utf16();
bool wstring_is_utf16()
{
return (std::wstring(L"💩") == std::wstring(L"\U0001F4A9"));
}
bool u16string_is_utf16();
bool u16string_is_utf16()
{
return (std::u16string(u"💩") == std::u16string(u"\U0001F4A9"));
}
bool u32string_is_utf32();
bool u32string_is_utf32()
{
return (std::u32string(U"💩") == std::u32string(U"\U0001F4A9"));
}
} // namespace
TEST_CASE("wide strings")
{
SECTION("std::wstring")
{
// U+10C5 U+0061(a) U+00E4 U+00F6 U+1F4A4 U+1F9E2, written with \u/\U
// escapes rather than as raw multibyte characters so this file
// compiles on toolchains (e.g. classic ICC) that error out on
// multibyte character sequences in source files
std::wstring const w = L"[12.2,\"\u10C5a\u00E4\u00F6\U0001F4A4\U0001F9E2\"]";
json const j = json::parse(w);
CHECK(j.dump() == "[12.2,\"" "\xE1\x83\x85" "a" "\xC3\xA4" "\xC3\xB6" "\xF0\x9F\x92\xA4" "\xF0\x9F\xA7\xA2" "\"]");
if (wstring_is_utf16())
{
std::wstring const w = L"[12.2,\"Ⴥaäö💤🧢\"]";
json const j = json::parse(w);
CHECK(j.dump() == "[12.2,\"Ⴥaäö💤🧢\"]");
}
}
SECTION("invalid std::wstring")
{
std::wstring const w = L"\"\xDBFF";
json _;
CHECK_THROWS_AS(_ = json::parse(w), json::parse_error&);
if (wstring_is_utf16())
{
std::wstring const w = L"\"\xDBFF";
json _;
CHECK_THROWS_AS(_ = json::parse(w), json::parse_error&);
// the exact message depends on the width of wchar_t: a 16-bit
// wchar_t passes the lone surrogate to the UTF-8 decoder unchanged
// (rejected as a single ill-formed byte at column 2), while a
// 32-bit wchar_t first encodes it as an ill-formed three-byte
// sequence (rejected one byte later, at column 3)
const char* const error_low_surrogate = sizeof(wchar_t) == 2
? "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'"
: "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xED\xB0'";
const char* const error_high_surrogate = sizeof(wchar_t) == 2
? "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'"
: "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xED\xA0'";
// the exact message depends on the width of wchar_t: a 16-bit
// wchar_t passes the lone surrogate to the UTF-8 decoder unchanged
// (rejected as a single ill-formed byte at column 2), while a
// 32-bit wchar_t first encodes it as an ill-formed three-byte
// sequence (rejected one byte later, at column 3)
const char* const error_low_surrogate = sizeof(wchar_t) == 2
? "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'"
: "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xED\xB0'";
const char* const error_high_surrogate = sizeof(wchar_t) == 2
? "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'"
: "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xED\xA0'";
// a lone low surrogate cannot start a pair
CHECK_THROWS_WITH_AS(_ = json::parse(std::wstring{L'"', static_cast<wchar_t>(0xDC00), L'"'}), error_low_surrogate, json::parse_error&);
// a high surrogate followed by a non-low-surrogate unit is invalid
CHECK_THROWS_WITH_AS(_ = json::parse(std::wstring{L'"', static_cast<wchar_t>(0xD800), L'a', L'"'}), error_high_surrogate, json::parse_error&);
// ... also when the unit is above the low surrogates
CHECK_THROWS_WITH_AS(_ = json::parse(std::wstring{L'"', static_cast<wchar_t>(0xD800), static_cast<wchar_t>(0xE000), L'"'}), error_high_surrogate, json::parse_error&);
// a lone low surrogate must not swallow the following unit: pairing
// it with any second unit would produce valid UTF-8, so the error
// has to report an ill-formed byte at the surrogate's own position
CHECK_THROWS_WITH_AS(_ = json::parse(std::wstring{L'"', static_cast<wchar_t>(0xDC00), L'a', L'"'}), error_low_surrogate, json::parse_error&);
// a lone low surrogate cannot start a pair
CHECK_THROWS_WITH_AS(_ = json::parse(std::wstring{L'"', static_cast<wchar_t>(0xDC00), L'"'}), error_low_surrogate, json::parse_error&);
// a high surrogate followed by a non-low-surrogate unit is invalid
CHECK_THROWS_WITH_AS(_ = json::parse(std::wstring{L'"', static_cast<wchar_t>(0xD800), L'a', L'"'}), error_high_surrogate, json::parse_error&);
// ... also when the unit is above the low surrogates
CHECK_THROWS_WITH_AS(_ = json::parse(std::wstring{L'"', static_cast<wchar_t>(0xD800), static_cast<wchar_t>(0xE000), L'"'}), error_high_surrogate, json::parse_error&);
// a lone low surrogate must not swallow the following unit: pairing
// it with any second unit would produce valid UTF-8, so the error
// has to report an ill-formed byte at the surrogate's own position
CHECK_THROWS_WITH_AS(_ = json::parse(std::wstring{L'"', static_cast<wchar_t>(0xDC00), L'a', L'"'}), error_low_surrogate, json::parse_error&);
}
}
SECTION("std::u16string")
{
std::u16string const w = u"[12.2,\"\u10C5a\u00E4\u00F6\U0001F4A4\U0001F9E2\"]";
json const j = json::parse(w);
CHECK(j.dump() == "[12.2,\"" "\xE1\x83\x85" "a" "\xC3\xA4" "\xC3\xB6" "\xF0\x9F\x92\xA4" "\xF0\x9F\xA7\xA2" "\"]");
if (u16string_is_utf16())
{
std::u16string const w = u"[12.2,\"Ⴥaäö💤🧢\"]";
json const j = json::parse(w);
CHECK(j.dump() == "[12.2,\"Ⴥaäö💤🧢\"]");
}
}
SECTION("invalid std::u16string")
{
std::u16string const w = u"\"\xDBFF";
json _;
CHECK_THROWS_AS(_ = json::parse(w), json::parse_error&);
if (u16string_is_utf16())
{
std::u16string const w = u"\"\xDBFF";
json _;
CHECK_THROWS_AS(_ = json::parse(w), json::parse_error&);
// a lone low surrogate cannot start a pair
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xDC00, u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'", json::parse_error&);
// a high surrogate followed by a non-low-surrogate unit is invalid
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xD800, u'a', u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'", json::parse_error&);
// ... also when the unit is above the low surrogates
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xD800, 0xE000, u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'", json::parse_error&);
// a lone low surrogate must not swallow the following unit: pairing
// it with any second unit would produce valid UTF-8, so the error
// has to report an ill-formed byte at the surrogate's own position
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xDC00, u'a', u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'", json::parse_error&);
// a valid surrogate pair is still decoded (U+1F600)
CHECK(json::parse(std::u16string{u'"', 0xD83D, 0xDE00, u'"'}).get<std::string>() == "\xF0\x9F\x98\x80");
// a lone low surrogate cannot start a pair
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xDC00, u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'", json::parse_error&);
// a high surrogate followed by a non-low-surrogate unit is invalid
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xD800, u'a', u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'", json::parse_error&);
// ... also when the unit is above the low surrogates
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xD800, 0xE000, u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'", json::parse_error&);
// a lone low surrogate must not swallow the following unit: pairing
// it with any second unit would produce valid UTF-8, so the error
// has to report an ill-formed byte at the surrogate's own position
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xDC00, u'a', u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'", json::parse_error&);
// a valid surrogate pair is still decoded (U+1F600)
CHECK(json::parse(std::u16string{u'"', 0xD83D, 0xDE00, u'"'}).get<std::string>() == "\xF0\x9F\x98\x80");
}
}
SECTION("std::u32string")
{
std::u32string const w = U"[12.2,\"\u10C5a\u00E4\u00F6\U0001F4A4\U0001F9E2\"]";
json const j = json::parse(w);
CHECK(j.dump() == "[12.2,\"" "\xE1\x83\x85" "a" "\xC3\xA4" "\xC3\xB6" "\xF0\x9F\x92\xA4" "\xF0\x9F\xA7\xA2" "\"]");
if (u32string_is_utf32())
{
std::u32string const w = U"[12.2,\"Ⴥaäö💤🧢\"]";
json const j = json::parse(w);
CHECK(j.dump() == "[12.2,\"Ⴥaäö💤🧢\"]");
}
}
SECTION("invalid std::u32string")
{
std::u32string const w = U"\"\x110000";
json _;
CHECK_THROWS_AS(_ = json::parse(w), json::parse_error&);
if (u32string_is_utf32())
{
std::u32string const w = U"\"\x110000";
json _;
CHECK_THROWS_AS(_ = json::parse(w), json::parse_error&);
// a code unit above U+10FFFF must not be narrowed onto the EOF
// sentinel: 0xFFFFFFFF would otherwise end the document silently and
// let everything following it pass the strict end-of-input check
std::u32string const trailing{U'[', U'1', U']', static_cast<char32_t>(0xFFFFFFFF), U'x'};
CHECK_THROWS_WITH_AS(_ = json::parse(trailing), "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid literal; last read: '1]\xFF'; expected end of input", json::parse_error&);
CHECK(!json::accept(trailing));
// a code unit above U+10FFFF must not be narrowed onto the EOF
// sentinel: 0xFFFFFFFF would otherwise end the document silently and
// let everything following it pass the strict end-of-input check
std::u32string const trailing{U'[', U'1', U']', static_cast<char32_t>(0xFFFFFFFF), U'x'};
CHECK_THROWS_WITH_AS(_ = json::parse(trailing), "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid literal; last read: '1]\xFF'; expected end of input", json::parse_error&);
CHECK(!json::accept(trailing));
// the same unit inside a string is reported as an ill-formed byte
CHECK_THROWS_WITH_AS(_ = json::parse(std::u32string{U'"', static_cast<char32_t>(0xFFFFFFFF), U'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xFF'", json::parse_error&);
// the same unit inside a string is reported as an ill-formed byte
CHECK_THROWS_WITH_AS(_ = json::parse(std::u32string{U'"', static_cast<char32_t>(0xFFFFFFFF), U'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xFF'", json::parse_error&);
}
}
}
#endif
-92
View File
@@ -1,92 +0,0 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++ (supporting code)
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#pragma once
// shared between unit-32bit.cpp (which must keep including this header,
// because JSON_32bitTest=ONLY builds only that file) and unit-bjdata.cpp
#include <climits> // CHAR_BIT
#include <limits>
#include <string>
#include <type_traits>
#include <nlohmann/json.hpp>
template <typename OfType, typename T, bool MinInRange, bool MaxInRange>
struct trait_test_arg
{
using of_type = OfType;
using type = T;
static constexpr bool min_in_range = MinInRange;
static constexpr bool max_in_range = MaxInRange;
};
TEST_CASE_TEMPLATE_DEFINE("value_in_range_of trait", T, value_in_range_of_test) // NOLINT(readability-math-missing-parentheses)
{
using nlohmann::detail::value_in_range_of;
using of_type = typename T::of_type;
using type = typename T::type;
constexpr bool min_in_range = T::min_in_range;
constexpr bool max_in_range = T::max_in_range;
type const val_min = std::numeric_limits<type>::min();
type const val_min2 = val_min + 1;
type const val_max = std::numeric_limits<type>::max();
type const val_max2 = val_max - 1;
REQUIRE(CHAR_BIT == 8);
std::string of_type_str;
if (std::is_unsigned<of_type>::value)
{
of_type_str += "u";
}
of_type_str += "int";
of_type_str += std::to_string(sizeof(of_type) * 8);
INFO("of_type := ", of_type_str);
std::string type_str;
if (std::is_unsigned<type>::value)
{
type_str += "u";
}
type_str += "int";
type_str += std::to_string(sizeof(type) * 8);
INFO("type := ", type_str);
CAPTURE(val_min);
CAPTURE(min_in_range);
CAPTURE(val_max);
CAPTURE(max_in_range);
if (min_in_range)
{
CHECK(value_in_range_of<of_type>(val_min));
CHECK(value_in_range_of<of_type>(val_min2));
}
else
{
CHECK_FALSE(value_in_range_of<of_type>(val_min));
CHECK_FALSE(value_in_range_of<of_type>(val_min2));
}
if (max_in_range)
{
CHECK(value_in_range_of<of_type>(val_max));
CHECK(value_in_range_of<of_type>(val_max2));
}
else
{
CHECK_FALSE(value_in_range_of<of_type>(val_max));
CHECK_FALSE(value_in_range_of<of_type>(val_max2));
}
}
+45
View File
@@ -0,0 +1,45 @@
set(LIBFUZZER_FLAGS_BASE "${CMAKE_CXX_FLAGS}")
# Disable the coverage and sanitizer instrumentation for the fuzzer itself.
set(CMAKE_CXX_FLAGS "${LIBFUZZER_FLAGS_BASE} -fno-sanitize-coverage=trace-pc-guard,edge,trace-cmp,indirect-calls,8bit-counters -Werror")
if( LLVM_USE_SANITIZE_COVERAGE )
if(NOT "${LLVM_USE_SANITIZER}" STREQUAL "Address")
message(FATAL_ERROR
"LibFuzzer and its tests require LLVM_USE_SANITIZER=Address and "
"LLVM_USE_SANITIZE_COVERAGE=YES to be set."
)
endif()
add_library(LLVMFuzzerNoMainObjects OBJECT
FuzzerCrossOver.cpp
FuzzerDriver.cpp
FuzzerExtFunctionsDlsym.cpp
FuzzerExtFunctionsWeak.cpp
FuzzerExtFunctionsWeakAlias.cpp
FuzzerIO.cpp
FuzzerIOPosix.cpp
FuzzerIOWindows.cpp
FuzzerLoop.cpp
FuzzerMerge.cpp
FuzzerMutate.cpp
FuzzerSHA1.cpp
FuzzerTracePC.cpp
FuzzerTraceState.cpp
FuzzerUtil.cpp
FuzzerUtilDarwin.cpp
FuzzerUtilLinux.cpp
FuzzerUtilPosix.cpp
FuzzerUtilWindows.cpp
)
add_library(LLVMFuzzerNoMain STATIC
$<TARGET_OBJECTS:LLVMFuzzerNoMainObjects>
)
target_link_libraries(LLVMFuzzerNoMain ${PTHREAD_LIB})
add_library(LLVMFuzzer STATIC
FuzzerMain.cpp
$<TARGET_OBJECTS:LLVMFuzzerNoMainObjects>
)
target_link_libraries(LLVMFuzzer ${PTHREAD_LIB})
if( LLVM_INCLUDE_TESTS )
add_subdirectory(test)
endif()
endif()
+217
View File
@@ -0,0 +1,217 @@
//===- FuzzerCorpus.h - Internal header for the Fuzzer ----------*- C++ -* ===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// fuzzer::InputCorpus
//===----------------------------------------------------------------------===//
#ifndef LLVM_FUZZER_CORPUS
#define LLVM_FUZZER_CORPUS
#include "FuzzerDefs.h"
#include "FuzzerIO.h"
#include "FuzzerRandom.h"
#include "FuzzerSHA1.h"
#include "FuzzerTracePC.h"
#include <numeric>
#include <random>
#include <unordered_set>
namespace fuzzer {
struct InputInfo {
Unit U; // The actual input data.
uint8_t Sha1[kSHA1NumBytes]; // Checksum.
// Number of features that this input has and no smaller input has.
size_t NumFeatures = 0;
size_t Tmp = 0; // Used by ValidateFeatureSet.
// Stats.
size_t NumExecutedMutations = 0;
size_t NumSuccessfullMutations = 0;
bool MayDeleteFile = false;
};
class InputCorpus {
public:
static const size_t kFeatureSetSize = 1 << 16;
InputCorpus(const std::string &OutputCorpus) : OutputCorpus(OutputCorpus) {
memset(InputSizesPerFeature, 0, sizeof(InputSizesPerFeature));
memset(SmallestElementPerFeature, 0, sizeof(SmallestElementPerFeature));
}
~InputCorpus() {
for (auto II : Inputs)
delete II;
}
size_t size() const { return Inputs.size(); }
size_t SizeInBytes() const {
size_t Res = 0;
for (auto II : Inputs)
Res += II->U.size();
return Res;
}
size_t NumActiveUnits() const {
size_t Res = 0;
for (auto II : Inputs)
Res += !II->U.empty();
return Res;
}
bool empty() const { return Inputs.empty(); }
const Unit &operator[] (size_t Idx) const { return Inputs[Idx]->U; }
void AddToCorpus(const Unit &U, size_t NumFeatures, bool MayDeleteFile = false) {
assert(!U.empty());
uint8_t Hash[kSHA1NumBytes];
if (FeatureDebug)
Printf("ADD_TO_CORPUS %zd NF %zd\n", Inputs.size(), NumFeatures);
ComputeSHA1(U.data(), U.size(), Hash);
Hashes.insert(Sha1ToString(Hash));
Inputs.push_back(new InputInfo());
InputInfo &II = *Inputs.back();
II.U = U;
II.NumFeatures = NumFeatures;
II.MayDeleteFile = MayDeleteFile;
memcpy(II.Sha1, Hash, kSHA1NumBytes);
UpdateCorpusDistribution();
ValidateFeatureSet();
}
bool HasUnit(const Unit &U) { return Hashes.count(Hash(U)); }
bool HasUnit(const std::string &H) { return Hashes.count(H); }
InputInfo &ChooseUnitToMutate(Random &Rand) {
InputInfo &II = *Inputs[ChooseUnitIdxToMutate(Rand)];
assert(!II.U.empty());
return II;
};
// Returns an index of random unit from the corpus to mutate.
// Hypothesis: units added to the corpus last are more likely to be
// interesting. This function gives more weight to the more recent units.
size_t ChooseUnitIdxToMutate(Random &Rand) {
size_t Idx = static_cast<size_t>(CorpusDistribution(Rand.Get_mt19937()));
assert(Idx < Inputs.size());
return Idx;
}
void PrintStats() {
for (size_t i = 0; i < Inputs.size(); i++) {
const auto &II = *Inputs[i];
Printf(" [%zd %s]\tsz: %zd\truns: %zd\tsucc: %zd\n", i,
Sha1ToString(II.Sha1).c_str(), II.U.size(),
II.NumExecutedMutations, II.NumSuccessfullMutations);
}
}
void PrintFeatureSet() {
for (size_t i = 0; i < kFeatureSetSize; i++) {
if(size_t Sz = GetFeature(i))
Printf("[%zd: id %zd sz%zd] ", i, SmallestElementPerFeature[i], Sz);
}
Printf("\n\t");
for (size_t i = 0; i < Inputs.size(); i++)
if (size_t N = Inputs[i]->NumFeatures)
Printf(" %zd=>%zd ", i, N);
Printf("\n");
}
void DeleteInput(size_t Idx) {
InputInfo &II = *Inputs[Idx];
if (!OutputCorpus.empty() && II.MayDeleteFile)
RemoveFile(DirPlusFile(OutputCorpus, Sha1ToString(II.Sha1)));
Unit().swap(II.U);
if (FeatureDebug)
Printf("EVICTED %zd\n", Idx);
}
bool AddFeature(size_t Idx, uint32_t NewSize, bool Shrink) {
assert(NewSize);
Idx = Idx % kFeatureSetSize;
uint32_t OldSize = GetFeature(Idx);
if (OldSize == 0 || (Shrink && OldSize > NewSize)) {
if (OldSize > 0) {
size_t OldIdx = SmallestElementPerFeature[Idx];
InputInfo &II = *Inputs[OldIdx];
assert(II.NumFeatures > 0);
II.NumFeatures--;
if (II.NumFeatures == 0)
DeleteInput(OldIdx);
}
if (FeatureDebug)
Printf("ADD FEATURE %zd sz %d\n", Idx, NewSize);
SmallestElementPerFeature[Idx] = Inputs.size();
InputSizesPerFeature[Idx] = NewSize;
CountingFeatures = true;
return true;
}
return false;
}
size_t NumFeatures() const {
size_t Res = 0;
for (size_t i = 0; i < kFeatureSetSize; i++)
Res += GetFeature(i) != 0;
return Res;
}
void ResetFeatureSet() {
assert(Inputs.empty());
memset(InputSizesPerFeature, 0, sizeof(InputSizesPerFeature));
memset(SmallestElementPerFeature, 0, sizeof(SmallestElementPerFeature));
}
private:
static const bool FeatureDebug = false;
size_t GetFeature(size_t Idx) const { return InputSizesPerFeature[Idx]; }
void ValidateFeatureSet() {
if (!CountingFeatures) return;
if (FeatureDebug)
PrintFeatureSet();
for (size_t Idx = 0; Idx < kFeatureSetSize; Idx++)
if (GetFeature(Idx))
Inputs[SmallestElementPerFeature[Idx]]->Tmp++;
for (auto II: Inputs) {
if (II->Tmp != II->NumFeatures)
Printf("ZZZ %zd %zd\n", II->Tmp, II->NumFeatures);
assert(II->Tmp == II->NumFeatures);
II->Tmp = 0;
}
}
// Updates the probability distribution for the units in the corpus.
// Must be called whenever the corpus or unit weights are changed.
void UpdateCorpusDistribution() {
size_t N = Inputs.size();
Intervals.resize(N + 1);
Weights.resize(N);
std::iota(Intervals.begin(), Intervals.end(), 0);
if (CountingFeatures)
for (size_t i = 0; i < N; i++)
Weights[i] = Inputs[i]->NumFeatures * (i + 1);
else
std::iota(Weights.begin(), Weights.end(), 1);
CorpusDistribution = std::piecewise_constant_distribution<double>(
Intervals.begin(), Intervals.end(), Weights.begin());
}
std::piecewise_constant_distribution<double> CorpusDistribution;
std::vector<double> Intervals;
std::vector<double> Weights;
std::unordered_set<std::string> Hashes;
std::vector<InputInfo*> Inputs;
bool CountingFeatures = false;
uint32_t InputSizesPerFeature[kFeatureSetSize];
uint32_t SmallestElementPerFeature[kFeatureSetSize];
std::string OutputCorpus;
};
} // namespace fuzzer
#endif // LLVM_FUZZER_CORPUS
+52
View File
@@ -0,0 +1,52 @@
//===- FuzzerCrossOver.cpp - Cross over two test inputs -------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// Cross over test inputs.
//===----------------------------------------------------------------------===//
#include "FuzzerDefs.h"
#include "FuzzerMutate.h"
#include "FuzzerRandom.h"
#include <cstring>
namespace fuzzer {
// Cross Data1 and Data2, store the result (up to MaxOutSize bytes) in Out.
size_t MutationDispatcher::CrossOver(const uint8_t *Data1, size_t Size1,
const uint8_t *Data2, size_t Size2,
uint8_t *Out, size_t MaxOutSize) {
assert(Size1 || Size2);
MaxOutSize = Rand(MaxOutSize) + 1;
size_t OutPos = 0;
size_t Pos1 = 0;
size_t Pos2 = 0;
size_t *InPos = &Pos1;
size_t InSize = Size1;
const uint8_t *Data = Data1;
bool CurrentlyUsingFirstData = true;
while (OutPos < MaxOutSize && (Pos1 < Size1 || Pos2 < Size2)) {
// Merge a part of Data into Out.
size_t OutSizeLeft = MaxOutSize - OutPos;
if (*InPos < InSize) {
size_t InSizeLeft = InSize - *InPos;
size_t MaxExtraSize = std::min(OutSizeLeft, InSizeLeft);
size_t ExtraSize = Rand(MaxExtraSize) + 1;
memcpy(Out + OutPos, Data + *InPos, ExtraSize);
OutPos += ExtraSize;
(*InPos) += ExtraSize;
}
// Use the other input data on the next iteration.
InPos = CurrentlyUsingFirstData ? &Pos2 : &Pos1;
InSize = CurrentlyUsingFirstData ? Size2 : Size1;
Data = CurrentlyUsingFirstData ? Data2 : Data1;
CurrentlyUsingFirstData = !CurrentlyUsingFirstData;
}
return OutPos;
}
} // namespace fuzzer
+89
View File
@@ -0,0 +1,89 @@
//===- FuzzerDefs.h - Internal header for the Fuzzer ------------*- C++ -* ===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// Basic definitions.
//===----------------------------------------------------------------------===//
#ifndef LLVM_FUZZER_DEFS_H
#define LLVM_FUZZER_DEFS_H
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <string>
#include <vector>
// Platform detection.
#ifdef __linux__
#define LIBFUZZER_APPLE 0
#define LIBFUZZER_LINUX 1
#define LIBFUZZER_WINDOWS 0
#elif __APPLE__
#define LIBFUZZER_APPLE 1
#define LIBFUZZER_LINUX 0
#define LIBFUZZER_WINDOWS 0
#elif _WIN32
#define LIBFUZZER_APPLE 0
#define LIBFUZZER_LINUX 0
#define LIBFUZZER_WINDOWS 1
#else
#error "Support for your platform has not been implemented"
#endif
#define LIBFUZZER_POSIX LIBFUZZER_APPLE || LIBFUZZER_LINUX
#ifdef __x86_64
#define ATTRIBUTE_TARGET_POPCNT __attribute__((target("popcnt")))
#else
#define ATTRIBUTE_TARGET_POPCNT
#endif
#ifdef __clang__ // avoid gcc warning.
# define ATTRIBUTE_NO_SANITIZE_MEMORY __attribute__((no_sanitize("memory")))
#else
# define ATTRIBUTE_NO_SANITIZE_MEMORY
#endif
namespace fuzzer {
template <class T> T Min(T a, T b) { return a < b ? a : b; }
template <class T> T Max(T a, T b) { return a > b ? a : b; }
class Random;
class Dictionary;
class DictionaryEntry;
class MutationDispatcher;
struct FuzzingOptions;
class InputCorpus;
struct InputInfo;
struct ExternalFunctions;
// Global interface to functions that may or may not be available.
extern ExternalFunctions *EF;
typedef std::vector<uint8_t> Unit;
typedef std::vector<Unit> UnitVector;
typedef int (*UserCallback)(const uint8_t *Data, size_t Size);
int FuzzerDriver(int *argc, char ***argv, UserCallback Callback);
struct ScopedDoingMyOwnMemmem {
ScopedDoingMyOwnMemmem();
~ScopedDoingMyOwnMemmem();
};
inline uint8_t Bswap(uint8_t x) { return x; }
inline uint16_t Bswap(uint16_t x) { return __builtin_bswap16(x); }
inline uint32_t Bswap(uint32_t x) { return __builtin_bswap32(x); }
inline uint64_t Bswap(uint64_t x) { return __builtin_bswap64(x); }
} // namespace fuzzer
#endif // LLVM_FUZZER_DEFS_H
+124
View File
@@ -0,0 +1,124 @@
//===- FuzzerDictionary.h - Internal header for the Fuzzer ------*- C++ -* ===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// fuzzer::Dictionary
//===----------------------------------------------------------------------===//
#ifndef LLVM_FUZZER_DICTIONARY_H
#define LLVM_FUZZER_DICTIONARY_H
#include "FuzzerDefs.h"
#include "FuzzerIO.h"
#include "FuzzerUtil.h"
#include <algorithm>
#include <limits>
namespace fuzzer {
// A simple POD sized array of bytes.
template <size_t kMaxSize> class FixedWord {
public:
FixedWord() {}
FixedWord(const uint8_t *B, uint8_t S) { Set(B, S); }
void Set(const uint8_t *B, uint8_t S) {
assert(S <= kMaxSize);
memcpy(Data, B, S);
Size = S;
}
bool operator==(const FixedWord<kMaxSize> &w) const {
return Size == w.Size && 0 == memcmp(Data, w.Data, Size);
}
bool operator<(const FixedWord<kMaxSize> &w) const {
if (Size != w.Size)
return Size < w.Size;
return memcmp(Data, w.Data, Size) < 0;
}
static size_t GetMaxSize() { return kMaxSize; }
const uint8_t *data() const { return Data; }
uint8_t size() const { return Size; }
private:
uint8_t Size = 0;
uint8_t Data[kMaxSize];
};
typedef FixedWord<27> Word; // 28 bytes.
class DictionaryEntry {
public:
DictionaryEntry() {}
DictionaryEntry(Word W) : W(W) {}
DictionaryEntry(Word W, size_t PositionHint) : W(W), PositionHint(PositionHint) {}
const Word &GetW() const { return W; }
bool HasPositionHint() const { return PositionHint != std::numeric_limits<size_t>::max(); }
size_t GetPositionHint() const {
assert(HasPositionHint());
return PositionHint;
}
void IncUseCount() { UseCount++; }
void IncSuccessCount() { SuccessCount++; }
size_t GetUseCount() const { return UseCount; }
size_t GetSuccessCount() const {return SuccessCount; }
void Print(const char *PrintAfter = "\n") {
PrintASCII(W.data(), W.size());
if (HasPositionHint())
Printf("@%zd", GetPositionHint());
Printf("%s", PrintAfter);
}
private:
Word W;
size_t PositionHint = std::numeric_limits<size_t>::max();
size_t UseCount = 0;
size_t SuccessCount = 0;
};
class Dictionary {
public:
static const size_t kMaxDictSize = 1 << 14;
bool ContainsWord(const Word &W) const {
return std::any_of(begin(), end(), [&](const DictionaryEntry &DE) {
return DE.GetW() == W;
});
}
const DictionaryEntry *begin() const { return &DE[0]; }
const DictionaryEntry *end() const { return begin() + Size; }
DictionaryEntry & operator[] (size_t Idx) {
assert(Idx < Size);
return DE[Idx];
}
void push_back(DictionaryEntry DE) {
if (Size < kMaxDictSize)
this->DE[Size++] = DE;
}
void clear() { Size = 0; }
bool empty() const { return Size == 0; }
size_t size() const { return Size; }
private:
DictionaryEntry DE[kMaxDictSize];
size_t Size = 0;
};
// Parses one dictionary entry.
// If successfull, write the enty to Unit and returns true,
// otherwise returns false.
bool ParseOneDictionaryEntry(const std::string &Str, Unit *U);
// Parses the dictionary file, fills Units, returns true iff all lines
// were parsed succesfully.
bool ParseDictionaryFile(const std::string &Text, std::vector<Unit> *Units);
} // namespace fuzzer
#endif // LLVM_FUZZER_DICTIONARY_H
+545
View File
@@ -0,0 +1,545 @@
//===- FuzzerDriver.cpp - FuzzerDriver function and flags -----------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// FuzzerDriver and flag parsing.
//===----------------------------------------------------------------------===//
#include "FuzzerCorpus.h"
#include "FuzzerInterface.h"
#include "FuzzerInternal.h"
#include "FuzzerIO.h"
#include "FuzzerMutate.h"
#include "FuzzerRandom.h"
#include "FuzzerTracePC.h"
#include <algorithm>
#include <atomic>
#include <chrono>
#include <cstring>
#include <mutex>
#include <string>
#include <thread>
// This function should be present in the libFuzzer so that the client
// binary can test for its existence.
extern "C" __attribute__((used)) void __libfuzzer_is_present() {}
namespace fuzzer {
// Program arguments.
struct FlagDescription {
const char *Name;
const char *Description;
int Default;
int *IntFlag;
const char **StrFlag;
unsigned int *UIntFlag;
};
struct {
#define FUZZER_DEPRECATED_FLAG(Name)
#define FUZZER_FLAG_INT(Name, Default, Description) int Name;
#define FUZZER_FLAG_UNSIGNED(Name, Default, Description) unsigned int Name;
#define FUZZER_FLAG_STRING(Name, Description) const char *Name;
#include "FuzzerFlags.def"
#undef FUZZER_DEPRECATED_FLAG
#undef FUZZER_FLAG_INT
#undef FUZZER_FLAG_UNSIGNED
#undef FUZZER_FLAG_STRING
} Flags;
static const FlagDescription FlagDescriptions [] {
#define FUZZER_DEPRECATED_FLAG(Name) \
{#Name, "Deprecated; don't use", 0, nullptr, nullptr, nullptr},
#define FUZZER_FLAG_INT(Name, Default, Description) \
{#Name, Description, Default, &Flags.Name, nullptr, nullptr},
#define FUZZER_FLAG_UNSIGNED(Name, Default, Description) \
{#Name, Description, static_cast<int>(Default), \
nullptr, nullptr, &Flags.Name},
#define FUZZER_FLAG_STRING(Name, Description) \
{#Name, Description, 0, nullptr, &Flags.Name, nullptr},
#include "FuzzerFlags.def"
#undef FUZZER_DEPRECATED_FLAG
#undef FUZZER_FLAG_INT
#undef FUZZER_FLAG_UNSIGNED
#undef FUZZER_FLAG_STRING
};
static const size_t kNumFlags =
sizeof(FlagDescriptions) / sizeof(FlagDescriptions[0]);
static std::vector<std::string> *Inputs;
static std::string *ProgName;
static void PrintHelp() {
Printf("Usage:\n");
auto Prog = ProgName->c_str();
Printf("\nTo run fuzzing pass 0 or more directories.\n");
Printf("%s [-flag1=val1 [-flag2=val2 ...] ] [dir1 [dir2 ...] ]\n", Prog);
Printf("\nTo run individual tests without fuzzing pass 1 or more files:\n");
Printf("%s [-flag1=val1 [-flag2=val2 ...] ] file1 [file2 ...]\n", Prog);
Printf("\nFlags: (strictly in form -flag=value)\n");
size_t MaxFlagLen = 0;
for (size_t F = 0; F < kNumFlags; F++)
MaxFlagLen = std::max(strlen(FlagDescriptions[F].Name), MaxFlagLen);
for (size_t F = 0; F < kNumFlags; F++) {
const auto &D = FlagDescriptions[F];
if (strstr(D.Description, "internal flag") == D.Description) continue;
Printf(" %s", D.Name);
for (size_t i = 0, n = MaxFlagLen - strlen(D.Name); i < n; i++)
Printf(" ");
Printf("\t");
Printf("%d\t%s\n", D.Default, D.Description);
}
Printf("\nFlags starting with '--' will be ignored and "
"will be passed verbatim to subprocesses.\n");
}
static const char *FlagValue(const char *Param, const char *Name) {
size_t Len = strlen(Name);
if (Param[0] == '-' && strstr(Param + 1, Name) == Param + 1 &&
Param[Len + 1] == '=')
return &Param[Len + 2];
return nullptr;
}
// Avoid calling stol as it triggers a bug in clang/glibc build.
static long MyStol(const char *Str) {
long Res = 0;
long Sign = 1;
if (*Str == '-') {
Str++;
Sign = -1;
}
for (size_t i = 0; Str[i]; i++) {
char Ch = Str[i];
if (Ch < '0' || Ch > '9')
return Res;
Res = Res * 10 + (Ch - '0');
}
return Res * Sign;
}
static bool ParseOneFlag(const char *Param) {
if (Param[0] != '-') return false;
if (Param[1] == '-') {
static bool PrintedWarning = false;
if (!PrintedWarning) {
PrintedWarning = true;
Printf("INFO: libFuzzer ignores flags that start with '--'\n");
}
for (size_t F = 0; F < kNumFlags; F++)
if (FlagValue(Param + 1, FlagDescriptions[F].Name))
Printf("WARNING: did you mean '%s' (single dash)?\n", Param + 1);
return true;
}
for (size_t F = 0; F < kNumFlags; F++) {
const char *Name = FlagDescriptions[F].Name;
const char *Str = FlagValue(Param, Name);
if (Str) {
if (FlagDescriptions[F].IntFlag) {
int Val = MyStol(Str);
*FlagDescriptions[F].IntFlag = Val;
if (Flags.verbosity >= 2)
Printf("Flag: %s %d\n", Name, Val);
return true;
} else if (FlagDescriptions[F].UIntFlag) {
unsigned int Val = std::stoul(Str);
*FlagDescriptions[F].UIntFlag = Val;
if (Flags.verbosity >= 2)
Printf("Flag: %s %u\n", Name, Val);
return true;
} else if (FlagDescriptions[F].StrFlag) {
*FlagDescriptions[F].StrFlag = Str;
if (Flags.verbosity >= 2)
Printf("Flag: %s %s\n", Name, Str);
return true;
} else { // Deprecated flag.
Printf("Flag: %s: deprecated, don't use\n", Name);
return true;
}
}
}
Printf("\n\nWARNING: unrecognized flag '%s'; "
"use -help=1 to list all flags\n\n", Param);
return true;
}
// We don't use any library to minimize dependencies.
static void ParseFlags(const std::vector<std::string> &Args) {
for (size_t F = 0; F < kNumFlags; F++) {
if (FlagDescriptions[F].IntFlag)
*FlagDescriptions[F].IntFlag = FlagDescriptions[F].Default;
if (FlagDescriptions[F].UIntFlag)
*FlagDescriptions[F].UIntFlag =
static_cast<unsigned int>(FlagDescriptions[F].Default);
if (FlagDescriptions[F].StrFlag)
*FlagDescriptions[F].StrFlag = nullptr;
}
Inputs = new std::vector<std::string>;
for (size_t A = 1; A < Args.size(); A++) {
if (ParseOneFlag(Args[A].c_str())) continue;
Inputs->push_back(Args[A]);
}
}
static std::mutex Mu;
static void PulseThread() {
while (true) {
SleepSeconds(600);
std::lock_guard<std::mutex> Lock(Mu);
Printf("pulse...\n");
}
}
static void WorkerThread(const std::string &Cmd, std::atomic<unsigned> *Counter,
unsigned NumJobs, std::atomic<bool> *HasErrors) {
while (true) {
unsigned C = (*Counter)++;
if (C >= NumJobs) break;
std::string Log = "fuzz-" + std::to_string(C) + ".log";
std::string ToRun = Cmd + " > " + Log + " 2>&1\n";
if (Flags.verbosity)
Printf("%s", ToRun.c_str());
int ExitCode = ExecuteCommand(ToRun);
if (ExitCode != 0)
*HasErrors = true;
std::lock_guard<std::mutex> Lock(Mu);
Printf("================== Job %u exited with exit code %d ============\n",
C, ExitCode);
fuzzer::CopyFileToErr(Log);
}
}
std::string CloneArgsWithoutX(const std::vector<std::string> &Args,
const char *X1, const char *X2) {
std::string Cmd;
for (auto &S : Args) {
if (FlagValue(S.c_str(), X1) || FlagValue(S.c_str(), X2))
continue;
Cmd += S + " ";
}
return Cmd;
}
static int RunInMultipleProcesses(const std::vector<std::string> &Args,
unsigned NumWorkers, unsigned NumJobs) {
std::atomic<unsigned> Counter(0);
std::atomic<bool> HasErrors(false);
std::string Cmd = CloneArgsWithoutX(Args, "jobs", "workers");
std::vector<std::thread> V;
std::thread Pulse(PulseThread);
Pulse.detach();
for (unsigned i = 0; i < NumWorkers; i++)
V.push_back(std::thread(WorkerThread, Cmd, &Counter, NumJobs, &HasErrors));
for (auto &T : V)
T.join();
return HasErrors ? 1 : 0;
}
static void RssThread(Fuzzer *F, size_t RssLimitMb) {
while (true) {
SleepSeconds(1);
size_t Peak = GetPeakRSSMb();
if (Peak > RssLimitMb)
F->RssLimitCallback();
}
}
static void StartRssThread(Fuzzer *F, size_t RssLimitMb) {
if (!RssLimitMb) return;
std::thread T(RssThread, F, RssLimitMb);
T.detach();
}
int RunOneTest(Fuzzer *F, const char *InputFilePath, size_t MaxLen) {
Unit U = FileToVector(InputFilePath);
if (MaxLen && MaxLen < U.size())
U.resize(MaxLen);
F->RunOne(U.data(), U.size());
F->TryDetectingAMemoryLeak(U.data(), U.size(), true);
return 0;
}
static bool AllInputsAreFiles() {
if (Inputs->empty()) return false;
for (auto &Path : *Inputs)
if (!IsFile(Path))
return false;
return true;
}
int MinimizeCrashInput(const std::vector<std::string> &Args) {
if (Inputs->size() != 1) {
Printf("ERROR: -minimize_crash should be given one input file\n");
exit(1);
}
std::string InputFilePath = Inputs->at(0);
std::string BaseCmd =
CloneArgsWithoutX(Args, "minimize_crash", "exact_artifact_path");
auto InputPos = BaseCmd.find(" " + InputFilePath + " ");
assert(InputPos != std::string::npos);
BaseCmd.erase(InputPos, InputFilePath.size() + 1);
if (Flags.runs <= 0 && Flags.max_total_time == 0) {
Printf("INFO: you need to specify -runs=N or "
"-max_total_time=N with -minimize_crash=1\n"
"INFO: defaulting to -max_total_time=600\n");
BaseCmd += " -max_total_time=600";
}
// BaseCmd += " > /dev/null 2>&1 ";
std::string CurrentFilePath = InputFilePath;
while (true) {
Unit U = FileToVector(CurrentFilePath);
if (U.size() < 2) {
Printf("CRASH_MIN: '%s' is small enough\n", CurrentFilePath.c_str());
return 0;
}
Printf("CRASH_MIN: minimizing crash input: '%s' (%zd bytes)\n",
CurrentFilePath.c_str(), U.size());
auto Cmd = BaseCmd + " " + CurrentFilePath;
Printf("CRASH_MIN: executing: %s\n", Cmd.c_str());
int ExitCode = ExecuteCommand(Cmd);
if (ExitCode == 0) {
Printf("ERROR: the input %s did not crash\n", CurrentFilePath.c_str());
exit(1);
}
Printf("CRASH_MIN: '%s' (%zd bytes) caused a crash. Will try to minimize "
"it further\n",
CurrentFilePath.c_str(), U.size());
std::string ArtifactPath = "minimized-from-" + Hash(U);
Cmd += " -minimize_crash_internal_step=1 -exact_artifact_path=" +
ArtifactPath;
Printf("CRASH_MIN: executing: %s\n", Cmd.c_str());
ExitCode = ExecuteCommand(Cmd);
if (ExitCode == 0) {
if (Flags.exact_artifact_path) {
CurrentFilePath = Flags.exact_artifact_path;
WriteToFile(U, CurrentFilePath);
}
Printf("CRASH_MIN: failed to minimize beyond %s (%d bytes), exiting\n",
CurrentFilePath.c_str(), U.size());
return 0;
}
CurrentFilePath = ArtifactPath;
Printf("\n\n\n\n\n\n*********************************\n");
}
return 0;
}
int MinimizeCrashInputInternalStep(Fuzzer *F, InputCorpus *Corpus) {
assert(Inputs->size() == 1);
std::string InputFilePath = Inputs->at(0);
Unit U = FileToVector(InputFilePath);
assert(U.size() > 2);
Printf("INFO: Starting MinimizeCrashInputInternalStep: %zd\n", U.size());
Corpus->AddToCorpus(U, 0);
F->SetMaxInputLen(U.size());
F->SetMaxMutationLen(U.size() - 1);
F->MinimizeCrashLoop(U);
Printf("INFO: Done MinimizeCrashInputInternalStep, no crashes found\n");
exit(0);
return 0;
}
int FuzzerDriver(int *argc, char ***argv, UserCallback Callback) {
using namespace fuzzer;
assert(argc && argv && "Argument pointers cannot be nullptr");
EF = new ExternalFunctions();
if (EF->LLVMFuzzerInitialize)
EF->LLVMFuzzerInitialize(argc, argv);
const std::vector<std::string> Args(*argv, *argv + *argc);
assert(!Args.empty());
ProgName = new std::string(Args[0]);
ParseFlags(Args);
if (Flags.help) {
PrintHelp();
return 0;
}
if (Flags.minimize_crash)
return MinimizeCrashInput(Args);
if (Flags.close_fd_mask & 2)
DupAndCloseStderr();
if (Flags.close_fd_mask & 1)
CloseStdout();
if (Flags.jobs > 0 && Flags.workers == 0) {
Flags.workers = std::min(NumberOfCpuCores() / 2, Flags.jobs);
if (Flags.workers > 1)
Printf("Running %u workers\n", Flags.workers);
}
if (Flags.workers > 0 && Flags.jobs > 0)
return RunInMultipleProcesses(Args, Flags.workers, Flags.jobs);
const size_t kMaxSaneLen = 1 << 20;
const size_t kMinDefaultLen = 64;
FuzzingOptions Options;
Options.Verbosity = Flags.verbosity;
Options.MaxLen = Flags.max_len;
Options.UnitTimeoutSec = Flags.timeout;
Options.ErrorExitCode = Flags.error_exitcode;
Options.TimeoutExitCode = Flags.timeout_exitcode;
Options.MaxTotalTimeSec = Flags.max_total_time;
Options.DoCrossOver = Flags.cross_over;
Options.MutateDepth = Flags.mutate_depth;
Options.UseCounters = Flags.use_counters;
Options.UseIndirCalls = Flags.use_indir_calls;
Options.UseMemcmp = Flags.use_memcmp;
Options.UseMemmem = Flags.use_memmem;
Options.UseCmp = Flags.use_cmp;
Options.UseValueProfile = Flags.use_value_profile;
Options.Shrink = Flags.shrink;
Options.ShuffleAtStartUp = Flags.shuffle;
Options.PreferSmall = Flags.prefer_small;
Options.ReloadIntervalSec = Flags.reload;
Options.OnlyASCII = Flags.only_ascii;
Options.OutputCSV = Flags.output_csv;
Options.DetectLeaks = Flags.detect_leaks;
Options.TraceMalloc = Flags.trace_malloc;
Options.RssLimitMb = Flags.rss_limit_mb;
if (Flags.runs >= 0)
Options.MaxNumberOfRuns = Flags.runs;
if (!Inputs->empty() && !Flags.minimize_crash_internal_step)
Options.OutputCorpus = (*Inputs)[0];
Options.ReportSlowUnits = Flags.report_slow_units;
if (Flags.artifact_prefix)
Options.ArtifactPrefix = Flags.artifact_prefix;
if (Flags.exact_artifact_path)
Options.ExactArtifactPath = Flags.exact_artifact_path;
std::vector<Unit> Dictionary;
if (Flags.dict)
if (!ParseDictionaryFile(FileToString(Flags.dict), &Dictionary))
return 1;
if (Flags.verbosity > 0 && !Dictionary.empty())
Printf("Dictionary: %zd entries\n", Dictionary.size());
bool DoPlainRun = AllInputsAreFiles();
Options.SaveArtifacts =
!DoPlainRun || Flags.minimize_crash_internal_step;
Options.PrintNewCovPcs = Flags.print_pcs;
Options.PrintFinalStats = Flags.print_final_stats;
Options.PrintCorpusStats = Flags.print_corpus_stats;
Options.PrintCoverage = Flags.print_coverage;
Options.DumpCoverage = Flags.dump_coverage;
if (Flags.exit_on_src_pos)
Options.ExitOnSrcPos = Flags.exit_on_src_pos;
if (Flags.exit_on_item)
Options.ExitOnItem = Flags.exit_on_item;
unsigned Seed = Flags.seed;
// Initialize Seed.
if (Seed == 0)
Seed = (std::chrono::system_clock::now().time_since_epoch().count() << 10) +
GetPid();
if (Flags.verbosity)
Printf("INFO: Seed: %u\n", Seed);
Random Rand(Seed);
auto *MD = new MutationDispatcher(Rand, Options);
auto *Corpus = new InputCorpus(Options.OutputCorpus);
auto *F = new Fuzzer(Callback, *Corpus, *MD, Options);
for (auto &U: Dictionary)
if (U.size() <= Word::GetMaxSize())
MD->AddWordToManualDictionary(Word(U.data(), U.size()));
StartRssThread(F, Flags.rss_limit_mb);
Options.HandleAbrt = Flags.handle_abrt;
Options.HandleBus = Flags.handle_bus;
Options.HandleFpe = Flags.handle_fpe;
Options.HandleIll = Flags.handle_ill;
Options.HandleInt = Flags.handle_int;
Options.HandleSegv = Flags.handle_segv;
Options.HandleTerm = Flags.handle_term;
SetSignalHandler(Options);
if (Flags.minimize_crash_internal_step)
return MinimizeCrashInputInternalStep(F, Corpus);
if (DoPlainRun) {
Options.SaveArtifacts = false;
int Runs = std::max(1, Flags.runs);
Printf("%s: Running %zd inputs %d time(s) each.\n", ProgName->c_str(),
Inputs->size(), Runs);
for (auto &Path : *Inputs) {
auto StartTime = system_clock::now();
Printf("Running: %s\n", Path.c_str());
for (int Iter = 0; Iter < Runs; Iter++)
RunOneTest(F, Path.c_str(), Options.MaxLen);
auto StopTime = system_clock::now();
auto MS = duration_cast<milliseconds>(StopTime - StartTime).count();
Printf("Executed %s in %zd ms\n", Path.c_str(), (long)MS);
}
Printf("***\n"
"*** NOTE: fuzzing was not performed, you have only\n"
"*** executed the target code on a fixed set of inputs.\n"
"***\n");
F->PrintFinalStats();
exit(0);
}
if (Flags.merge) {
if (Options.MaxLen == 0)
F->SetMaxInputLen(kMaxSaneLen);
if (TPC.UsingTracePcGuard()) {
if (Flags.merge_control_file)
F->CrashResistantMergeInternalStep(Flags.merge_control_file);
else
F->CrashResistantMerge(Args, *Inputs);
} else {
F->Merge(*Inputs);
}
exit(0);
}
size_t TemporaryMaxLen = Options.MaxLen ? Options.MaxLen : kMaxSaneLen;
UnitVector InitialCorpus;
for (auto &Inp : *Inputs) {
Printf("Loading corpus dir: %s\n", Inp.c_str());
ReadDirToVectorOfUnits(Inp.c_str(), &InitialCorpus, nullptr,
TemporaryMaxLen, /*ExitOnError=*/false);
}
if (Options.MaxLen == 0) {
size_t MaxLen = 0;
for (auto &U : InitialCorpus)
MaxLen = std::max(U.size(), MaxLen);
F->SetMaxInputLen(std::min(std::max(kMinDefaultLen, MaxLen), kMaxSaneLen));
}
if (InitialCorpus.empty()) {
InitialCorpus.push_back(Unit({'\n'})); // Valid ASCII input.
if (Options.Verbosity)
Printf("INFO: A corpus is not provided, starting from an empty corpus\n");
}
F->ShuffleAndMinimize(&InitialCorpus);
InitialCorpus.clear(); // Don't need this memory any more.
F->Loop();
if (Flags.verbosity)
Printf("Done %d runs in %zd second(s)\n", F->getTotalNumberOfRuns(),
F->secondsSinceProcessStartUp());
F->PrintFinalStats();
exit(0); // Don't let F destroy itself.
}
// Storage for global ExternalFunctions object.
ExternalFunctions *EF = nullptr;
} // namespace fuzzer
+50
View File
@@ -0,0 +1,50 @@
//===- FuzzerExtFunctions.def - External functions --------------*- C++ -* ===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// This defines the external function pointers that
// ``fuzzer::ExternalFunctions`` should contain and try to initialize. The
// EXT_FUNC macro must be defined at the point of inclusion. The signature of
// the macro is:
//
// EXT_FUNC(<name>, <return_type>, <function_signature>, <warn_if_missing>)
//===----------------------------------------------------------------------===//
// Optional user functions
EXT_FUNC(LLVMFuzzerInitialize, int, (int *argc, char ***argv), false);
EXT_FUNC(LLVMFuzzerCustomMutator, size_t,
(uint8_t * Data, size_t Size, size_t MaxSize, unsigned int Seed),
false);
EXT_FUNC(LLVMFuzzerCustomCrossOver, size_t,
(const uint8_t * Data1, size_t Size1,
const uint8_t * Data2, size_t Size2,
uint8_t * Out, size_t MaxOutSize, unsigned int Seed),
false);
// Sanitizer functions
EXT_FUNC(__lsan_enable, void, (), false);
EXT_FUNC(__lsan_disable, void, (), false);
EXT_FUNC(__lsan_do_recoverable_leak_check, int, (), false);
EXT_FUNC(__sanitizer_get_number_of_counters, size_t, (), false);
EXT_FUNC(__sanitizer_install_malloc_and_free_hooks, int,
(void (*malloc_hook)(const volatile void *, size_t),
void (*free_hook)(const volatile void *)),
false);
EXT_FUNC(__sanitizer_get_total_unique_caller_callee_pairs, size_t, (), false);
EXT_FUNC(__sanitizer_get_total_unique_coverage, size_t, (), true);
EXT_FUNC(__sanitizer_print_memory_profile, int, (size_t), false);
EXT_FUNC(__sanitizer_print_stack_trace, void, (), true);
EXT_FUNC(__sanitizer_symbolize_pc, void,
(void *, const char *fmt, char *out_buf, size_t out_buf_size), false);
EXT_FUNC(__sanitizer_get_module_and_offset_for_pc, int,
(void *pc, char *module_path,
size_t module_path_len,void **pc_offset), false);
EXT_FUNC(__sanitizer_reset_coverage, void, (), true);
EXT_FUNC(__sanitizer_set_death_callback, void, (void (*)(void)), true);
EXT_FUNC(__sanitizer_set_report_fd, void, (void*), false);
EXT_FUNC(__sanitizer_update_counter_bitset_and_clear_counters, uintptr_t,
(uint8_t*), false);
+35
View File
@@ -0,0 +1,35 @@
//===- FuzzerExtFunctions.h - Interface to external functions ---*- C++ -* ===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// Defines an interface to (possibly optional) functions.
//===----------------------------------------------------------------------===//
#ifndef LLVM_FUZZER_EXT_FUNCTIONS_H
#define LLVM_FUZZER_EXT_FUNCTIONS_H
#include <stddef.h>
#include <stdint.h>
namespace fuzzer {
struct ExternalFunctions {
// Initialize function pointers. Functions that are not available will be set
// to nullptr. Do not call this constructor before ``main()`` has been
// entered.
ExternalFunctions();
#define EXT_FUNC(NAME, RETURN_TYPE, FUNC_SIG, WARN) \
RETURN_TYPE(*NAME) FUNC_SIG = nullptr
#include "FuzzerExtFunctions.def"
#undef EXT_FUNC
};
} // namespace fuzzer
#endif
+52
View File
@@ -0,0 +1,52 @@
//===- FuzzerExtFunctionsDlsym.cpp - Interface to external functions ------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// Implementation for operating systems that support dlsym(). We only use it on
// Apple platforms for now. We don't use this approach on Linux because it
// requires that clients of LibFuzzer pass ``--export-dynamic`` to the linker.
// That is a complication we don't wish to expose to clients right now.
//===----------------------------------------------------------------------===//
#include "FuzzerDefs.h"
#if LIBFUZZER_APPLE
#include "FuzzerExtFunctions.h"
#include "FuzzerIO.h"
#include <dlfcn.h>
using namespace fuzzer;
template <typename T>
static T GetFnPtr(const char *FnName, bool WarnIfMissing) {
dlerror(); // Clear any previous errors.
void *Fn = dlsym(RTLD_DEFAULT, FnName);
if (Fn == nullptr) {
if (WarnIfMissing) {
const char *ErrorMsg = dlerror();
Printf("WARNING: Failed to find function \"%s\".", FnName);
if (ErrorMsg)
Printf(" Reason %s.", ErrorMsg);
Printf("\n");
}
}
return reinterpret_cast<T>(Fn);
}
namespace fuzzer {
ExternalFunctions::ExternalFunctions() {
#define EXT_FUNC(NAME, RETURN_TYPE, FUNC_SIG, WARN) \
this->NAME = GetFnPtr<decltype(ExternalFunctions::NAME)>(#NAME, WARN)
#include "FuzzerExtFunctions.def"
#undef EXT_FUNC
}
} // namespace fuzzer
#endif // LIBFUZZER_APPLE
+53
View File
@@ -0,0 +1,53 @@
//===- FuzzerExtFunctionsWeak.cpp - Interface to external functions -------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// Implementation for Linux. This relies on the linker's support for weak
// symbols. We don't use this approach on Apple platforms because it requires
// clients of LibFuzzer to pass ``-U _<symbol_name>`` to the linker to allow
// weak symbols to be undefined. That is a complication we don't want to expose
// to clients right now.
//===----------------------------------------------------------------------===//
#include "FuzzerDefs.h"
#if LIBFUZZER_LINUX
#include "FuzzerExtFunctions.h"
#include "FuzzerIO.h"
extern "C" {
// Declare these symbols as weak to allow them to be optionally defined.
#define EXT_FUNC(NAME, RETURN_TYPE, FUNC_SIG, WARN) \
__attribute__((weak)) RETURN_TYPE NAME FUNC_SIG
#include "FuzzerExtFunctions.def"
#undef EXT_FUNC
}
using namespace fuzzer;
static void CheckFnPtr(void *FnPtr, const char *FnName, bool WarnIfMissing) {
if (FnPtr == nullptr && WarnIfMissing) {
Printf("WARNING: Failed to find function \"%s\".\n", FnName);
}
}
namespace fuzzer {
ExternalFunctions::ExternalFunctions() {
#define EXT_FUNC(NAME, RETURN_TYPE, FUNC_SIG, WARN) \
this->NAME = ::NAME; \
CheckFnPtr((void *)::NAME, #NAME, WARN);
#include "FuzzerExtFunctions.def"
#undef EXT_FUNC
}
} // namespace fuzzer
#endif // LIBFUZZER_LINUX
+56
View File
@@ -0,0 +1,56 @@
//===- FuzzerExtFunctionsWeakAlias.cpp - Interface to external functions --===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// Implementation using weak aliases. Works for Windows.
//===----------------------------------------------------------------------===//
#include "FuzzerDefs.h"
#if LIBFUZZER_WINDOWS
#include "FuzzerExtFunctions.h"
#include "FuzzerIO.h"
using namespace fuzzer;
extern "C" {
// Declare these symbols as weak to allow them to be optionally defined.
#define EXT_FUNC(NAME, RETURN_TYPE, FUNC_SIG, WARN) \
RETURN_TYPE NAME##Def FUNC_SIG { \
Printf("ERROR: Function \"%s\" not defined.\n", #NAME); \
exit(1); \
} \
RETURN_TYPE NAME FUNC_SIG __attribute__((weak, alias(#NAME "Def")));
#include "FuzzerExtFunctions.def"
#undef EXT_FUNC
}
template <typename T>
static T *GetFnPtr(T *Fun, T *FunDef, const char *FnName, bool WarnIfMissing) {
if (Fun == FunDef) {
if (WarnIfMissing)
Printf("WARNING: Failed to find function \"%s\".\n", FnName);
return nullptr;
}
return Fun;
}
namespace fuzzer {
ExternalFunctions::ExternalFunctions() {
#define EXT_FUNC(NAME, RETURN_TYPE, FUNC_SIG, WARN) \
this->NAME = GetFnPtr<decltype(::NAME)>(::NAME, ::NAME##Def, #NAME, WARN);
#include "FuzzerExtFunctions.def"
#undef EXT_FUNC
}
} // namespace fuzzer
#endif // LIBFUZZER_WINDOWS
+115
View File
@@ -0,0 +1,115 @@
//===- FuzzerFlags.def - Run-time flags -------------------------*- C++ -* ===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// Flags. FUZZER_FLAG_INT/FUZZER_FLAG_STRING macros should be defined at the
// point of inclusion. We are not using any flag parsing library for better
// portability and independence.
//===----------------------------------------------------------------------===//
FUZZER_FLAG_INT(verbosity, 1, "Verbosity level.")
FUZZER_FLAG_UNSIGNED(seed, 0, "Random seed. If 0, seed is generated.")
FUZZER_FLAG_INT(runs, -1,
"Number of individual test runs (-1 for infinite runs).")
FUZZER_FLAG_INT(max_len, 0, "Maximum length of the test input. "
"If 0, libFuzzer tries to guess a good value based on the corpus "
"and reports it. ")
FUZZER_FLAG_INT(cross_over, 1, "If 1, cross over inputs.")
FUZZER_FLAG_INT(mutate_depth, 5,
"Apply this number of consecutive mutations to each input.")
FUZZER_FLAG_INT(shuffle, 1, "Shuffle inputs at startup")
FUZZER_FLAG_INT(prefer_small, 1,
"If 1, always prefer smaller inputs during the corpus shuffle.")
FUZZER_FLAG_INT(
timeout, 1200,
"Timeout in seconds (if positive). "
"If one unit runs more than this number of seconds the process will abort.")
FUZZER_FLAG_INT(error_exitcode, 77, "When libFuzzer itself reports a bug "
"this exit code will be used.")
FUZZER_FLAG_INT(timeout_exitcode, 77, "When libFuzzer reports a timeout "
"this exit code will be used.")
FUZZER_FLAG_INT(max_total_time, 0, "If positive, indicates the maximal total "
"time in seconds to run the fuzzer.")
FUZZER_FLAG_INT(help, 0, "Print help.")
FUZZER_FLAG_INT(merge, 0, "If 1, the 2-nd, 3-rd, etc corpora will be "
"merged into the 1-st corpus. Only interesting units will be taken. "
"This flag can be used to minimize a corpus.")
FUZZER_FLAG_STRING(merge_control_file, "internal flag")
FUZZER_FLAG_INT(minimize_crash, 0, "If 1, minimizes the provided"
" crash input. Use with -runs=N or -max_total_time=N to limit "
"the number attempts")
FUZZER_FLAG_INT(minimize_crash_internal_step, 0, "internal flag")
FUZZER_FLAG_INT(use_counters, 1, "Use coverage counters")
FUZZER_FLAG_INT(use_indir_calls, 1, "Use indirect caller-callee counters")
FUZZER_FLAG_INT(use_memcmp, 1,
"Use hints from intercepting memcmp, strcmp, etc")
FUZZER_FLAG_INT(use_memmem, 1,
"Use hints from intercepting memmem, strstr, etc")
FUZZER_FLAG_INT(use_value_profile, 0,
"Experimental. Use value profile to guide fuzzing.")
FUZZER_FLAG_INT(use_cmp, 1, "Use CMP traces to guide mutations")
FUZZER_FLAG_INT(shrink, 0, "Experimental. Try to shrink corpus elements.")
FUZZER_FLAG_UNSIGNED(jobs, 0, "Number of jobs to run. If jobs >= 1 we spawn"
" this number of jobs in separate worker processes"
" with stdout/stderr redirected to fuzz-JOB.log.")
FUZZER_FLAG_UNSIGNED(workers, 0,
"Number of simultaneous worker processes to run the jobs."
" If zero, \"min(jobs,NumberOfCpuCores()/2)\" is used.")
FUZZER_FLAG_INT(reload, 1,
"Reload the main corpus every <N> seconds to get new units"
" discovered by other processes. If 0, disabled")
FUZZER_FLAG_INT(report_slow_units, 10,
"Report slowest units if they run for more than this number of seconds.")
FUZZER_FLAG_INT(only_ascii, 0,
"If 1, generate only ASCII (isprint+isspace) inputs.")
FUZZER_FLAG_STRING(dict, "Experimental. Use the dictionary file.")
FUZZER_FLAG_STRING(artifact_prefix, "Write fuzzing artifacts (crash, "
"timeout, or slow inputs) as "
"$(artifact_prefix)file")
FUZZER_FLAG_STRING(exact_artifact_path,
"Write the single artifact on failure (crash, timeout) "
"as $(exact_artifact_path). This overrides -artifact_prefix "
"and will not use checksum in the file name. Do not "
"use the same path for several parallel processes.")
FUZZER_FLAG_INT(output_csv, 0, "Enable pulse output in CSV format.")
FUZZER_FLAG_INT(print_pcs, 0, "If 1, print out newly covered PCs.")
FUZZER_FLAG_INT(print_final_stats, 0, "If 1, print statistics at exit.")
FUZZER_FLAG_INT(print_corpus_stats, 0,
"If 1, print statistics on corpus elements at exit.")
FUZZER_FLAG_INT(print_coverage, 0, "If 1, print coverage information at exit."
" Experimental, only with trace-pc-guard")
FUZZER_FLAG_INT(dump_coverage, 0, "If 1, dump coverage information at exit."
" Experimental, only with trace-pc-guard")
FUZZER_FLAG_INT(handle_segv, 1, "If 1, try to intercept SIGSEGV.")
FUZZER_FLAG_INT(handle_bus, 1, "If 1, try to intercept SIGSEGV.")
FUZZER_FLAG_INT(handle_abrt, 1, "If 1, try to intercept SIGABRT.")
FUZZER_FLAG_INT(handle_ill, 1, "If 1, try to intercept SIGILL.")
FUZZER_FLAG_INT(handle_fpe, 1, "If 1, try to intercept SIGFPE.")
FUZZER_FLAG_INT(handle_int, 1, "If 1, try to intercept SIGINT.")
FUZZER_FLAG_INT(handle_term, 1, "If 1, try to intercept SIGTERM.")
FUZZER_FLAG_INT(close_fd_mask, 0, "If 1, close stdout at startup; "
"if 2, close stderr; if 3, close both. "
"Be careful, this will also close e.g. asan's stderr/stdout.")
FUZZER_FLAG_INT(detect_leaks, 1, "If 1, and if LeakSanitizer is enabled "
"try to detect memory leaks during fuzzing (i.e. not only at shut down).")
FUZZER_FLAG_INT(trace_malloc, 0, "If >= 1 will print all mallocs/frees. "
"If >= 2 will also print stack traces.")
FUZZER_FLAG_INT(rss_limit_mb, 2048, "If non-zero, the fuzzer will exit upon"
"reaching this limit of RSS memory usage.")
FUZZER_FLAG_STRING(exit_on_src_pos, "Exit if a newly found PC originates"
" from the given source location. Example: -exit_on_src_pos=foo.cc:123. "
"Used primarily for testing libFuzzer itself.")
FUZZER_FLAG_STRING(exit_on_item, "Exit if an item with a given sha1 sum"
" was added to the corpus. "
"Used primarily for testing libFuzzer itself.")
FUZZER_DEPRECATED_FLAG(exit_on_first)
FUZZER_DEPRECATED_FLAG(save_minimized_corpus)
FUZZER_DEPRECATED_FLAG(sync_command)
FUZZER_DEPRECATED_FLAG(sync_timeout)
FUZZER_DEPRECATED_FLAG(test_single_input)
FUZZER_DEPRECATED_FLAG(drill)
FUZZER_DEPRECATED_FLAG(truncate_units)
+117
View File
@@ -0,0 +1,117 @@
//===- FuzzerIO.cpp - IO utils. -------------------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// IO functions.
//===----------------------------------------------------------------------===//
#include "FuzzerIO.h"
#include "FuzzerDefs.h"
#include "FuzzerExtFunctions.h"
#include <algorithm>
#include <cstdarg>
#include <fstream>
#include <iterator>
#include <sys/stat.h>
#include <sys/types.h>
namespace fuzzer {
static FILE *OutputFile = stderr;
long GetEpoch(const std::string &Path) {
struct stat St;
if (stat(Path.c_str(), &St))
return 0; // Can't stat, be conservative.
return St.st_mtime;
}
Unit FileToVector(const std::string &Path, size_t MaxSize, bool ExitOnError) {
std::ifstream T(Path);
if (ExitOnError && !T) {
Printf("No such directory: %s; exiting\n", Path.c_str());
exit(1);
}
T.seekg(0, T.end);
size_t FileLen = T.tellg();
if (MaxSize)
FileLen = std::min(FileLen, MaxSize);
T.seekg(0, T.beg);
Unit Res(FileLen);
T.read(reinterpret_cast<char *>(Res.data()), FileLen);
return Res;
}
std::string FileToString(const std::string &Path) {
std::ifstream T(Path);
return std::string((std::istreambuf_iterator<char>(T)),
std::istreambuf_iterator<char>());
}
void CopyFileToErr(const std::string &Path) {
Printf("%s", FileToString(Path).c_str());
}
void WriteToFile(const Unit &U, const std::string &Path) {
// Use raw C interface because this function may be called from a sig handler.
FILE *Out = fopen(Path.c_str(), "w");
if (!Out) return;
fwrite(U.data(), sizeof(U[0]), U.size(), Out);
fclose(Out);
}
void ReadDirToVectorOfUnits(const char *Path, std::vector<Unit> *V,
long *Epoch, size_t MaxSize, bool ExitOnError) {
long E = Epoch ? *Epoch : 0;
std::vector<std::string> Files;
ListFilesInDirRecursive(Path, Epoch, &Files, /*TopDir*/true);
size_t NumLoaded = 0;
for (size_t i = 0; i < Files.size(); i++) {
auto &X = Files[i];
if (Epoch && GetEpoch(X) < E) continue;
NumLoaded++;
if ((NumLoaded & (NumLoaded - 1)) == 0 && NumLoaded >= 1024)
Printf("Loaded %zd/%zd files from %s\n", NumLoaded, Files.size(), Path);
auto S = FileToVector(X, MaxSize, ExitOnError);
if (!S.empty())
V->push_back(S);
}
}
std::string DirPlusFile(const std::string &DirPath,
const std::string &FileName) {
return DirPath + GetSeparator() + FileName;
}
void DupAndCloseStderr() {
int OutputFd = DuplicateFile(2);
if (OutputFd > 0) {
FILE *NewOutputFile = OpenFile(OutputFd, "w");
if (NewOutputFile) {
OutputFile = NewOutputFile;
if (EF->__sanitizer_set_report_fd)
EF->__sanitizer_set_report_fd(reinterpret_cast<void *>(OutputFd));
CloseFile(2);
}
}
}
void CloseStdout() {
CloseFile(1);
}
void Printf(const char *Fmt, ...) {
va_list ap;
va_start(ap, Fmt);
vfprintf(OutputFile, Fmt, ap);
va_end(ap);
fflush(OutputFile);
}
} // namespace fuzzer
+64
View File
@@ -0,0 +1,64 @@
//===- FuzzerIO.h - Internal header for IO utils ----------------*- C++ -* ===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// IO interface.
//===----------------------------------------------------------------------===//
#ifndef LLVM_FUZZER_IO_H
#define LLVM_FUZZER_IO_H
#include "FuzzerDefs.h"
namespace fuzzer {
long GetEpoch(const std::string &Path);
Unit FileToVector(const std::string &Path, size_t MaxSize = 0,
bool ExitOnError = true);
std::string FileToString(const std::string &Path);
void CopyFileToErr(const std::string &Path);
void WriteToFile(const Unit &U, const std::string &Path);
void ReadDirToVectorOfUnits(const char *Path, std::vector<Unit> *V,
long *Epoch, size_t MaxSize, bool ExitOnError);
// Returns "Dir/FileName" or equivalent for the current OS.
std::string DirPlusFile(const std::string &DirPath,
const std::string &FileName);
// Returns the name of the dir, similar to the 'dirname' utility.
std::string DirName(const std::string &FileName);
void DupAndCloseStderr();
void CloseStdout();
void Printf(const char *Fmt, ...);
// Platform specific functions:
bool IsFile(const std::string &Path);
void ListFilesInDirRecursive(const std::string &Dir, long *Epoch,
std::vector<std::string> *V, bool TopDir);
char GetSeparator();
FILE* OpenFile(int Fd, const char *Mode);
int CloseFile(int Fd);
int DuplicateFile(int Fd);
void RemoveFile(const std::string &Path);
} // namespace fuzzer
#endif // LLVM_FUZZER_IO_H
+88
View File
@@ -0,0 +1,88 @@
//===- FuzzerIOPosix.cpp - IO utils for Posix. ----------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// IO functions implementation using Posix API.
//===----------------------------------------------------------------------===//
#include "FuzzerDefs.h"
#if LIBFUZZER_POSIX
#include "FuzzerExtFunctions.h"
#include "FuzzerIO.h"
#include <cstdarg>
#include <cstdio>
#include <dirent.h>
#include <fstream>
#include <iterator>
#include <libgen.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
namespace fuzzer {
bool IsFile(const std::string &Path) {
struct stat St;
if (stat(Path.c_str(), &St))
return false;
return S_ISREG(St.st_mode);
}
void ListFilesInDirRecursive(const std::string &Dir, long *Epoch,
std::vector<std::string> *V, bool TopDir) {
auto E = GetEpoch(Dir);
if (Epoch)
if (E && *Epoch >= E) return;
DIR *D = opendir(Dir.c_str());
if (!D) {
Printf("No such directory: %s; exiting\n", Dir.c_str());
exit(1);
}
while (auto E = readdir(D)) {
std::string Path = DirPlusFile(Dir, E->d_name);
if (E->d_type == DT_REG || E->d_type == DT_LNK)
V->push_back(Path);
else if (E->d_type == DT_DIR && *E->d_name != '.')
ListFilesInDirRecursive(Path, Epoch, V, false);
}
closedir(D);
if (Epoch && TopDir)
*Epoch = E;
}
char GetSeparator() {
return '/';
}
FILE* OpenFile(int Fd, const char* Mode) {
return fdopen(Fd, Mode);
}
int CloseFile(int fd) {
return close(fd);
}
int DuplicateFile(int Fd) {
return dup(Fd);
}
void RemoveFile(const std::string &Path) {
unlink(Path.c_str());
}
std::string DirName(const std::string &FileName) {
char *Tmp = new char[FileName.size() + 1];
memcpy(Tmp, FileName.c_str(), FileName.size() + 1);
std::string Res = dirname(Tmp);
delete [] Tmp;
return Res;
}
} // namespace fuzzer
#endif // LIBFUZZER_POSIX
+282
View File
@@ -0,0 +1,282 @@
//===- FuzzerIOWindows.cpp - IO utils for Windows. ------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// IO functions implementation for Windows.
//===----------------------------------------------------------------------===//
#include "FuzzerDefs.h"
#if LIBFUZZER_WINDOWS
#include "FuzzerExtFunctions.h"
#include "FuzzerIO.h"
#include <cstdarg>
#include <cstdio>
#include <fstream>
#include <io.h>
#include <iterator>
#include <sys/stat.h>
#include <sys/types.h>
#include <windows.h>
namespace fuzzer {
static bool IsFile(const std::string &Path, const DWORD &FileAttributes) {
if (FileAttributes & FILE_ATTRIBUTE_NORMAL)
return true;
if (FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
return false;
HANDLE FileHandle(
CreateFileA(Path.c_str(), 0, FILE_SHARE_READ, NULL, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS, 0));
if (FileHandle == INVALID_HANDLE_VALUE) {
Printf("CreateFileA() failed for \"%s\" (Error code: %lu).\n", Path.c_str(),
GetLastError());
return false;
}
DWORD FileType = GetFileType(FileHandle);
if (FileType == FILE_TYPE_UNKNOWN) {
Printf("GetFileType() failed for \"%s\" (Error code: %lu).\n", Path.c_str(),
GetLastError());
CloseHandle(FileHandle);
return false;
}
if (FileType != FILE_TYPE_DISK) {
CloseHandle(FileHandle);
return false;
}
CloseHandle(FileHandle);
return true;
}
bool IsFile(const std::string &Path) {
DWORD Att = GetFileAttributesA(Path.c_str());
if (Att == INVALID_FILE_ATTRIBUTES) {
Printf("GetFileAttributesA() failed for \"%s\" (Error code: %lu).\n",
Path.c_str(), GetLastError());
return false;
}
return IsFile(Path, Att);
}
void ListFilesInDirRecursive(const std::string &Dir, long *Epoch,
std::vector<std::string> *V, bool TopDir) {
auto E = GetEpoch(Dir);
if (Epoch)
if (E && *Epoch >= E) return;
std::string Path(Dir);
assert(!Path.empty());
if (Path.back() != '\\')
Path.push_back('\\');
Path.push_back('*');
// Get the first directory entry.
WIN32_FIND_DATAA FindInfo;
HANDLE FindHandle(FindFirstFileA(Path.c_str(), &FindInfo));
if (FindHandle == INVALID_HANDLE_VALUE)
{
Printf("No file found in: %s.\n", Dir.c_str());
return;
}
do {
std::string FileName = DirPlusFile(Dir, FindInfo.cFileName);
if (FindInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
size_t FilenameLen = strlen(FindInfo.cFileName);
if ((FilenameLen == 1 && FindInfo.cFileName[0] == '.') ||
(FilenameLen == 2 && FindInfo.cFileName[0] == '.' &&
FindInfo.cFileName[1] == '.'))
continue;
ListFilesInDirRecursive(FileName, Epoch, V, false);
}
else if (IsFile(FileName, FindInfo.dwFileAttributes))
V->push_back(FileName);
} while (FindNextFileA(FindHandle, &FindInfo));
DWORD LastError = GetLastError();
if (LastError != ERROR_NO_MORE_FILES)
Printf("FindNextFileA failed (Error code: %lu).\n", LastError);
FindClose(FindHandle);
if (Epoch && TopDir)
*Epoch = E;
}
char GetSeparator() {
return '\\';
}
FILE* OpenFile(int Fd, const char* Mode) {
return _fdopen(Fd, Mode);
}
int CloseFile(int Fd) {
return _close(Fd);
}
int DuplicateFile(int Fd) {
return _dup(Fd);
}
void RemoveFile(const std::string &Path) {
_unlink(Path.c_str());
}
static bool IsSeparator(char C) {
return C == '\\' || C == '/';
}
// Parse disk designators, like "C:\". If Relative == true, also accepts: "C:".
// Returns number of characters considered if successful.
static size_t ParseDrive(const std::string &FileName, const size_t Offset,
bool Relative = true) {
if (Offset + 1 >= FileName.size() || FileName[Offset + 1] != ':')
return 0;
if (Offset + 2 >= FileName.size() || !IsSeparator(FileName[Offset + 2])) {
if (!Relative) // Accept relative path?
return 0;
else
return 2;
}
return 3;
}
// Parse a file name, like: SomeFile.txt
// Returns number of characters considered if successful.
static size_t ParseFileName(const std::string &FileName, const size_t Offset) {
size_t Pos = Offset;
const size_t End = FileName.size();
for(; Pos < End && !IsSeparator(FileName[Pos]); ++Pos)
;
return Pos - Offset;
}
// Parse a directory ending in separator, like: SomeDir\
// Returns number of characters considered if successful.
static size_t ParseDir(const std::string &FileName, const size_t Offset) {
size_t Pos = Offset;
const size_t End = FileName.size();
if (Pos >= End || IsSeparator(FileName[Pos]))
return 0;
for(; Pos < End && !IsSeparator(FileName[Pos]); ++Pos)
;
if (Pos >= End)
return 0;
++Pos; // Include separator.
return Pos - Offset;
}
// Parse a servername and share, like: SomeServer\SomeShare\
// Returns number of characters considered if successful.
static size_t ParseServerAndShare(const std::string &FileName,
const size_t Offset) {
size_t Pos = Offset, Res;
if (!(Res = ParseDir(FileName, Pos)))
return 0;
Pos += Res;
if (!(Res = ParseDir(FileName, Pos)))
return 0;
Pos += Res;
return Pos - Offset;
}
// Parse the given Ref string from the position Offset, to exactly match the given
// string Patt.
// Returns number of characters considered if successful.
static size_t ParseCustomString(const std::string &Ref, size_t Offset,
const char *Patt) {
size_t Len = strlen(Patt);
if (Offset + Len > Ref.size())
return 0;
return Ref.compare(Offset, Len, Patt) == 0 ? Len : 0;
}
// Parse a location, like:
// \\?\UNC\Server\Share\ \\?\C:\ \\Server\Share\ \ C:\ C:
// Returns number of characters considered if successful.
static size_t ParseLocation(const std::string &FileName) {
size_t Pos = 0, Res;
if ((Res = ParseCustomString(FileName, Pos, R"(\\?\)"))) {
Pos += Res;
if ((Res = ParseCustomString(FileName, Pos, R"(UNC\)"))) {
Pos += Res;
if ((Res = ParseServerAndShare(FileName, Pos)))
return Pos + Res;
return 0;
}
if ((Res = ParseDrive(FileName, Pos, false)))
return Pos + Res;
return 0;
}
if (Pos < FileName.size() && IsSeparator(FileName[Pos])) {
++Pos;
if (Pos < FileName.size() && IsSeparator(FileName[Pos])) {
++Pos;
if ((Res = ParseServerAndShare(FileName, Pos)))
return Pos + Res;
return 0;
}
return Pos;
}
if ((Res = ParseDrive(FileName, Pos)))
return Pos + Res;
return Pos;
}
std::string DirName(const std::string &FileName) {
size_t LocationLen = ParseLocation(FileName);
size_t DirLen = 0, Res;
while ((Res = ParseDir(FileName, LocationLen + DirLen)))
DirLen += Res;
size_t FileLen = ParseFileName(FileName, LocationLen + DirLen);
if (LocationLen + DirLen + FileLen != FileName.size()) {
Printf("DirName() failed for \"%s\", invalid path.\n", FileName.c_str());
exit(1);
}
if (DirLen) {
--DirLen; // Remove trailing separator.
if (!FileLen) { // Path ended in separator.
assert(DirLen);
// Remove file name from Dir.
while (DirLen && !IsSeparator(FileName[LocationLen + DirLen - 1]))
--DirLen;
if (DirLen) // Remove trailing separator.
--DirLen;
}
}
if (!LocationLen) { // Relative path.
if (!DirLen)
return ".";
return std::string(".\\").append(FileName, 0, DirLen);
}
return FileName.substr(0, LocationLen + DirLen);
}
} // namespace fuzzer
#endif // LIBFUZZER_WINDOWS
+67
View File
@@ -0,0 +1,67 @@
//===- FuzzerInterface.h - Interface header for the Fuzzer ------*- C++ -* ===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// Define the interface between libFuzzer and the library being tested.
//===----------------------------------------------------------------------===//
// NOTE: the libFuzzer interface is thin and in the majority of cases
// you should not include this file into your target. In 95% of cases
// all you need is to define the following function in your file:
// extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size);
// WARNING: keep the interface in C.
#ifndef LLVM_FUZZER_INTERFACE_H
#define LLVM_FUZZER_INTERFACE_H
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
// Mandatory user-provided target function.
// Executes the code under test with [Data, Data+Size) as the input.
// libFuzzer will invoke this function *many* times with different inputs.
// Must return 0.
int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size);
// Optional user-provided initialization function.
// If provided, this function will be called by libFuzzer once at startup.
// It may read and modify argc/argv.
// Must return 0.
int LLVMFuzzerInitialize(int *argc, char ***argv);
// Optional user-provided custom mutator.
// Mutates raw data in [Data, Data+Size) inplace.
// Returns the new size, which is not greater than MaxSize.
// Given the same Seed produces the same mutation.
size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize,
unsigned int Seed);
// Optional user-provided custom cross-over function.
// Combines pieces of Data1 & Data2 together into Out.
// Returns the new size, which is not greater than MaxOutSize.
// Should produce the same mutation given the same Seed.
size_t LLVMFuzzerCustomCrossOver(const uint8_t *Data1, size_t Size1,
const uint8_t *Data2, size_t Size2,
uint8_t *Out, size_t MaxOutSize,
unsigned int Seed);
// Experimental, may go away in future.
// libFuzzer-provided function to be used inside LLVMFuzzerTestOneInput.
// Mutates raw data in [Data, Data+Size) inplace.
// Returns the new size, which is not greater than MaxSize.
size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif // LLVM_FUZZER_INTERFACE_H
+182
View File
@@ -0,0 +1,182 @@
//===- FuzzerInternal.h - Internal header for the Fuzzer --------*- C++ -* ===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// Define the main class fuzzer::Fuzzer and most functions.
//===----------------------------------------------------------------------===//
#ifndef LLVM_FUZZER_INTERNAL_H
#define LLVM_FUZZER_INTERNAL_H
#include "FuzzerDefs.h"
#include "FuzzerExtFunctions.h"
#include "FuzzerInterface.h"
#include "FuzzerOptions.h"
#include "FuzzerSHA1.h"
#include "FuzzerValueBitMap.h"
#include <algorithm>
#include <atomic>
#include <chrono>
#include <climits>
#include <cstdlib>
#include <string.h>
namespace fuzzer {
using namespace std::chrono;
class Fuzzer {
public:
// Aggregates all available coverage measurements.
struct Coverage {
Coverage() { Reset(); }
void Reset() {
BlockCoverage = 0;
CallerCalleeCoverage = 0;
CounterBitmapBits = 0;
CounterBitmap.clear();
VPMap.Reset();
}
size_t BlockCoverage;
size_t CallerCalleeCoverage;
// Precalculated number of bits in CounterBitmap.
size_t CounterBitmapBits;
std::vector<uint8_t> CounterBitmap;
ValueBitMap VPMap;
};
Fuzzer(UserCallback CB, InputCorpus &Corpus, MutationDispatcher &MD,
FuzzingOptions Options);
~Fuzzer();
void Loop();
void MinimizeCrashLoop(const Unit &U);
void ShuffleAndMinimize(UnitVector *V);
void InitializeTraceState();
void RereadOutputCorpus(size_t MaxSize);
size_t secondsSinceProcessStartUp() {
return duration_cast<seconds>(system_clock::now() - ProcessStartTime)
.count();
}
bool TimedOut() {
return Options.MaxTotalTimeSec > 0 &&
secondsSinceProcessStartUp() >
static_cast<size_t>(Options.MaxTotalTimeSec);
}
size_t execPerSec() {
size_t Seconds = secondsSinceProcessStartUp();
return Seconds ? TotalNumberOfRuns / Seconds : 0;
}
size_t getTotalNumberOfRuns() { return TotalNumberOfRuns; }
static void StaticAlarmCallback();
static void StaticCrashSignalCallback();
static void StaticInterruptCallback();
void ExecuteCallback(const uint8_t *Data, size_t Size);
size_t RunOne(const uint8_t *Data, size_t Size);
// Merge Corpora[1:] into Corpora[0].
void Merge(const std::vector<std::string> &Corpora);
void CrashResistantMerge(const std::vector<std::string> &Args,
const std::vector<std::string> &Corpora);
void CrashResistantMergeInternalStep(const std::string &ControlFilePath);
// Returns a subset of 'Extra' that adds coverage to 'Initial'.
UnitVector FindExtraUnits(const UnitVector &Initial, const UnitVector &Extra);
MutationDispatcher &GetMD() { return MD; }
void PrintFinalStats();
void SetMaxInputLen(size_t MaxInputLen);
void SetMaxMutationLen(size_t MaxMutationLen);
void RssLimitCallback();
// Public for tests.
void ResetCoverage();
bool InFuzzingThread() const { return IsMyThread; }
size_t GetCurrentUnitInFuzzingThead(const uint8_t **Data) const;
void TryDetectingAMemoryLeak(const uint8_t *Data, size_t Size,
bool DuringInitialCorpusExecution);
void HandleMalloc(size_t Size);
private:
void AlarmCallback();
void CrashCallback();
void InterruptCallback();
void MutateAndTestOne();
void ReportNewCoverage(InputInfo *II, const Unit &U);
size_t RunOne(const Unit &U) { return RunOne(U.data(), U.size()); }
void WriteToOutputCorpus(const Unit &U);
void WriteUnitToFileWithPrefix(const Unit &U, const char *Prefix);
void PrintStats(const char *Where, const char *End = "\n", size_t Units = 0);
void PrintStatusForNewUnit(const Unit &U);
void ShuffleCorpus(UnitVector *V);
void AddToCorpus(const Unit &U);
void CheckExitOnSrcPosOrItem();
// Trace-based fuzzing: we run a unit with some kind of tracing
// enabled and record potentially useful mutations. Then
// We apply these mutations one by one to the unit and run it again.
// Start tracing; forget all previously proposed mutations.
void StartTraceRecording();
// Stop tracing.
void StopTraceRecording();
void SetDeathCallback();
static void StaticDeathCallback();
void DumpCurrentUnit(const char *Prefix);
void DeathCallback();
void ResetEdgeCoverage();
void ResetCounters();
void PrepareCounters(Fuzzer::Coverage *C);
bool RecordMaxCoverage(Fuzzer::Coverage *C);
void AllocateCurrentUnitData();
uint8_t *CurrentUnitData = nullptr;
std::atomic<size_t> CurrentUnitSize;
uint8_t BaseSha1[kSHA1NumBytes]; // Checksum of the base unit.
bool RunningCB = false;
size_t TotalNumberOfRuns = 0;
size_t NumberOfNewUnitsAdded = 0;
bool HasMoreMallocsThanFrees = false;
size_t NumberOfLeakDetectionAttempts = 0;
UserCallback CB;
InputCorpus &Corpus;
MutationDispatcher &MD;
FuzzingOptions Options;
system_clock::time_point ProcessStartTime = system_clock::now();
system_clock::time_point UnitStartTime, UnitStopTime;
long TimeOfLongestUnitInSeconds = 0;
long EpochOfLastReadOfOutputCorpus = 0;
// Maximum recorded coverage.
Coverage MaxCoverage;
size_t MaxInputLen = 0;
size_t MaxMutationLen = 0;
// Need to know our own thread.
static thread_local bool IsMyThread;
bool InMergeMode = false;
};
}; // namespace fuzzer
#endif // LLVM_FUZZER_INTERNAL_H
+792
View File
@@ -0,0 +1,792 @@
//===- FuzzerLoop.cpp - Fuzzer's main loop --------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// Fuzzer's main loop.
//===----------------------------------------------------------------------===//
#include "FuzzerCorpus.h"
#include "FuzzerInternal.h"
#include "FuzzerIO.h"
#include "FuzzerMutate.h"
#include "FuzzerRandom.h"
#include "FuzzerTracePC.h"
#include <algorithm>
#include <cstring>
#include <memory>
#include <set>
#if defined(__has_include)
#if __has_include(<sanitizer / coverage_interface.h>)
#include <sanitizer/coverage_interface.h>
#endif
#if __has_include(<sanitizer / lsan_interface.h>)
#include <sanitizer/lsan_interface.h>
#endif
#endif
#define NO_SANITIZE_MEMORY
#if defined(__has_feature)
#if __has_feature(memory_sanitizer)
#undef NO_SANITIZE_MEMORY
#define NO_SANITIZE_MEMORY __attribute__((no_sanitize_memory))
#endif
#endif
namespace fuzzer {
static const size_t kMaxUnitSizeToPrint = 256;
thread_local bool Fuzzer::IsMyThread;
static void MissingExternalApiFunction(const char *FnName) {
Printf("ERROR: %s is not defined. Exiting.\n"
"Did you use -fsanitize-coverage=... to build your code?\n",
FnName);
exit(1);
}
#define CHECK_EXTERNAL_FUNCTION(fn) \
do { \
if (!(EF->fn)) \
MissingExternalApiFunction(#fn); \
} while (false)
// Only one Fuzzer per process.
static Fuzzer *F;
void Fuzzer::ResetEdgeCoverage() {
CHECK_EXTERNAL_FUNCTION(__sanitizer_reset_coverage);
EF->__sanitizer_reset_coverage();
}
void Fuzzer::ResetCounters() {
if (Options.UseCounters)
EF->__sanitizer_update_counter_bitset_and_clear_counters(0);
}
void Fuzzer::PrepareCounters(Fuzzer::Coverage *C) {
if (Options.UseCounters) {
size_t NumCounters = EF->__sanitizer_get_number_of_counters();
C->CounterBitmap.resize(NumCounters);
}
}
// Records data to a maximum coverage tracker. Returns true if additional
// coverage was discovered.
bool Fuzzer::RecordMaxCoverage(Fuzzer::Coverage *C) {
bool Res = false;
uint64_t NewBlockCoverage = EF->__sanitizer_get_total_unique_coverage();
if (NewBlockCoverage > C->BlockCoverage) {
Res = true;
C->BlockCoverage = NewBlockCoverage;
}
if (Options.UseIndirCalls &&
EF->__sanitizer_get_total_unique_caller_callee_pairs) {
uint64_t NewCallerCalleeCoverage =
EF->__sanitizer_get_total_unique_caller_callee_pairs();
if (NewCallerCalleeCoverage > C->CallerCalleeCoverage) {
Res = true;
C->CallerCalleeCoverage = NewCallerCalleeCoverage;
}
}
if (Options.UseCounters) {
uint64_t CounterDelta =
EF->__sanitizer_update_counter_bitset_and_clear_counters(
C->CounterBitmap.data());
if (CounterDelta > 0) {
Res = true;
C->CounterBitmapBits += CounterDelta;
}
}
return Res;
}
// Leak detection is expensive, so we first check if there were more mallocs
// than frees (using the sanitizer malloc hooks) and only then try to call lsan.
struct MallocFreeTracer {
void Start(int TraceLevel) {
this->TraceLevel = TraceLevel;
if (TraceLevel)
Printf("MallocFreeTracer: START\n");
Mallocs = 0;
Frees = 0;
}
// Returns true if there were more mallocs than frees.
bool Stop() {
if (TraceLevel)
Printf("MallocFreeTracer: STOP %zd %zd (%s)\n", Mallocs.load(),
Frees.load(), Mallocs == Frees ? "same" : "DIFFERENT");
bool Result = Mallocs > Frees;
Mallocs = 0;
Frees = 0;
TraceLevel = 0;
return Result;
}
std::atomic<size_t> Mallocs;
std::atomic<size_t> Frees;
int TraceLevel = 0;
};
static MallocFreeTracer AllocTracer;
ATTRIBUTE_NO_SANITIZE_MEMORY
void MallocHook(const volatile void *ptr, size_t size) {
size_t N = AllocTracer.Mallocs++;
F->HandleMalloc(size);
if (int TraceLevel = AllocTracer.TraceLevel) {
Printf("MALLOC[%zd] %p %zd\n", N, ptr, size);
if (TraceLevel >= 2 && EF)
EF->__sanitizer_print_stack_trace();
}
}
ATTRIBUTE_NO_SANITIZE_MEMORY
void FreeHook(const volatile void *ptr) {
size_t N = AllocTracer.Frees++;
if (int TraceLevel = AllocTracer.TraceLevel) {
Printf("FREE[%zd] %p\n", N, ptr);
if (TraceLevel >= 2 && EF)
EF->__sanitizer_print_stack_trace();
}
}
// Crash on a single malloc that exceeds the rss limit.
void Fuzzer::HandleMalloc(size_t Size) {
if (!Options.RssLimitMb || (Size >> 20) < (size_t)Options.RssLimitMb)
return;
Printf("==%d== ERROR: libFuzzer: out-of-memory (malloc(%zd))\n", GetPid(),
Size);
Printf(" To change the out-of-memory limit use -rss_limit_mb=<N>\n\n");
if (EF->__sanitizer_print_stack_trace)
EF->__sanitizer_print_stack_trace();
DumpCurrentUnit("oom-");
Printf("SUMMARY: libFuzzer: out-of-memory\n");
PrintFinalStats();
_Exit(Options.ErrorExitCode); // Stop right now.
}
Fuzzer::Fuzzer(UserCallback CB, InputCorpus &Corpus, MutationDispatcher &MD,
FuzzingOptions Options)
: CB(CB), Corpus(Corpus), MD(MD), Options(Options) {
SetDeathCallback();
InitializeTraceState();
assert(!F);
F = this;
TPC.ResetMaps();
ResetCoverage();
IsMyThread = true;
if (Options.DetectLeaks && EF->__sanitizer_install_malloc_and_free_hooks)
EF->__sanitizer_install_malloc_and_free_hooks(MallocHook, FreeHook);
TPC.SetUseCounters(Options.UseCounters);
TPC.SetUseValueProfile(Options.UseValueProfile);
TPC.SetPrintNewPCs(Options.PrintNewCovPcs);
if (Options.Verbosity)
TPC.PrintModuleInfo();
if (!Options.OutputCorpus.empty() && Options.ReloadIntervalSec)
EpochOfLastReadOfOutputCorpus = GetEpoch(Options.OutputCorpus);
MaxInputLen = MaxMutationLen = Options.MaxLen;
AllocateCurrentUnitData();
CurrentUnitSize = 0;
memset(BaseSha1, 0, sizeof(BaseSha1));
}
Fuzzer::~Fuzzer() { }
void Fuzzer::AllocateCurrentUnitData() {
if (CurrentUnitData || MaxInputLen == 0) return;
CurrentUnitData = new uint8_t[MaxInputLen];
}
void Fuzzer::SetDeathCallback() {
CHECK_EXTERNAL_FUNCTION(__sanitizer_set_death_callback);
EF->__sanitizer_set_death_callback(StaticDeathCallback);
}
void Fuzzer::StaticDeathCallback() {
assert(F);
F->DeathCallback();
}
static void WarnOnUnsuccessfullMerge(bool DoWarn) {
if (!DoWarn) return;
Printf(
"***\n"
"***\n"
"***\n"
"*** NOTE: merge did not succeed due to a failure on one of the inputs.\n"
"*** You will need to filter out crashes from the corpus, e.g. like this:\n"
"*** for f in WITH_CRASHES/*; do ./fuzzer $f && cp $f NO_CRASHES; done\n"
"*** Future versions may have crash-resistant merge, stay tuned.\n"
"***\n"
"***\n"
"***\n");
}
void Fuzzer::DumpCurrentUnit(const char *Prefix) {
WarnOnUnsuccessfullMerge(InMergeMode);
if (!CurrentUnitData) return; // Happens when running individual inputs.
MD.PrintMutationSequence();
Printf("; base unit: %s\n", Sha1ToString(BaseSha1).c_str());
size_t UnitSize = CurrentUnitSize;
if (UnitSize <= kMaxUnitSizeToPrint) {
PrintHexArray(CurrentUnitData, UnitSize, "\n");
PrintASCII(CurrentUnitData, UnitSize, "\n");
}
WriteUnitToFileWithPrefix({CurrentUnitData, CurrentUnitData + UnitSize},
Prefix);
}
NO_SANITIZE_MEMORY
void Fuzzer::DeathCallback() {
DumpCurrentUnit("crash-");
PrintFinalStats();
}
void Fuzzer::StaticAlarmCallback() {
assert(F);
F->AlarmCallback();
}
void Fuzzer::StaticCrashSignalCallback() {
assert(F);
F->CrashCallback();
}
void Fuzzer::StaticInterruptCallback() {
assert(F);
F->InterruptCallback();
}
void Fuzzer::CrashCallback() {
Printf("==%lu== ERROR: libFuzzer: deadly signal\n", GetPid());
if (EF->__sanitizer_print_stack_trace)
EF->__sanitizer_print_stack_trace();
Printf("NOTE: libFuzzer has rudimentary signal handlers.\n"
" Combine libFuzzer with AddressSanitizer or similar for better "
"crash reports.\n");
Printf("SUMMARY: libFuzzer: deadly signal\n");
DumpCurrentUnit("crash-");
PrintFinalStats();
exit(Options.ErrorExitCode);
}
void Fuzzer::InterruptCallback() {
Printf("==%lu== libFuzzer: run interrupted; exiting\n", GetPid());
PrintFinalStats();
_Exit(0); // Stop right now, don't perform any at-exit actions.
}
NO_SANITIZE_MEMORY
void Fuzzer::AlarmCallback() {
assert(Options.UnitTimeoutSec > 0);
if (!InFuzzingThread()) return;
if (!RunningCB)
return; // We have not started running units yet.
size_t Seconds =
duration_cast<seconds>(system_clock::now() - UnitStartTime).count();
if (Seconds == 0)
return;
if (Options.Verbosity >= 2)
Printf("AlarmCallback %zd\n", Seconds);
if (Seconds >= (size_t)Options.UnitTimeoutSec) {
Printf("ALARM: working on the last Unit for %zd seconds\n", Seconds);
Printf(" and the timeout value is %d (use -timeout=N to change)\n",
Options.UnitTimeoutSec);
DumpCurrentUnit("timeout-");
Printf("==%lu== ERROR: libFuzzer: timeout after %d seconds\n", GetPid(),
Seconds);
if (EF->__sanitizer_print_stack_trace)
EF->__sanitizer_print_stack_trace();
Printf("SUMMARY: libFuzzer: timeout\n");
PrintFinalStats();
_Exit(Options.TimeoutExitCode); // Stop right now.
}
}
void Fuzzer::RssLimitCallback() {
Printf(
"==%lu== ERROR: libFuzzer: out-of-memory (used: %zdMb; limit: %zdMb)\n",
GetPid(), GetPeakRSSMb(), Options.RssLimitMb);
Printf(" To change the out-of-memory limit use -rss_limit_mb=<N>\n\n");
if (EF->__sanitizer_print_memory_profile)
EF->__sanitizer_print_memory_profile(95);
DumpCurrentUnit("oom-");
Printf("SUMMARY: libFuzzer: out-of-memory\n");
PrintFinalStats();
_Exit(Options.ErrorExitCode); // Stop right now.
}
void Fuzzer::PrintStats(const char *Where, const char *End, size_t Units) {
size_t ExecPerSec = execPerSec();
if (Options.OutputCSV) {
static bool csvHeaderPrinted = false;
if (!csvHeaderPrinted) {
csvHeaderPrinted = true;
Printf("runs,block_cov,bits,cc_cov,corpus,execs_per_sec,tbms,reason\n");
}
Printf("%zd,%zd,%zd,%zd,%zd,%zd,%s\n", TotalNumberOfRuns,
MaxCoverage.BlockCoverage, MaxCoverage.CounterBitmapBits,
MaxCoverage.CallerCalleeCoverage, Corpus.size(), ExecPerSec, Where);
}
if (!Options.Verbosity)
return;
Printf("#%zd\t%s", TotalNumberOfRuns, Where);
if (MaxCoverage.BlockCoverage)
Printf(" cov: %zd", MaxCoverage.BlockCoverage);
if (size_t N = MaxCoverage.VPMap.GetNumBitsSinceLastMerge())
Printf(" vp: %zd", N);
if (size_t N = TPC.GetTotalPCCoverage())
Printf(" cov: %zd", N);
if (auto TB = MaxCoverage.CounterBitmapBits)
Printf(" bits: %zd", TB);
if (size_t N = Corpus.NumFeatures())
Printf( " ft: %zd", N);
if (MaxCoverage.CallerCalleeCoverage)
Printf(" indir: %zd", MaxCoverage.CallerCalleeCoverage);
if (!Corpus.empty()) {
Printf(" corp: %zd", Corpus.NumActiveUnits());
if (size_t N = Corpus.SizeInBytes()) {
if (N < (1<<14))
Printf("/%zdb", N);
else if (N < (1 << 24))
Printf("/%zdKb", N >> 10);
else
Printf("/%zdMb", N >> 20);
}
}
if (Units)
Printf(" units: %zd", Units);
Printf(" exec/s: %zd", ExecPerSec);
Printf(" rss: %zdMb", GetPeakRSSMb());
Printf("%s", End);
}
void Fuzzer::PrintFinalStats() {
if (Options.PrintCoverage)
TPC.PrintCoverage();
if (Options.DumpCoverage)
TPC.DumpCoverage();
if (Options.PrintCorpusStats)
Corpus.PrintStats();
if (!Options.PrintFinalStats) return;
size_t ExecPerSec = execPerSec();
Printf("stat::number_of_executed_units: %zd\n", TotalNumberOfRuns);
Printf("stat::average_exec_per_sec: %zd\n", ExecPerSec);
Printf("stat::new_units_added: %zd\n", NumberOfNewUnitsAdded);
Printf("stat::slowest_unit_time_sec: %zd\n", TimeOfLongestUnitInSeconds);
Printf("stat::peak_rss_mb: %zd\n", GetPeakRSSMb());
}
void Fuzzer::SetMaxInputLen(size_t MaxInputLen) {
assert(this->MaxInputLen == 0); // Can only reset MaxInputLen from 0 to non-0.
assert(MaxInputLen);
this->MaxInputLen = MaxInputLen;
this->MaxMutationLen = MaxInputLen;
AllocateCurrentUnitData();
Printf("INFO: -max_len is not provided, using %zd\n", MaxInputLen);
}
void Fuzzer::SetMaxMutationLen(size_t MaxMutationLen) {
assert(MaxMutationLen && MaxMutationLen <= MaxInputLen);
this->MaxMutationLen = MaxMutationLen;
}
void Fuzzer::CheckExitOnSrcPosOrItem() {
if (!Options.ExitOnSrcPos.empty()) {
static auto *PCsSet = new std::set<uintptr_t>;
for (size_t i = 1, N = TPC.GetNumPCs(); i < N; i++) {
uintptr_t PC = TPC.GetPC(i);
if (!PC) continue;
if (!PCsSet->insert(PC).second) continue;
std::string Descr = DescribePC("%L", PC);
if (Descr.find(Options.ExitOnSrcPos) != std::string::npos) {
Printf("INFO: found line matching '%s', exiting.\n",
Options.ExitOnSrcPos.c_str());
_Exit(0);
}
}
}
if (!Options.ExitOnItem.empty()) {
if (Corpus.HasUnit(Options.ExitOnItem)) {
Printf("INFO: found item with checksum '%s', exiting.\n",
Options.ExitOnItem.c_str());
_Exit(0);
}
}
}
void Fuzzer::RereadOutputCorpus(size_t MaxSize) {
if (Options.OutputCorpus.empty() || !Options.ReloadIntervalSec) return;
std::vector<Unit> AdditionalCorpus;
ReadDirToVectorOfUnits(Options.OutputCorpus.c_str(), &AdditionalCorpus,
&EpochOfLastReadOfOutputCorpus, MaxSize,
/*ExitOnError*/ false);
if (Options.Verbosity >= 2)
Printf("Reload: read %zd new units.\n", AdditionalCorpus.size());
bool Reloaded = false;
for (auto &U : AdditionalCorpus) {
if (U.size() > MaxSize)
U.resize(MaxSize);
if (!Corpus.HasUnit(U)) {
if (size_t NumFeatures = RunOne(U)) {
CheckExitOnSrcPosOrItem();
Corpus.AddToCorpus(U, NumFeatures);
Reloaded = true;
}
}
}
if (Reloaded)
PrintStats("RELOAD");
}
void Fuzzer::ShuffleCorpus(UnitVector *V) {
std::random_shuffle(V->begin(), V->end(), MD.GetRand());
if (Options.PreferSmall)
std::stable_sort(V->begin(), V->end(), [](const Unit &A, const Unit &B) {
return A.size() < B.size();
});
}
void Fuzzer::ShuffleAndMinimize(UnitVector *InitialCorpus) {
Printf("#0\tREAD units: %zd\n", InitialCorpus->size());
if (Options.ShuffleAtStartUp)
ShuffleCorpus(InitialCorpus);
// Test the callback with empty input and never try it again.
uint8_t dummy;
ExecuteCallback(&dummy, 0);
for (const auto &U : *InitialCorpus) {
if (size_t NumFeatures = RunOne(U)) {
CheckExitOnSrcPosOrItem();
Corpus.AddToCorpus(U, NumFeatures);
if (Options.Verbosity >= 2)
Printf("NEW0: %zd L %zd\n", MaxCoverage.BlockCoverage, U.size());
}
TryDetectingAMemoryLeak(U.data(), U.size(),
/*DuringInitialCorpusExecution*/ true);
}
PrintStats("INITED");
if (Corpus.empty()) {
Printf("ERROR: no interesting inputs were found. "
"Is the code instrumented for coverage? Exiting.\n");
exit(1);
}
}
size_t Fuzzer::RunOne(const uint8_t *Data, size_t Size) {
if (!Size) return 0;
TotalNumberOfRuns++;
ExecuteCallback(Data, Size);
size_t Res = 0;
if (size_t NumFeatures = TPC.CollectFeatures([&](size_t Feature) -> bool {
return Corpus.AddFeature(Feature, Size, Options.Shrink);
}))
Res = NumFeatures;
if (!TPC.UsingTracePcGuard()) {
if (TPC.UpdateValueProfileMap(&MaxCoverage.VPMap))
Res = 1;
if (!Res && RecordMaxCoverage(&MaxCoverage))
Res = 1;
}
auto TimeOfUnit =
duration_cast<seconds>(UnitStopTime - UnitStartTime).count();
if (!(TotalNumberOfRuns & (TotalNumberOfRuns - 1)) &&
secondsSinceProcessStartUp() >= 2)
PrintStats("pulse ");
if (TimeOfUnit > TimeOfLongestUnitInSeconds * 1.1 &&
TimeOfUnit >= Options.ReportSlowUnits) {
TimeOfLongestUnitInSeconds = TimeOfUnit;
Printf("Slowest unit: %zd s:\n", TimeOfLongestUnitInSeconds);
WriteUnitToFileWithPrefix({Data, Data + Size}, "slow-unit-");
}
return Res;
}
size_t Fuzzer::GetCurrentUnitInFuzzingThead(const uint8_t **Data) const {
assert(InFuzzingThread());
*Data = CurrentUnitData;
return CurrentUnitSize;
}
void Fuzzer::ExecuteCallback(const uint8_t *Data, size_t Size) {
assert(InFuzzingThread());
// We copy the contents of Unit into a separate heap buffer
// so that we reliably find buffer overflows in it.
uint8_t *DataCopy = new uint8_t[Size];
memcpy(DataCopy, Data, Size);
if (CurrentUnitData && CurrentUnitData != Data)
memcpy(CurrentUnitData, Data, Size);
CurrentUnitSize = Size;
AllocTracer.Start(Options.TraceMalloc);
UnitStartTime = system_clock::now();
ResetCounters(); // Reset coverage right before the callback.
TPC.ResetMaps();
RunningCB = true;
int Res = CB(DataCopy, Size);
RunningCB = false;
UnitStopTime = system_clock::now();
(void)Res;
assert(Res == 0);
HasMoreMallocsThanFrees = AllocTracer.Stop();
CurrentUnitSize = 0;
delete[] DataCopy;
}
void Fuzzer::WriteToOutputCorpus(const Unit &U) {
if (Options.OnlyASCII)
assert(IsASCII(U));
if (Options.OutputCorpus.empty())
return;
std::string Path = DirPlusFile(Options.OutputCorpus, Hash(U));
WriteToFile(U, Path);
if (Options.Verbosity >= 2)
Printf("Written to %s\n", Path.c_str());
}
void Fuzzer::WriteUnitToFileWithPrefix(const Unit &U, const char *Prefix) {
if (!Options.SaveArtifacts)
return;
std::string Path = Options.ArtifactPrefix + Prefix + Hash(U);
if (!Options.ExactArtifactPath.empty())
Path = Options.ExactArtifactPath; // Overrides ArtifactPrefix.
WriteToFile(U, Path);
Printf("artifact_prefix='%s'; Test unit written to %s\n",
Options.ArtifactPrefix.c_str(), Path.c_str());
if (U.size() <= kMaxUnitSizeToPrint)
Printf("Base64: %s\n", Base64(U).c_str());
}
void Fuzzer::PrintStatusForNewUnit(const Unit &U) {
if (!Options.PrintNEW)
return;
PrintStats("NEW ", "");
if (Options.Verbosity) {
Printf(" L: %zd ", U.size());
MD.PrintMutationSequence();
Printf("\n");
}
}
void Fuzzer::ReportNewCoverage(InputInfo *II, const Unit &U) {
II->NumSuccessfullMutations++;
MD.RecordSuccessfulMutationSequence();
PrintStatusForNewUnit(U);
WriteToOutputCorpus(U);
NumberOfNewUnitsAdded++;
TPC.PrintNewPCs();
}
// Finds minimal number of units in 'Extra' that add coverage to 'Initial'.
// We do it by actually executing the units, sometimes more than once,
// because we may be using different coverage-like signals and the only
// common thing between them is that we can say "this unit found new stuff".
UnitVector Fuzzer::FindExtraUnits(const UnitVector &Initial,
const UnitVector &Extra) {
UnitVector Res = Extra;
UnitVector Tmp;
size_t OldSize = Res.size();
for (int Iter = 0; Iter < 10; Iter++) {
ShuffleCorpus(&Res);
TPC.ResetMaps();
Corpus.ResetFeatureSet();
ResetCoverage();
for (auto &U : Initial) {
TPC.ResetMaps();
RunOne(U);
}
Tmp.clear();
for (auto &U : Res) {
TPC.ResetMaps();
if (RunOne(U))
Tmp.push_back(U);
}
char Stat[7] = "MIN ";
Stat[3] = '0' + Iter;
PrintStats(Stat, "\n", Tmp.size());
size_t NewSize = Tmp.size();
assert(NewSize <= OldSize);
Res.swap(Tmp);
if (NewSize + 5 >= OldSize)
break;
OldSize = NewSize;
}
return Res;
}
void Fuzzer::Merge(const std::vector<std::string> &Corpora) {
if (Corpora.size() <= 1) {
Printf("Merge requires two or more corpus dirs\n");
return;
}
InMergeMode = true;
std::vector<std::string> ExtraCorpora(Corpora.begin() + 1, Corpora.end());
assert(MaxInputLen > 0);
UnitVector Initial, Extra;
ReadDirToVectorOfUnits(Corpora[0].c_str(), &Initial, nullptr, MaxInputLen,
true);
for (auto &C : ExtraCorpora)
ReadDirToVectorOfUnits(C.c_str(), &Extra, nullptr, MaxInputLen, true);
if (!Initial.empty()) {
Printf("=== Minimizing the initial corpus of %zd units\n", Initial.size());
Initial = FindExtraUnits({}, Initial);
}
Printf("=== Merging extra %zd units\n", Extra.size());
auto Res = FindExtraUnits(Initial, Extra);
for (auto &U: Res)
WriteToOutputCorpus(U);
Printf("=== Merge: written %zd units\n", Res.size());
}
// Tries detecting a memory leak on the particular input that we have just
// executed before calling this function.
void Fuzzer::TryDetectingAMemoryLeak(const uint8_t *Data, size_t Size,
bool DuringInitialCorpusExecution) {
if (!HasMoreMallocsThanFrees) return; // mallocs==frees, a leak is unlikely.
if (!Options.DetectLeaks) return;
if (!&(EF->__lsan_enable) || !&(EF->__lsan_disable) ||
!(EF->__lsan_do_recoverable_leak_check))
return; // No lsan.
// Run the target once again, but with lsan disabled so that if there is
// a real leak we do not report it twice.
EF->__lsan_disable();
ExecuteCallback(Data, Size);
EF->__lsan_enable();
if (!HasMoreMallocsThanFrees) return; // a leak is unlikely.
if (NumberOfLeakDetectionAttempts++ > 1000) {
Options.DetectLeaks = false;
Printf("INFO: libFuzzer disabled leak detection after every mutation.\n"
" Most likely the target function accumulates allocated\n"
" memory in a global state w/o actually leaking it.\n"
" You may try running this binary with -trace_malloc=[12]"
" to get a trace of mallocs and frees.\n"
" If LeakSanitizer is enabled in this process it will still\n"
" run on the process shutdown.\n");
return;
}
// Now perform the actual lsan pass. This is expensive and we must ensure
// we don't call it too often.
if (EF->__lsan_do_recoverable_leak_check()) { // Leak is found, report it.
if (DuringInitialCorpusExecution)
Printf("\nINFO: a leak has been found in the initial corpus.\n\n");
Printf("INFO: to ignore leaks on libFuzzer side use -detect_leaks=0.\n\n");
CurrentUnitSize = Size;
DumpCurrentUnit("leak-");
PrintFinalStats();
_Exit(Options.ErrorExitCode); // not exit() to disable lsan further on.
}
}
void Fuzzer::MutateAndTestOne() {
MD.StartMutationSequence();
auto &II = Corpus.ChooseUnitToMutate(MD.GetRand());
const auto &U = II.U;
memcpy(BaseSha1, II.Sha1, sizeof(BaseSha1));
assert(CurrentUnitData);
size_t Size = U.size();
assert(Size <= MaxInputLen && "Oversized Unit");
memcpy(CurrentUnitData, U.data(), Size);
assert(MaxMutationLen > 0);
for (int i = 0; i < Options.MutateDepth; i++) {
if (TotalNumberOfRuns >= Options.MaxNumberOfRuns)
break;
size_t NewSize = 0;
NewSize = MD.Mutate(CurrentUnitData, Size, MaxMutationLen);
assert(NewSize > 0 && "Mutator returned empty unit");
assert(NewSize <= MaxMutationLen && "Mutator return overisized unit");
Size = NewSize;
if (i == 0)
StartTraceRecording();
II.NumExecutedMutations++;
if (size_t NumFeatures = RunOne(CurrentUnitData, Size)) {
Corpus.AddToCorpus({CurrentUnitData, CurrentUnitData + Size}, NumFeatures,
/*MayDeleteFile=*/true);
ReportNewCoverage(&II, {CurrentUnitData, CurrentUnitData + Size});
CheckExitOnSrcPosOrItem();
}
StopTraceRecording();
TryDetectingAMemoryLeak(CurrentUnitData, Size,
/*DuringInitialCorpusExecution*/ false);
}
}
void Fuzzer::ResetCoverage() {
ResetEdgeCoverage();
MaxCoverage.Reset();
PrepareCounters(&MaxCoverage);
}
void Fuzzer::Loop() {
system_clock::time_point LastCorpusReload = system_clock::now();
if (Options.DoCrossOver)
MD.SetCorpus(&Corpus);
while (true) {
auto Now = system_clock::now();
if (duration_cast<seconds>(Now - LastCorpusReload).count() >=
Options.ReloadIntervalSec) {
RereadOutputCorpus(MaxInputLen);
LastCorpusReload = system_clock::now();
}
if (TotalNumberOfRuns >= Options.MaxNumberOfRuns)
break;
if (TimedOut()) break;
// Perform several mutations and runs.
MutateAndTestOne();
}
PrintStats("DONE ", "\n");
MD.PrintRecommendedDictionary();
}
void Fuzzer::MinimizeCrashLoop(const Unit &U) {
if (U.size() <= 2) return;
while (!TimedOut() && TotalNumberOfRuns < Options.MaxNumberOfRuns) {
MD.StartMutationSequence();
memcpy(CurrentUnitData, U.data(), U.size());
for (int i = 0; i < Options.MutateDepth; i++) {
size_t NewSize = MD.Mutate(CurrentUnitData, U.size(), MaxMutationLen);
assert(NewSize > 0 && NewSize <= MaxMutationLen);
RunOne(CurrentUnitData, NewSize);
TryDetectingAMemoryLeak(CurrentUnitData, NewSize,
/*DuringInitialCorpusExecution*/ false);
}
}
}
} // namespace fuzzer
extern "C" {
size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize) {
assert(fuzzer::F);
return fuzzer::F->GetMD().DefaultMutate(Data, Size, MaxSize);
}
} // extern "C"
+21
View File
@@ -0,0 +1,21 @@
//===- FuzzerMain.cpp - main() function and flags -------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// main() and flags.
//===----------------------------------------------------------------------===//
#include "FuzzerDefs.h"
extern "C" {
// This function should be defined by the user.
int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size);
} // extern "C"
int main(int argc, char **argv) {
return fuzzer::FuzzerDriver(&argc, &argv, LLVMFuzzerTestOneInput);
}
+261
View File
@@ -0,0 +1,261 @@
//===- FuzzerMerge.cpp - merging corpora ----------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// Merging corpora.
//===----------------------------------------------------------------------===//
#include "FuzzerInternal.h"
#include "FuzzerIO.h"
#include "FuzzerMerge.h"
#include "FuzzerTracePC.h"
#include "FuzzerUtil.h"
#include <fstream>
#include <iterator>
#include <sstream>
namespace fuzzer {
bool Merger::Parse(const std::string &Str, bool ParseCoverage) {
std::istringstream SS(Str);
return Parse(SS, ParseCoverage);
}
void Merger::ParseOrExit(std::istream &IS, bool ParseCoverage) {
if (!Parse(IS, ParseCoverage)) {
Printf("MERGE: failed to parse the control file (unexpected error)\n");
exit(1);
}
}
// The control file example:
//
// 3 # The number of inputs
// 1 # The number of inputs in the first corpus, <= the previous number
// file0
// file1
// file2 # One file name per line.
// STARTED 0 123 # FileID, file size
// DONE 0 1 4 6 8 # FileID COV1 COV2 ...
// STARTED 1 456 # If DONE is missing, the input crashed while processing.
// STARTED 2 567
// DONE 2 8 9
bool Merger::Parse(std::istream &IS, bool ParseCoverage) {
LastFailure.clear();
std::string Line;
// Parse NumFiles.
if (!std::getline(IS, Line, '\n')) return false;
std::istringstream L1(Line);
size_t NumFiles = 0;
L1 >> NumFiles;
if (NumFiles == 0 || NumFiles > 10000000) return false;
// Parse NumFilesInFirstCorpus.
if (!std::getline(IS, Line, '\n')) return false;
std::istringstream L2(Line);
NumFilesInFirstCorpus = NumFiles + 1;
L2 >> NumFilesInFirstCorpus;
if (NumFilesInFirstCorpus > NumFiles) return false;
// Parse file names.
Files.resize(NumFiles);
for (size_t i = 0; i < NumFiles; i++)
if (!std::getline(IS, Files[i].Name, '\n'))
return false;
// Parse STARTED and DONE lines.
size_t ExpectedStartMarker = 0;
const size_t kInvalidStartMarker = -1;
size_t LastSeenStartMarker = kInvalidStartMarker;
while (std::getline(IS, Line, '\n')) {
std::istringstream ISS1(Line);
std::string Marker;
size_t N;
ISS1 >> Marker;
ISS1 >> N;
if (Marker == "STARTED") {
// STARTED FILE_ID FILE_SIZE
if (ExpectedStartMarker != N)
return false;
ISS1 >> Files[ExpectedStartMarker].Size;
LastSeenStartMarker = ExpectedStartMarker;
assert(ExpectedStartMarker < Files.size());
ExpectedStartMarker++;
} else if (Marker == "DONE") {
// DONE FILE_SIZE COV1 COV2 COV3 ...
size_t CurrentFileIdx = N;
if (CurrentFileIdx != LastSeenStartMarker)
return false;
LastSeenStartMarker = kInvalidStartMarker;
if (ParseCoverage) {
auto &V = Files[CurrentFileIdx].Features;
V.clear();
while (ISS1 >> std::hex >> N)
V.push_back(N);
std::sort(V.begin(), V.end());
}
} else {
return false;
}
}
if (LastSeenStartMarker != kInvalidStartMarker)
LastFailure = Files[LastSeenStartMarker].Name;
FirstNotProcessedFile = ExpectedStartMarker;
return true;
}
// Decides which files need to be merged (add thost to NewFiles).
// Returns the number of new features added.
size_t Merger::Merge(std::vector<std::string> *NewFiles) {
NewFiles->clear();
assert(NumFilesInFirstCorpus <= Files.size());
std::set<uint32_t> AllFeatures;
// What features are in the initial corpus?
for (size_t i = 0; i < NumFilesInFirstCorpus; i++) {
auto &Cur = Files[i].Features;
AllFeatures.insert(Cur.begin(), Cur.end());
}
size_t InitialNumFeatures = AllFeatures.size();
// Remove all features that we already know from all other inputs.
for (size_t i = NumFilesInFirstCorpus; i < Files.size(); i++) {
auto &Cur = Files[i].Features;
std::vector<uint32_t> Tmp;
std::set_difference(Cur.begin(), Cur.end(), AllFeatures.begin(),
AllFeatures.end(), std::inserter(Tmp, Tmp.begin()));
Cur.swap(Tmp);
}
// Sort. Give preference to
// * smaller files
// * files with more features.
std::sort(Files.begin() + NumFilesInFirstCorpus, Files.end(),
[&](const MergeFileInfo &a, const MergeFileInfo &b) -> bool {
if (a.Size != b.Size)
return a.Size < b.Size;
return a.Features.size() > b.Features.size();
});
// One greedy pass: add the file's features to AllFeatures.
// If new features were added, add this file to NewFiles.
for (size_t i = NumFilesInFirstCorpus; i < Files.size(); i++) {
auto &Cur = Files[i].Features;
// Printf("%s -> sz %zd ft %zd\n", Files[i].Name.c_str(),
// Files[i].Size, Cur.size());
size_t OldSize = AllFeatures.size();
AllFeatures.insert(Cur.begin(), Cur.end());
if (AllFeatures.size() > OldSize)
NewFiles->push_back(Files[i].Name);
}
return AllFeatures.size() - InitialNumFeatures;
}
// Inner process. May crash if the target crashes.
void Fuzzer::CrashResistantMergeInternalStep(const std::string &CFPath) {
Printf("MERGE-INNER: using the control file '%s'\n", CFPath.c_str());
Merger M;
std::ifstream IF(CFPath);
M.ParseOrExit(IF, false);
IF.close();
if (!M.LastFailure.empty())
Printf("MERGE-INNER: '%s' caused a failure at the previous merge step\n",
M.LastFailure.c_str());
Printf("MERGE-INNER: %zd total files;"
" %zd processed earlier; will process %zd files now\n",
M.Files.size(), M.FirstNotProcessedFile,
M.Files.size() - M.FirstNotProcessedFile);
std::ofstream OF(CFPath, std::ofstream::out | std::ofstream::app);
for (size_t i = M.FirstNotProcessedFile; i < M.Files.size(); i++) {
auto U = FileToVector(M.Files[i].Name);
if (U.size() > MaxInputLen) {
U.resize(MaxInputLen);
U.shrink_to_fit();
}
std::ostringstream StartedLine;
// Write the pre-run marker.
OF << "STARTED " << std::dec << i << " " << U.size() << "\n";
OF.flush(); // Flush is important since ExecuteCommand may crash.
// Run.
TPC.ResetMaps();
ExecuteCallback(U.data(), U.size());
// Collect coverage.
std::set<size_t> Features;
TPC.CollectFeatures([&](size_t Feature) -> bool {
Features.insert(Feature);
return true;
});
// Show stats.
TotalNumberOfRuns++;
if (!(TotalNumberOfRuns & (TotalNumberOfRuns - 1)))
PrintStats("pulse ");
// Write the post-run marker and the coverage.
OF << "DONE " << i;
for (size_t F : Features)
OF << " " << std::hex << F;
OF << "\n";
}
}
// Outer process. Does not call the target code and thus sohuld not fail.
void Fuzzer::CrashResistantMerge(const std::vector<std::string> &Args,
const std::vector<std::string> &Corpora) {
if (Corpora.size() <= 1) {
Printf("Merge requires two or more corpus dirs\n");
return;
}
std::vector<std::string> AllFiles;
ListFilesInDirRecursive(Corpora[0], nullptr, &AllFiles, /*TopDir*/true);
size_t NumFilesInFirstCorpus = AllFiles.size();
for (size_t i = 1; i < Corpora.size(); i++)
ListFilesInDirRecursive(Corpora[i], nullptr, &AllFiles, /*TopDir*/true);
Printf("MERGE-OUTER: %zd files, %zd in the initial corpus\n",
AllFiles.size(), NumFilesInFirstCorpus);
std::string CFPath =
"libFuzzerTemp." + std::to_string(GetPid()) + ".txt";
// Write the control file.
RemoveFile(CFPath);
std::ofstream ControlFile(CFPath);
ControlFile << AllFiles.size() << "\n";
ControlFile << NumFilesInFirstCorpus << "\n";
for (auto &Path: AllFiles)
ControlFile << Path << "\n";
ControlFile.close();
// Execute the inner process untill it passes.
// Every inner process should execute at least one input.
std::string BaseCmd = CloneArgsWithoutX(Args, "keep-all-flags");
for (size_t i = 1; i <= AllFiles.size(); i++) {
Printf("MERGE-OUTER: attempt %zd\n", i);
auto ExitCode =
ExecuteCommand(BaseCmd + " -merge_control_file=" + CFPath);
if (!ExitCode) {
Printf("MERGE-OUTER: succesfull in %zd attempt(s)\n", i);
break;
}
}
// Read the control file and do the merge.
Merger M;
std::ifstream IF(CFPath);
M.ParseOrExit(IF, true);
IF.close();
std::vector<std::string> NewFiles;
size_t NumNewFeatures = M.Merge(&NewFiles);
Printf("MERGE-OUTER: %zd new files with %zd new features added\n",
NewFiles.size(), NumNewFeatures);
for (auto &F: NewFiles)
WriteToOutputCorpus(FileToVector(F));
// We are done, delete the control file.
RemoveFile(CFPath);
}
} // namespace fuzzer
+70
View File
@@ -0,0 +1,70 @@
//===- FuzzerMerge.h - merging corpa ----------------------------*- C++ -* ===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// Merging Corpora.
//
// The task:
// Take the existing corpus (possibly empty) and merge new inputs into
// it so that only inputs with new coverage ('features') are added.
// The process should tolerate the crashes, OOMs, leaks, etc.
//
// Algorithm:
// The outter process collects the set of files and writes their names
// into a temporary "control" file, then repeatedly launches the inner
// process until all inputs are processed.
// The outer process does not actually execute the target code.
//
// The inner process reads the control file and sees a) list of all the inputs
// and b) the last processed input. Then it starts processing the inputs one
// by one. Before processing every input it writes one line to control file:
// STARTED INPUT_ID INPUT_SIZE
// After processing an input it write another line:
// DONE INPUT_ID Feature1 Feature2 Feature3 ...
// If a crash happens while processing an input the last line in the control
// file will be "STARTED INPUT_ID" and so the next process will know
// where to resume.
//
// Once all inputs are processed by the innner process(es) the outer process
// reads the control files and does the merge based entirely on the contents
// of control file.
// It uses a single pass greedy algorithm choosing first the smallest inputs
// within the same size the inputs that have more new features.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_FUZZER_MERGE_H
#define LLVM_FUZZER_MERGE_H
#include "FuzzerDefs.h"
#include <istream>
#include <set>
namespace fuzzer {
struct MergeFileInfo {
std::string Name;
size_t Size = 0;
std::vector<uint32_t> Features;
};
struct Merger {
std::vector<MergeFileInfo> Files;
size_t NumFilesInFirstCorpus = 0;
size_t FirstNotProcessedFile = 0;
std::string LastFailure;
bool Parse(std::istream &IS, bool ParseCoverage);
bool Parse(const std::string &Str, bool ParseCoverage);
void ParseOrExit(std::istream &IS, bool ParseCoverage);
size_t Merge(std::vector<std::string> *NewFiles);
};
} // namespace fuzzer
#endif // LLVM_FUZZER_MERGE_H
+527
View File
@@ -0,0 +1,527 @@
//===- FuzzerMutate.cpp - Mutate a test input -----------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// Mutate a test input.
//===----------------------------------------------------------------------===//
#include "FuzzerCorpus.h"
#include "FuzzerDefs.h"
#include "FuzzerExtFunctions.h"
#include "FuzzerIO.h"
#include "FuzzerMutate.h"
#include "FuzzerOptions.h"
namespace fuzzer {
const size_t Dictionary::kMaxDictSize;
static void PrintASCII(const Word &W, const char *PrintAfter) {
PrintASCII(W.data(), W.size(), PrintAfter);
}
MutationDispatcher::MutationDispatcher(Random &Rand,
const FuzzingOptions &Options)
: Rand(Rand), Options(Options) {
DefaultMutators.insert(
DefaultMutators.begin(),
{
{&MutationDispatcher::Mutate_EraseBytes, "EraseBytes"},
{&MutationDispatcher::Mutate_InsertByte, "InsertByte"},
{&MutationDispatcher::Mutate_InsertRepeatedBytes,
"InsertRepeatedBytes"},
{&MutationDispatcher::Mutate_ChangeByte, "ChangeByte"},
{&MutationDispatcher::Mutate_ChangeBit, "ChangeBit"},
{&MutationDispatcher::Mutate_ShuffleBytes, "ShuffleBytes"},
{&MutationDispatcher::Mutate_ChangeASCIIInteger, "ChangeASCIIInt"},
{&MutationDispatcher::Mutate_ChangeBinaryInteger, "ChangeBinInt"},
{&MutationDispatcher::Mutate_CopyPart, "CopyPart"},
{&MutationDispatcher::Mutate_CrossOver, "CrossOver"},
{&MutationDispatcher::Mutate_AddWordFromManualDictionary,
"ManualDict"},
{&MutationDispatcher::Mutate_AddWordFromTemporaryAutoDictionary,
"TempAutoDict"},
{&MutationDispatcher::Mutate_AddWordFromPersistentAutoDictionary,
"PersAutoDict"},
});
if(Options.UseCmp)
DefaultMutators.push_back(
{&MutationDispatcher::Mutate_AddWordFromTORC, "CMP"});
if (EF->LLVMFuzzerCustomMutator)
Mutators.push_back({&MutationDispatcher::Mutate_Custom, "Custom"});
else
Mutators = DefaultMutators;
if (EF->LLVMFuzzerCustomCrossOver)
Mutators.push_back(
{&MutationDispatcher::Mutate_CustomCrossOver, "CustomCrossOver"});
}
static char RandCh(Random &Rand) {
if (Rand.RandBool()) return Rand(256);
const char *Special = "!*'();:@&=+$,/?%#[]012Az-`~.\xff\x00";
return Special[Rand(sizeof(Special) - 1)];
}
size_t MutationDispatcher::Mutate_Custom(uint8_t *Data, size_t Size,
size_t MaxSize) {
return EF->LLVMFuzzerCustomMutator(Data, Size, MaxSize, Rand.Rand());
}
size_t MutationDispatcher::Mutate_CustomCrossOver(uint8_t *Data, size_t Size,
size_t MaxSize) {
if (!Corpus || Corpus->size() < 2 || Size == 0)
return 0;
size_t Idx = Rand(Corpus->size());
const Unit &Other = (*Corpus)[Idx];
if (Other.empty())
return 0;
MutateInPlaceHere.resize(MaxSize);
auto &U = MutateInPlaceHere;
size_t NewSize = EF->LLVMFuzzerCustomCrossOver(
Data, Size, Other.data(), Other.size(), U.data(), U.size(), Rand.Rand());
if (!NewSize)
return 0;
assert(NewSize <= MaxSize && "CustomCrossOver returned overisized unit");
memcpy(Data, U.data(), NewSize);
return NewSize;
}
size_t MutationDispatcher::Mutate_ShuffleBytes(uint8_t *Data, size_t Size,
size_t MaxSize) {
if (Size > MaxSize) return 0;
assert(Size);
size_t ShuffleAmount =
Rand(std::min(Size, (size_t)8)) + 1; // [1,8] and <= Size.
size_t ShuffleStart = Rand(Size - ShuffleAmount);
assert(ShuffleStart + ShuffleAmount <= Size);
std::random_shuffle(Data + ShuffleStart, Data + ShuffleStart + ShuffleAmount,
Rand);
return Size;
}
size_t MutationDispatcher::Mutate_EraseBytes(uint8_t *Data, size_t Size,
size_t MaxSize) {
assert(Size);
if (Size == 1) return 0;
size_t N = Rand(Size / 2) + 1;
assert(N < Size);
size_t Idx = Rand(Size - N + 1);
// Erase Data[Idx:Idx+N].
memmove(Data + Idx, Data + Idx + N, Size - Idx - N);
// Printf("Erase: %zd %zd => %zd; Idx %zd\n", N, Size, Size - N, Idx);
return Size - N;
}
size_t MutationDispatcher::Mutate_InsertByte(uint8_t *Data, size_t Size,
size_t MaxSize) {
if (Size >= MaxSize) return 0;
size_t Idx = Rand(Size + 1);
// Insert new value at Data[Idx].
memmove(Data + Idx + 1, Data + Idx, Size - Idx);
Data[Idx] = RandCh(Rand);
return Size + 1;
}
size_t MutationDispatcher::Mutate_InsertRepeatedBytes(uint8_t *Data,
size_t Size,
size_t MaxSize) {
const size_t kMinBytesToInsert = 3;
if (Size + kMinBytesToInsert >= MaxSize) return 0;
size_t MaxBytesToInsert = std::min(MaxSize - Size, (size_t)128);
size_t N = Rand(MaxBytesToInsert - kMinBytesToInsert + 1) + kMinBytesToInsert;
assert(Size + N <= MaxSize && N);
size_t Idx = Rand(Size + 1);
// Insert new values at Data[Idx].
memmove(Data + Idx + N, Data + Idx, Size - Idx);
// Give preference to 0x00 and 0xff.
uint8_t Byte = Rand.RandBool() ? Rand(256) : (Rand.RandBool() ? 0 : 255);
for (size_t i = 0; i < N; i++)
Data[Idx + i] = Byte;
return Size + N;
}
size_t MutationDispatcher::Mutate_ChangeByte(uint8_t *Data, size_t Size,
size_t MaxSize) {
if (Size > MaxSize) return 0;
size_t Idx = Rand(Size);
Data[Idx] = RandCh(Rand);
return Size;
}
size_t MutationDispatcher::Mutate_ChangeBit(uint8_t *Data, size_t Size,
size_t MaxSize) {
if (Size > MaxSize) return 0;
size_t Idx = Rand(Size);
Data[Idx] ^= 1 << Rand(8);
return Size;
}
size_t MutationDispatcher::Mutate_AddWordFromManualDictionary(uint8_t *Data,
size_t Size,
size_t MaxSize) {
return AddWordFromDictionary(ManualDictionary, Data, Size, MaxSize);
}
size_t MutationDispatcher::Mutate_AddWordFromTemporaryAutoDictionary(
uint8_t *Data, size_t Size, size_t MaxSize) {
return AddWordFromDictionary(TempAutoDictionary, Data, Size, MaxSize);
}
size_t MutationDispatcher::ApplyDictionaryEntry(uint8_t *Data, size_t Size,
size_t MaxSize,
DictionaryEntry &DE) {
const Word &W = DE.GetW();
bool UsePositionHint = DE.HasPositionHint() &&
DE.GetPositionHint() + W.size() < Size &&
Rand.RandBool();
if (Rand.RandBool()) { // Insert W.
if (Size + W.size() > MaxSize) return 0;
size_t Idx = UsePositionHint ? DE.GetPositionHint() : Rand(Size + 1);
memmove(Data + Idx + W.size(), Data + Idx, Size - Idx);
memcpy(Data + Idx, W.data(), W.size());
Size += W.size();
} else { // Overwrite some bytes with W.
if (W.size() > Size) return 0;
size_t Idx = UsePositionHint ? DE.GetPositionHint() : Rand(Size - W.size());
memcpy(Data + Idx, W.data(), W.size());
}
return Size;
}
// Somewhere in the past we have observed a comparison instructions
// with arguments Arg1 Arg2. This function tries to guess a dictionary
// entry that will satisfy that comparison.
// It first tries to find one of the arguments (possibly swapped) in the
// input and if it succeeds it creates a DE with a position hint.
// Otherwise it creates a DE with one of the arguments w/o a position hint.
template <class T>
DictionaryEntry MutationDispatcher::MakeDictionaryEntryFromCMP(
T Arg1, T Arg2, const uint8_t *Data, size_t Size) {
ScopedDoingMyOwnMemmem scoped_doing_my_own_memmem;
bool HandleFirst = Rand.RandBool();
T ExistingBytes, DesiredBytes;
Word W;
const uint8_t *End = Data + Size;
for (int Arg = 0; Arg < 2; Arg++) {
ExistingBytes = HandleFirst ? Arg1 : Arg2;
DesiredBytes = HandleFirst ? Arg2 : Arg1;
DesiredBytes += Rand(-1, 1);
if (Rand.RandBool()) ExistingBytes = Bswap(ExistingBytes);
if (Rand.RandBool()) DesiredBytes = Bswap(DesiredBytes);
HandleFirst = !HandleFirst;
W.Set(reinterpret_cast<uint8_t*>(&DesiredBytes), sizeof(T));
const size_t kMaxNumPositions = 8;
size_t Positions[kMaxNumPositions];
size_t NumPositions = 0;
for (const uint8_t *Cur = Data;
Cur < End && NumPositions < kMaxNumPositions; Cur++) {
Cur = (uint8_t *)SearchMemory(Cur, End - Cur, &ExistingBytes, sizeof(T));
if (!Cur) break;
Positions[NumPositions++] = Cur - Data;
}
if (!NumPositions) break;
return DictionaryEntry(W, Positions[Rand(NumPositions)]);
}
DictionaryEntry DE(W);
return DE;
}
size_t MutationDispatcher::Mutate_AddWordFromTORC(
uint8_t *Data, size_t Size, size_t MaxSize) {
Word W;
DictionaryEntry DE;
if (Rand.RandBool()) {
auto X = TPC.TORC8.Get(Rand.Rand());
DE = MakeDictionaryEntryFromCMP(X.A, X.B, Data, Size);
} else {
auto X = TPC.TORC4.Get(Rand.Rand());
if ((X.A >> 16) == 0 && (X.B >> 16) == 0 && Rand.RandBool())
DE = MakeDictionaryEntryFromCMP((uint16_t)X.A, (uint16_t)X.B, Data,
Size);
else
DE = MakeDictionaryEntryFromCMP(X.A, X.B, Data, Size);
}
Size = ApplyDictionaryEntry(Data, Size, MaxSize, DE);
if (!Size) return 0;
DictionaryEntry &DERef =
CmpDictionaryEntriesDeque[CmpDictionaryEntriesDequeIdx++ %
kCmpDictionaryEntriesDequeSize];
DERef = DE;
CurrentDictionaryEntrySequence.push_back(&DERef);
return Size;
}
size_t MutationDispatcher::Mutate_AddWordFromPersistentAutoDictionary(
uint8_t *Data, size_t Size, size_t MaxSize) {
return AddWordFromDictionary(PersistentAutoDictionary, Data, Size, MaxSize);
}
size_t MutationDispatcher::AddWordFromDictionary(Dictionary &D, uint8_t *Data,
size_t Size, size_t MaxSize) {
if (Size > MaxSize) return 0;
if (D.empty()) return 0;
DictionaryEntry &DE = D[Rand(D.size())];
Size = ApplyDictionaryEntry(Data, Size, MaxSize, DE);
if (!Size) return 0;
DE.IncUseCount();
CurrentDictionaryEntrySequence.push_back(&DE);
return Size;
}
// Overwrites part of To[0,ToSize) with a part of From[0,FromSize).
// Returns ToSize.
size_t MutationDispatcher::CopyPartOf(const uint8_t *From, size_t FromSize,
uint8_t *To, size_t ToSize) {
// Copy From[FromBeg, FromBeg + CopySize) into To[ToBeg, ToBeg + CopySize).
size_t ToBeg = Rand(ToSize);
size_t CopySize = Rand(ToSize - ToBeg) + 1;
assert(ToBeg + CopySize <= ToSize);
CopySize = std::min(CopySize, FromSize);
size_t FromBeg = Rand(FromSize - CopySize + 1);
assert(FromBeg + CopySize <= FromSize);
memmove(To + ToBeg, From + FromBeg, CopySize);
return ToSize;
}
// Inserts part of From[0,ToSize) into To.
// Returns new size of To on success or 0 on failure.
size_t MutationDispatcher::InsertPartOf(const uint8_t *From, size_t FromSize,
uint8_t *To, size_t ToSize,
size_t MaxToSize) {
if (ToSize >= MaxToSize) return 0;
size_t AvailableSpace = MaxToSize - ToSize;
size_t MaxCopySize = std::min(AvailableSpace, FromSize);
size_t CopySize = Rand(MaxCopySize) + 1;
size_t FromBeg = Rand(FromSize - CopySize + 1);
assert(FromBeg + CopySize <= FromSize);
size_t ToInsertPos = Rand(ToSize + 1);
assert(ToInsertPos + CopySize <= MaxToSize);
size_t TailSize = ToSize - ToInsertPos;
if (To == From) {
MutateInPlaceHere.resize(MaxToSize);
memcpy(MutateInPlaceHere.data(), From + FromBeg, CopySize);
memmove(To + ToInsertPos + CopySize, To + ToInsertPos, TailSize);
memmove(To + ToInsertPos, MutateInPlaceHere.data(), CopySize);
} else {
memmove(To + ToInsertPos + CopySize, To + ToInsertPos, TailSize);
memmove(To + ToInsertPos, From + FromBeg, CopySize);
}
return ToSize + CopySize;
}
size_t MutationDispatcher::Mutate_CopyPart(uint8_t *Data, size_t Size,
size_t MaxSize) {
if (Size > MaxSize) return 0;
if (Rand.RandBool())
return CopyPartOf(Data, Size, Data, Size);
else
return InsertPartOf(Data, Size, Data, Size, MaxSize);
}
size_t MutationDispatcher::Mutate_ChangeASCIIInteger(uint8_t *Data, size_t Size,
size_t MaxSize) {
if (Size > MaxSize) return 0;
size_t B = Rand(Size);
while (B < Size && !isdigit(Data[B])) B++;
if (B == Size) return 0;
size_t E = B;
while (E < Size && isdigit(Data[E])) E++;
assert(B < E);
// now we have digits in [B, E).
// strtol and friends don't accept non-zero-teminated data, parse it manually.
uint64_t Val = Data[B] - '0';
for (size_t i = B + 1; i < E; i++)
Val = Val * 10 + Data[i] - '0';
// Mutate the integer value.
switch(Rand(5)) {
case 0: Val++; break;
case 1: Val--; break;
case 2: Val /= 2; break;
case 3: Val *= 2; break;
case 4: Val = Rand(Val * Val); break;
default: assert(0);
}
// Just replace the bytes with the new ones, don't bother moving bytes.
for (size_t i = B; i < E; i++) {
size_t Idx = E + B - i - 1;
assert(Idx >= B && Idx < E);
Data[Idx] = (Val % 10) + '0';
Val /= 10;
}
return Size;
}
template<class T>
size_t ChangeBinaryInteger(uint8_t *Data, size_t Size, Random &Rand) {
if (Size < sizeof(T)) return 0;
size_t Off = Rand(Size - sizeof(T) + 1);
assert(Off + sizeof(T) <= Size);
T Val;
if (Off < 64 && !Rand(4)) {
Val = Size;
if (Rand.RandBool())
Val = Bswap(Val);
} else {
memcpy(&Val, Data + Off, sizeof(Val));
T Add = Rand(21);
Add -= 10;
if (Rand.RandBool())
Val = Bswap(T(Bswap(Val) + Add)); // Add assuming different endiannes.
else
Val = Val + Add; // Add assuming current endiannes.
if (Add == 0 || Rand.RandBool()) // Maybe negate.
Val = -Val;
}
memcpy(Data + Off, &Val, sizeof(Val));
return Size;
}
size_t MutationDispatcher::Mutate_ChangeBinaryInteger(uint8_t *Data,
size_t Size,
size_t MaxSize) {
if (Size > MaxSize) return 0;
switch (Rand(4)) {
case 3: return ChangeBinaryInteger<uint64_t>(Data, Size, Rand);
case 2: return ChangeBinaryInteger<uint32_t>(Data, Size, Rand);
case 1: return ChangeBinaryInteger<uint16_t>(Data, Size, Rand);
case 0: return ChangeBinaryInteger<uint8_t>(Data, Size, Rand);
default: assert(0);
}
return 0;
}
size_t MutationDispatcher::Mutate_CrossOver(uint8_t *Data, size_t Size,
size_t MaxSize) {
if (Size > MaxSize) return 0;
if (!Corpus || Corpus->size() < 2 || Size == 0) return 0;
size_t Idx = Rand(Corpus->size());
const Unit &O = (*Corpus)[Idx];
if (O.empty()) return 0;
MutateInPlaceHere.resize(MaxSize);
auto &U = MutateInPlaceHere;
size_t NewSize = 0;
switch(Rand(3)) {
case 0:
NewSize = CrossOver(Data, Size, O.data(), O.size(), U.data(), U.size());
break;
case 1:
NewSize = InsertPartOf(O.data(), O.size(), U.data(), U.size(), MaxSize);
if (NewSize)
break;
// LLVM_FALLTHROUGH;
case 2:
NewSize = CopyPartOf(O.data(), O.size(), U.data(), U.size());
break;
default: assert(0);
}
assert(NewSize > 0 && "CrossOver returned empty unit");
assert(NewSize <= MaxSize && "CrossOver returned overisized unit");
memcpy(Data, U.data(), NewSize);
return NewSize;
}
void MutationDispatcher::StartMutationSequence() {
CurrentMutatorSequence.clear();
CurrentDictionaryEntrySequence.clear();
}
// Copy successful dictionary entries to PersistentAutoDictionary.
void MutationDispatcher::RecordSuccessfulMutationSequence() {
for (auto DE : CurrentDictionaryEntrySequence) {
// PersistentAutoDictionary.AddWithSuccessCountOne(DE);
DE->IncSuccessCount();
// Linear search is fine here as this happens seldom.
if (!PersistentAutoDictionary.ContainsWord(DE->GetW()))
PersistentAutoDictionary.push_back({DE->GetW(), 1});
}
}
void MutationDispatcher::PrintRecommendedDictionary() {
std::vector<DictionaryEntry> V;
for (auto &DE : PersistentAutoDictionary)
if (!ManualDictionary.ContainsWord(DE.GetW()))
V.push_back(DE);
if (V.empty()) return;
Printf("###### Recommended dictionary. ######\n");
for (auto &DE: V) {
Printf("\"");
PrintASCII(DE.GetW(), "\"");
Printf(" # Uses: %zd\n", DE.GetUseCount());
}
Printf("###### End of recommended dictionary. ######\n");
}
void MutationDispatcher::PrintMutationSequence() {
Printf("MS: %zd ", CurrentMutatorSequence.size());
for (auto M : CurrentMutatorSequence)
Printf("%s-", M.Name);
if (!CurrentDictionaryEntrySequence.empty()) {
Printf(" DE: ");
for (auto DE : CurrentDictionaryEntrySequence) {
Printf("\"");
PrintASCII(DE->GetW(), "\"-");
}
}
}
size_t MutationDispatcher::Mutate(uint8_t *Data, size_t Size, size_t MaxSize) {
return MutateImpl(Data, Size, MaxSize, Mutators);
}
size_t MutationDispatcher::DefaultMutate(uint8_t *Data, size_t Size,
size_t MaxSize) {
return MutateImpl(Data, Size, MaxSize, DefaultMutators);
}
// Mutates Data in place, returns new size.
size_t MutationDispatcher::MutateImpl(uint8_t *Data, size_t Size,
size_t MaxSize,
const std::vector<Mutator> &Mutators) {
assert(MaxSize > 0);
if (Size == 0) {
for (size_t i = 0; i < MaxSize; i++)
Data[i] = RandCh(Rand);
if (Options.OnlyASCII)
ToASCII(Data, MaxSize);
return MaxSize;
}
assert(Size > 0);
// Some mutations may fail (e.g. can't insert more bytes if Size == MaxSize),
// in which case they will return 0.
// Try several times before returning un-mutated data.
for (int Iter = 0; Iter < 100; Iter++) {
auto M = Mutators[Rand(Mutators.size())];
size_t NewSize = (this->*(M.Fn))(Data, Size, MaxSize);
if (NewSize && NewSize <= MaxSize) {
if (Options.OnlyASCII)
ToASCII(Data, NewSize);
CurrentMutatorSequence.push_back(M);
return NewSize;
}
}
return std::min(Size, MaxSize);
}
void MutationDispatcher::AddWordToManualDictionary(const Word &W) {
ManualDictionary.push_back(
{W, std::numeric_limits<size_t>::max()});
}
void MutationDispatcher::AddWordToAutoDictionary(DictionaryEntry DE) {
static const size_t kMaxAutoDictSize = 1 << 14;
if (TempAutoDictionary.size() >= kMaxAutoDictSize) return;
TempAutoDictionary.push_back(DE);
}
void MutationDispatcher::ClearAutoDictionary() {
TempAutoDictionary.clear();
}
} // namespace fuzzer
+145
View File
@@ -0,0 +1,145 @@
//===- FuzzerMutate.h - Internal header for the Fuzzer ----------*- C++ -* ===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// fuzzer::MutationDispatcher
//===----------------------------------------------------------------------===//
#ifndef LLVM_FUZZER_MUTATE_H
#define LLVM_FUZZER_MUTATE_H
#include "FuzzerDefs.h"
#include "FuzzerDictionary.h"
#include "FuzzerRandom.h"
namespace fuzzer {
class MutationDispatcher {
public:
MutationDispatcher(Random &Rand, const FuzzingOptions &Options);
~MutationDispatcher() {}
/// Indicate that we are about to start a new sequence of mutations.
void StartMutationSequence();
/// Print the current sequence of mutations.
void PrintMutationSequence();
/// Indicate that the current sequence of mutations was successfull.
void RecordSuccessfulMutationSequence();
/// Mutates data by invoking user-provided mutator.
size_t Mutate_Custom(uint8_t *Data, size_t Size, size_t MaxSize);
/// Mutates data by invoking user-provided crossover.
size_t Mutate_CustomCrossOver(uint8_t *Data, size_t Size, size_t MaxSize);
/// Mutates data by shuffling bytes.
size_t Mutate_ShuffleBytes(uint8_t *Data, size_t Size, size_t MaxSize);
/// Mutates data by erasing bytes.
size_t Mutate_EraseBytes(uint8_t *Data, size_t Size, size_t MaxSize);
/// Mutates data by inserting a byte.
size_t Mutate_InsertByte(uint8_t *Data, size_t Size, size_t MaxSize);
/// Mutates data by inserting several repeated bytes.
size_t Mutate_InsertRepeatedBytes(uint8_t *Data, size_t Size, size_t MaxSize);
/// Mutates data by chanding one byte.
size_t Mutate_ChangeByte(uint8_t *Data, size_t Size, size_t MaxSize);
/// Mutates data by chanding one bit.
size_t Mutate_ChangeBit(uint8_t *Data, size_t Size, size_t MaxSize);
/// Mutates data by copying/inserting a part of data into a different place.
size_t Mutate_CopyPart(uint8_t *Data, size_t Size, size_t MaxSize);
/// Mutates data by adding a word from the manual dictionary.
size_t Mutate_AddWordFromManualDictionary(uint8_t *Data, size_t Size,
size_t MaxSize);
/// Mutates data by adding a word from the temporary automatic dictionary.
size_t Mutate_AddWordFromTemporaryAutoDictionary(uint8_t *Data, size_t Size,
size_t MaxSize);
/// Mutates data by adding a word from the TORC.
size_t Mutate_AddWordFromTORC(uint8_t *Data, size_t Size, size_t MaxSize);
/// Mutates data by adding a word from the persistent automatic dictionary.
size_t Mutate_AddWordFromPersistentAutoDictionary(uint8_t *Data, size_t Size,
size_t MaxSize);
/// Tries to find an ASCII integer in Data, changes it to another ASCII int.
size_t Mutate_ChangeASCIIInteger(uint8_t *Data, size_t Size, size_t MaxSize);
/// Change a 1-, 2-, 4-, or 8-byte integer in interesting ways.
size_t Mutate_ChangeBinaryInteger(uint8_t *Data, size_t Size, size_t MaxSize);
/// CrossOver Data with some other element of the corpus.
size_t Mutate_CrossOver(uint8_t *Data, size_t Size, size_t MaxSize);
/// Applies one of the configured mutations.
/// Returns the new size of data which could be up to MaxSize.
size_t Mutate(uint8_t *Data, size_t Size, size_t MaxSize);
/// Applies one of the default mutations. Provided as a service
/// to mutation authors.
size_t DefaultMutate(uint8_t *Data, size_t Size, size_t MaxSize);
/// Creates a cross-over of two pieces of Data, returns its size.
size_t CrossOver(const uint8_t *Data1, size_t Size1, const uint8_t *Data2,
size_t Size2, uint8_t *Out, size_t MaxOutSize);
void AddWordToManualDictionary(const Word &W);
void AddWordToAutoDictionary(DictionaryEntry DE);
void ClearAutoDictionary();
void PrintRecommendedDictionary();
void SetCorpus(const InputCorpus *Corpus) { this->Corpus = Corpus; }
Random &GetRand() { return Rand; }
private:
struct Mutator {
size_t (MutationDispatcher::*Fn)(uint8_t *Data, size_t Size, size_t Max);
const char *Name;
};
size_t AddWordFromDictionary(Dictionary &D, uint8_t *Data, size_t Size,
size_t MaxSize);
size_t MutateImpl(uint8_t *Data, size_t Size, size_t MaxSize,
const std::vector<Mutator> &Mutators);
size_t InsertPartOf(const uint8_t *From, size_t FromSize, uint8_t *To,
size_t ToSize, size_t MaxToSize);
size_t CopyPartOf(const uint8_t *From, size_t FromSize, uint8_t *To,
size_t ToSize);
size_t ApplyDictionaryEntry(uint8_t *Data, size_t Size, size_t MaxSize,
DictionaryEntry &DE);
template <class T>
DictionaryEntry MakeDictionaryEntryFromCMP(T Arg1, T Arg2,
const uint8_t *Data, size_t Size);
Random &Rand;
const FuzzingOptions &Options;
// Dictionary provided by the user via -dict=DICT_FILE.
Dictionary ManualDictionary;
// Temporary dictionary modified by the fuzzer itself,
// recreated periodically.
Dictionary TempAutoDictionary;
// Persistent dictionary modified by the fuzzer, consists of
// entries that led to successfull discoveries in the past mutations.
Dictionary PersistentAutoDictionary;
std::vector<Mutator> CurrentMutatorSequence;
std::vector<DictionaryEntry *> CurrentDictionaryEntrySequence;
static const size_t kCmpDictionaryEntriesDequeSize = 16;
DictionaryEntry CmpDictionaryEntriesDeque[kCmpDictionaryEntriesDequeSize];
size_t CmpDictionaryEntriesDequeIdx = 0;
const InputCorpus *Corpus = nullptr;
std::vector<uint8_t> MutateInPlaceHere;
std::vector<Mutator> Mutators;
std::vector<Mutator> DefaultMutators;
};
} // namespace fuzzer
#endif // LLVM_FUZZER_MUTATE_H
+68
View File
@@ -0,0 +1,68 @@
//===- FuzzerOptions.h - Internal header for the Fuzzer ---------*- C++ -* ===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// fuzzer::FuzzingOptions
//===----------------------------------------------------------------------===//
#ifndef LLVM_FUZZER_OPTIONS_H
#define LLVM_FUZZER_OPTIONS_H
#include "FuzzerDefs.h"
namespace fuzzer {
struct FuzzingOptions {
int Verbosity = 1;
size_t MaxLen = 0;
int UnitTimeoutSec = 300;
int TimeoutExitCode = 77;
int ErrorExitCode = 77;
int MaxTotalTimeSec = 0;
int RssLimitMb = 0;
bool DoCrossOver = true;
int MutateDepth = 5;
bool UseCounters = false;
bool UseIndirCalls = true;
bool UseMemcmp = true;
bool UseMemmem = true;
bool UseCmp = false;
bool UseValueProfile = false;
bool Shrink = false;
int ReloadIntervalSec = 1;
bool ShuffleAtStartUp = true;
bool PreferSmall = true;
size_t MaxNumberOfRuns = -1L;
int ReportSlowUnits = 10;
bool OnlyASCII = false;
std::string OutputCorpus;
std::string ArtifactPrefix = "./";
std::string ExactArtifactPath;
std::string ExitOnSrcPos;
std::string ExitOnItem;
bool SaveArtifacts = true;
bool PrintNEW = true; // Print a status line when new units are found;
bool OutputCSV = false;
bool PrintNewCovPcs = false;
bool PrintFinalStats = false;
bool PrintCorpusStats = false;
bool PrintCoverage = false;
bool DumpCoverage = false;
bool DetectLeaks = true;
int TraceMalloc = 0;
bool HandleAbrt = false;
bool HandleBus = false;
bool HandleFpe = false;
bool HandleIll = false;
bool HandleInt = false;
bool HandleSegv = false;
bool HandleTerm = false;
};
} // namespace fuzzer
#endif // LLVM_FUZZER_OPTIONS_H
+36
View File
@@ -0,0 +1,36 @@
//===- FuzzerRandom.h - Internal header for the Fuzzer ----------*- C++ -* ===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// fuzzer::Random
//===----------------------------------------------------------------------===//
#ifndef LLVM_FUZZER_RANDOM_H
#define LLVM_FUZZER_RANDOM_H
#include <random>
namespace fuzzer {
class Random {
public:
Random(unsigned int seed) : R(seed) {}
size_t Rand() { return R(); }
size_t RandBool() { return Rand() % 2; }
size_t operator()(size_t n) { return n ? Rand() % n : 0; }
intptr_t operator()(intptr_t From, intptr_t To) {
assert(From < To);
intptr_t RangeSize = To - From + 1;
return operator()(RangeSize) + From;
}
std::mt19937 &Get_mt19937() { return R; }
private:
std::mt19937 R;
};
} // namespace fuzzer
#endif // LLVM_FUZZER_RANDOM_H
+222
View File
@@ -0,0 +1,222 @@
//===- FuzzerSHA1.h - Private copy of the SHA1 implementation ---*- C++ -* ===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// This code is taken from public domain
// (http://oauth.googlecode.com/svn/code/c/liboauth/src/sha1.c)
// and modified by adding anonymous namespace, adding an interface
// function fuzzer::ComputeSHA1() and removing unnecessary code.
//
// lib/Fuzzer can not use SHA1 implementation from openssl because
// openssl may not be available and because we may be fuzzing openssl itself.
// For the same reason we do not want to depend on SHA1 from LLVM tree.
//===----------------------------------------------------------------------===//
#include "FuzzerSHA1.h"
#include "FuzzerDefs.h"
/* This code is public-domain - it is based on libcrypt
* placed in the public domain by Wei Dai and other contributors.
*/
#include <iomanip>
#include <sstream>
#include <stdint.h>
#include <string.h>
namespace { // Added for LibFuzzer
#ifdef __BIG_ENDIAN__
# define SHA_BIG_ENDIAN
#elif defined __LITTLE_ENDIAN__
/* override */
#elif defined __BYTE_ORDER
# if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
# define SHA_BIG_ENDIAN
# endif
#else // ! defined __LITTLE_ENDIAN__
# include <endian.h> // machine/endian.h
# if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
# define SHA_BIG_ENDIAN
# endif
#endif
/* header */
#define HASH_LENGTH 20
#define BLOCK_LENGTH 64
typedef struct sha1nfo {
uint32_t buffer[BLOCK_LENGTH/4];
uint32_t state[HASH_LENGTH/4];
uint32_t byteCount;
uint8_t bufferOffset;
uint8_t keyBuffer[BLOCK_LENGTH];
uint8_t innerHash[HASH_LENGTH];
} sha1nfo;
/* public API - prototypes - TODO: doxygen*/
/**
*/
void sha1_init(sha1nfo *s);
/**
*/
void sha1_writebyte(sha1nfo *s, uint8_t data);
/**
*/
void sha1_write(sha1nfo *s, const char *data, size_t len);
/**
*/
uint8_t* sha1_result(sha1nfo *s);
/* code */
#define SHA1_K0 0x5a827999
#define SHA1_K20 0x6ed9eba1
#define SHA1_K40 0x8f1bbcdc
#define SHA1_K60 0xca62c1d6
void sha1_init(sha1nfo *s) {
s->state[0] = 0x67452301;
s->state[1] = 0xefcdab89;
s->state[2] = 0x98badcfe;
s->state[3] = 0x10325476;
s->state[4] = 0xc3d2e1f0;
s->byteCount = 0;
s->bufferOffset = 0;
}
uint32_t sha1_rol32(uint32_t number, uint8_t bits) {
return ((number << bits) | (number >> (32-bits)));
}
void sha1_hashBlock(sha1nfo *s) {
uint8_t i;
uint32_t a,b,c,d,e,t;
a=s->state[0];
b=s->state[1];
c=s->state[2];
d=s->state[3];
e=s->state[4];
for (i=0; i<80; i++) {
if (i>=16) {
t = s->buffer[(i+13)&15] ^ s->buffer[(i+8)&15] ^ s->buffer[(i+2)&15] ^ s->buffer[i&15];
s->buffer[i&15] = sha1_rol32(t,1);
}
if (i<20) {
t = (d ^ (b & (c ^ d))) + SHA1_K0;
} else if (i<40) {
t = (b ^ c ^ d) + SHA1_K20;
} else if (i<60) {
t = ((b & c) | (d & (b | c))) + SHA1_K40;
} else {
t = (b ^ c ^ d) + SHA1_K60;
}
t+=sha1_rol32(a,5) + e + s->buffer[i&15];
e=d;
d=c;
c=sha1_rol32(b,30);
b=a;
a=t;
}
s->state[0] += a;
s->state[1] += b;
s->state[2] += c;
s->state[3] += d;
s->state[4] += e;
}
void sha1_addUncounted(sha1nfo *s, uint8_t data) {
uint8_t * const b = (uint8_t*) s->buffer;
#ifdef SHA_BIG_ENDIAN
b[s->bufferOffset] = data;
#else
b[s->bufferOffset ^ 3] = data;
#endif
s->bufferOffset++;
if (s->bufferOffset == BLOCK_LENGTH) {
sha1_hashBlock(s);
s->bufferOffset = 0;
}
}
void sha1_writebyte(sha1nfo *s, uint8_t data) {
++s->byteCount;
sha1_addUncounted(s, data);
}
void sha1_write(sha1nfo *s, const char *data, size_t len) {
for (;len--;) sha1_writebyte(s, (uint8_t) *data++);
}
void sha1_pad(sha1nfo *s) {
// Implement SHA-1 padding (fips180-2 §5.1.1)
// Pad with 0x80 followed by 0x00 until the end of the block
sha1_addUncounted(s, 0x80);
while (s->bufferOffset != 56) sha1_addUncounted(s, 0x00);
// Append length in the last 8 bytes
sha1_addUncounted(s, 0); // We're only using 32 bit lengths
sha1_addUncounted(s, 0); // But SHA-1 supports 64 bit lengths
sha1_addUncounted(s, 0); // So zero pad the top bits
sha1_addUncounted(s, s->byteCount >> 29); // Shifting to multiply by 8
sha1_addUncounted(s, s->byteCount >> 21); // as SHA-1 supports bitstreams as well as
sha1_addUncounted(s, s->byteCount >> 13); // byte.
sha1_addUncounted(s, s->byteCount >> 5);
sha1_addUncounted(s, s->byteCount << 3);
}
uint8_t* sha1_result(sha1nfo *s) {
// Pad to complete the last block
sha1_pad(s);
#ifndef SHA_BIG_ENDIAN
// Swap byte order back
int i;
for (i=0; i<5; i++) {
s->state[i]=
(((s->state[i])<<24)& 0xff000000)
| (((s->state[i])<<8) & 0x00ff0000)
| (((s->state[i])>>8) & 0x0000ff00)
| (((s->state[i])>>24)& 0x000000ff);
}
#endif
// Return pointer to hash (20 characters)
return (uint8_t*) s->state;
}
} // namespace; Added for LibFuzzer
namespace fuzzer {
// The rest is added for LibFuzzer
void ComputeSHA1(const uint8_t *Data, size_t Len, uint8_t *Out) {
sha1nfo s;
sha1_init(&s);
sha1_write(&s, (const char*)Data, Len);
memcpy(Out, sha1_result(&s), HASH_LENGTH);
}
std::string Sha1ToString(const uint8_t Sha1[kSHA1NumBytes]) {
std::stringstream SS;
for (int i = 0; i < kSHA1NumBytes; i++)
SS << std::hex << std::setfill('0') << std::setw(2) << (unsigned)Sha1[i];
return SS.str();
}
std::string Hash(const Unit &U) {
uint8_t Hash[kSHA1NumBytes];
ComputeSHA1(U.data(), U.size(), Hash);
return Sha1ToString(Hash);
}
}
+33
View File
@@ -0,0 +1,33 @@
//===- FuzzerSHA1.h - Internal header for the SHA1 utils --------*- C++ -* ===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// SHA1 utils.
//===----------------------------------------------------------------------===//
#ifndef LLVM_FUZZER_SHA1_H
#define LLVM_FUZZER_SHA1_H
#include "FuzzerDefs.h"
#include <cstddef>
#include <stdint.h>
namespace fuzzer {
// Private copy of SHA1 implementation.
static const int kSHA1NumBytes = 20;
// Computes SHA1 hash of 'Len' bytes in 'Data', writes kSHA1NumBytes to 'Out'.
void ComputeSHA1(const uint8_t *Data, size_t Len, uint8_t *Out);
std::string Sha1ToString(const uint8_t Sha1[kSHA1NumBytes]);
std::string Hash(const Unit &U);
} // namespace fuzzer
#endif // LLVM_FUZZER_SHA1_H
+339
View File
@@ -0,0 +1,339 @@
//===- FuzzerTracePC.cpp - PC tracing--------------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// Trace PCs.
// This module implements __sanitizer_cov_trace_pc_guard[_init],
// the callback required for -fsanitize-coverage=trace-pc-guard instrumentation.
//
//===----------------------------------------------------------------------===//
#include "FuzzerCorpus.h"
#include "FuzzerDefs.h"
#include "FuzzerDictionary.h"
#include "FuzzerExtFunctions.h"
#include "FuzzerIO.h"
#include "FuzzerTracePC.h"
#include "FuzzerValueBitMap.h"
#include <map>
#include <sanitizer/coverage_interface.h>
#include <set>
#include <sstream>
namespace fuzzer {
TracePC TPC;
void TracePC::HandleTrace(uint32_t *Guard, uintptr_t PC) {
uint32_t Idx = *Guard;
if (!Idx) return;
PCs[Idx % kNumPCs] = PC;
Counters[Idx % kNumCounters]++;
}
size_t TracePC::GetTotalPCCoverage() {
size_t Res = 0;
for (size_t i = 1; i < GetNumPCs(); i++)
if (PCs[i])
Res++;
return Res;
}
void TracePC::HandleInit(uint32_t *Start, uint32_t *Stop) {
if (Start == Stop || *Start) return;
assert(NumModules < sizeof(Modules) / sizeof(Modules[0]));
for (uint32_t *P = Start; P < Stop; P++)
*P = ++NumGuards;
Modules[NumModules].Start = Start;
Modules[NumModules].Stop = Stop;
NumModules++;
}
void TracePC::PrintModuleInfo() {
Printf("INFO: Loaded %zd modules (%zd guards): ", NumModules, NumGuards);
for (size_t i = 0; i < NumModules; i++)
Printf("[%p, %p), ", Modules[i].Start, Modules[i].Stop);
Printf("\n");
}
void TracePC::HandleCallerCallee(uintptr_t Caller, uintptr_t Callee) {
const uintptr_t kBits = 12;
const uintptr_t kMask = (1 << kBits) - 1;
uintptr_t Idx = (Caller & kMask) | ((Callee & kMask) << kBits);
HandleValueProfile(Idx);
}
static bool IsInterestingCoverageFile(std::string &File) {
if (File.find("compiler-rt/lib/") != std::string::npos)
return false; // sanitizer internal.
if (File.find("/usr/lib/") != std::string::npos)
return false;
if (File.find("/usr/include/") != std::string::npos)
return false;
if (File == "<null>")
return false;
return true;
}
void TracePC::PrintNewPCs() {
if (DoPrintNewPCs) {
if (!PrintedPCs)
PrintedPCs = new std::set<uintptr_t>;
for (size_t i = 1; i < GetNumPCs(); i++)
if (PCs[i] && PrintedPCs->insert(PCs[i]).second)
PrintPC("\tNEW_PC: %p %F %L\n", "\tNEW_PC: %p\n", PCs[i]);
}
}
void TracePC::PrintCoverage() {
if (!EF->__sanitizer_symbolize_pc ||
!EF->__sanitizer_get_module_and_offset_for_pc) {
Printf("INFO: __sanitizer_symbolize_pc or "
"__sanitizer_get_module_and_offset_for_pc is not available,"
" not printing coverage\n");
return;
}
std::map<std::string, std::vector<uintptr_t>> CoveredPCsPerModule;
std::map<std::string, uintptr_t> ModuleOffsets;
std::set<std::string> CoveredDirs, CoveredFiles, CoveredFunctions,
CoveredLines;
Printf("COVERAGE:\n");
for (size_t i = 1; i < GetNumPCs(); i++) {
if (!PCs[i]) continue;
std::string FileStr = DescribePC("%s", PCs[i]);
if (!IsInterestingCoverageFile(FileStr)) continue;
std::string FixedPCStr = DescribePC("%p", PCs[i]);
std::string FunctionStr = DescribePC("%F", PCs[i]);
std::string LineStr = DescribePC("%l", PCs[i]);
char ModulePathRaw[4096] = ""; // What's PATH_MAX in portable C++?
void *OffsetRaw = nullptr;
if (!EF->__sanitizer_get_module_and_offset_for_pc(
reinterpret_cast<void *>(PCs[i]), ModulePathRaw,
sizeof(ModulePathRaw), &OffsetRaw))
continue;
std::string Module = ModulePathRaw;
uintptr_t FixedPC = std::stol(FixedPCStr, 0, 16);
uintptr_t PcOffset = reinterpret_cast<uintptr_t>(OffsetRaw);
ModuleOffsets[Module] = FixedPC - PcOffset;
CoveredPCsPerModule[Module].push_back(PcOffset);
CoveredFunctions.insert(FunctionStr);
CoveredFiles.insert(FileStr);
CoveredDirs.insert(DirName(FileStr));
if (!CoveredLines.insert(FileStr + ":" + LineStr).second)
continue;
Printf("COVERED: %s %s:%s\n", FunctionStr.c_str(),
FileStr.c_str(), LineStr.c_str());
}
std::string CoveredDirsStr;
for (auto &Dir : CoveredDirs) {
if (!CoveredDirsStr.empty())
CoveredDirsStr += ",";
CoveredDirsStr += Dir;
}
Printf("COVERED_DIRS: %s\n", CoveredDirsStr.c_str());
for (auto &M : CoveredPCsPerModule) {
std::set<std::string> UncoveredFiles, UncoveredFunctions;
std::map<std::string, std::set<int> > UncoveredLines; // Func+File => lines
auto &ModuleName = M.first;
auto &CoveredOffsets = M.second;
uintptr_t ModuleOffset = ModuleOffsets[ModuleName];
std::sort(CoveredOffsets.begin(), CoveredOffsets.end());
Printf("MODULE_WITH_COVERAGE: %s\n", ModuleName.c_str());
// sancov does not yet fully support DSOs.
// std::string Cmd = "sancov -print-coverage-pcs " + ModuleName;
std::string Cmd = "objdump -d " + ModuleName +
" | grep 'call.*__sanitizer_cov_trace_pc_guard' | awk -F: '{print $1}'";
std::string SanCovOutput;
if (!ExecuteCommandAndReadOutput(Cmd, &SanCovOutput)) {
Printf("INFO: Command failed: %s\n", Cmd.c_str());
continue;
}
std::istringstream ISS(SanCovOutput);
std::string S;
while (std::getline(ISS, S, '\n')) {
uintptr_t PcOffset = std::stol(S, 0, 16);
if (!std::binary_search(CoveredOffsets.begin(), CoveredOffsets.end(),
PcOffset)) {
uintptr_t PC = ModuleOffset + PcOffset;
auto FileStr = DescribePC("%s", PC);
if (!IsInterestingCoverageFile(FileStr)) continue;
if (CoveredFiles.count(FileStr) == 0) {
UncoveredFiles.insert(FileStr);
continue;
}
auto FunctionStr = DescribePC("%F", PC);
if (CoveredFunctions.count(FunctionStr) == 0) {
UncoveredFunctions.insert(FunctionStr);
continue;
}
std::string LineStr = DescribePC("%l", PC);
uintptr_t Line = std::stoi(LineStr);
std::string FileLineStr = FileStr + ":" + LineStr;
if (CoveredLines.count(FileLineStr) == 0)
UncoveredLines[FunctionStr + " " + FileStr].insert(Line);
}
}
for (auto &FileLine: UncoveredLines)
for (int Line : FileLine.second)
Printf("UNCOVERED_LINE: %s:%d\n", FileLine.first.c_str(), Line);
for (auto &Func : UncoveredFunctions)
Printf("UNCOVERED_FUNC: %s\n", Func.c_str());
for (auto &File : UncoveredFiles)
Printf("UNCOVERED_FILE: %s\n", File.c_str());
}
}
void TracePC::DumpCoverage() {
__sanitizer_dump_coverage(PCs, GetNumPCs());
}
// Value profile.
// We keep track of various values that affect control flow.
// These values are inserted into a bit-set-based hash map.
// Every new bit in the map is treated as a new coverage.
//
// For memcmp/strcmp/etc the interesting value is the length of the common
// prefix of the parameters.
// For cmp instructions the interesting value is a XOR of the parameters.
// The interesting value is mixed up with the PC and is then added to the map.
ATTRIBUTE_NO_SANITIZE_MEMORY
void TracePC::AddValueForMemcmp(void *caller_pc, const void *s1, const void *s2,
size_t n) {
if (!n) return;
size_t Len = std::min(n, (size_t)32);
const uint8_t *A1 = reinterpret_cast<const uint8_t *>(s1);
const uint8_t *A2 = reinterpret_cast<const uint8_t *>(s2);
size_t I = 0;
for (; I < Len; I++)
if (A1[I] != A2[I])
break;
size_t PC = reinterpret_cast<size_t>(caller_pc);
size_t Idx = I;
// if (I < Len)
// Idx += __builtin_popcountl((A1[I] ^ A2[I])) - 1;
TPC.HandleValueProfile((PC & 4095) | (Idx << 12));
}
ATTRIBUTE_NO_SANITIZE_MEMORY
void TracePC::AddValueForStrcmp(void *caller_pc, const char *s1, const char *s2,
size_t n) {
if (!n) return;
size_t Len = std::min(n, (size_t)32);
const uint8_t *A1 = reinterpret_cast<const uint8_t *>(s1);
const uint8_t *A2 = reinterpret_cast<const uint8_t *>(s2);
size_t I = 0;
for (; I < Len; I++)
if (A1[I] != A2[I] || A1[I] == 0)
break;
size_t PC = reinterpret_cast<size_t>(caller_pc);
size_t Idx = I;
// if (I < Len && A1[I])
// Idx += __builtin_popcountl((A1[I] ^ A2[I])) - 1;
TPC.HandleValueProfile((PC & 4095) | (Idx << 12));
}
template <class T>
ATTRIBUTE_TARGET_POPCNT
#ifdef __clang__ // g++ can't handle this __attribute__ here :(
__attribute__((always_inline))
#endif // __clang__
void TracePC::HandleCmp(void *PC, T Arg1, T Arg2) {
uintptr_t PCuint = reinterpret_cast<uintptr_t>(PC);
uint64_t ArgXor = Arg1 ^ Arg2;
uint64_t ArgDistance = __builtin_popcountl(ArgXor) + 1; // [1,65]
uintptr_t Idx = ((PCuint & 4095) + 1) * ArgDistance;
if (sizeof(T) == 4)
TORC4.Insert(ArgXor, Arg1, Arg2);
else if (sizeof(T) == 8)
TORC8.Insert(ArgXor, Arg1, Arg2);
HandleValueProfile(Idx);
}
} // namespace fuzzer
extern "C" {
__attribute__((visibility("default")))
void __sanitizer_cov_trace_pc_guard(uint32_t *Guard) {
uintptr_t PC = (uintptr_t)__builtin_return_address(0);
fuzzer::TPC.HandleTrace(Guard, PC);
}
__attribute__((visibility("default")))
void __sanitizer_cov_trace_pc_guard_init(uint32_t *Start, uint32_t *Stop) {
fuzzer::TPC.HandleInit(Start, Stop);
}
__attribute__((visibility("default")))
void __sanitizer_cov_trace_pc_indir(uintptr_t Callee) {
uintptr_t PC = (uintptr_t)__builtin_return_address(0);
fuzzer::TPC.HandleCallerCallee(PC, Callee);
}
__attribute__((visibility("default")))
void __sanitizer_cov_trace_cmp8(uint64_t Arg1, uint64_t Arg2) {
fuzzer::TPC.HandleCmp(__builtin_return_address(0), Arg1, Arg2);
}
__attribute__((visibility("default")))
void __sanitizer_cov_trace_cmp4(uint32_t Arg1, uint32_t Arg2) {
fuzzer::TPC.HandleCmp(__builtin_return_address(0), Arg1, Arg2);
}
__attribute__((visibility("default")))
void __sanitizer_cov_trace_cmp2(uint16_t Arg1, uint16_t Arg2) {
fuzzer::TPC.HandleCmp(__builtin_return_address(0), Arg1, Arg2);
}
__attribute__((visibility("default")))
void __sanitizer_cov_trace_cmp1(uint8_t Arg1, uint8_t Arg2) {
fuzzer::TPC.HandleCmp(__builtin_return_address(0), Arg1, Arg2);
}
__attribute__((visibility("default")))
void __sanitizer_cov_trace_switch(uint64_t Val, uint64_t *Cases) {
// Updates the value profile based on the relative position of Val and Cases.
// We want to handle one random case at every call (handling all is slow).
// Since none of the arguments contain any random bits we use a thread-local
// counter to choose the random case to handle.
static thread_local size_t Counter;
Counter++;
uint64_t N = Cases[0];
uint64_t *Vals = Cases + 2;
char *PC = (char*)__builtin_return_address(0);
// We need a random number < N using Counter as a seed. But w/o DIV.
// * find a power of two >= N
// * mask Counter with this power of two.
// * maybe subtract N.
size_t Nlog = sizeof(long) * 8 - __builtin_clzl((long)N);
size_t PowerOfTwoGeN = 1U << Nlog;
assert(PowerOfTwoGeN >= N);
size_t Idx = Counter & (PowerOfTwoGeN - 1);
if (Idx >= N)
Idx -= N;
assert(Idx < N);
uint64_t TwoIn32 = 1ULL << 32;
if ((Val | Vals[Idx]) < TwoIn32)
fuzzer::TPC.HandleCmp(PC + Idx, static_cast<uint32_t>(Val),
static_cast<uint32_t>(Vals[Idx]));
else
fuzzer::TPC.HandleCmp(PC + Idx, Val, Vals[Idx]);
}
__attribute__((visibility("default")))
void __sanitizer_cov_trace_div4(uint32_t Val) {
fuzzer::TPC.HandleCmp(__builtin_return_address(0), Val, (uint32_t)0);
}
__attribute__((visibility("default")))
void __sanitizer_cov_trace_div8(uint64_t Val) {
fuzzer::TPC.HandleCmp(__builtin_return_address(0), Val, (uint64_t)0);
}
__attribute__((visibility("default")))
void __sanitizer_cov_trace_gep(uintptr_t Idx) {
fuzzer::TPC.HandleCmp(__builtin_return_address(0), Idx, (uintptr_t)0);
}
} // extern "C"
+158
View File
@@ -0,0 +1,158 @@
//===- FuzzerTracePC.h - Internal header for the Fuzzer ---------*- C++ -* ===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// fuzzer::TracePC
//===----------------------------------------------------------------------===//
#ifndef LLVM_FUZZER_TRACE_PC
#define LLVM_FUZZER_TRACE_PC
#include "FuzzerDefs.h"
#include "FuzzerValueBitMap.h"
#include <set>
namespace fuzzer {
// TableOfRecentCompares (TORC) remembers the most recently performed
// comparisons of type T.
// We record the arguments of CMP instructions in this table unconditionally
// because it seems cheaper this way than to compute some expensive
// conditions inside __sanitizer_cov_trace_cmp*.
// After the unit has been executed we may decide to use the contents of
// this table to populate a Dictionary.
template<class T, size_t kSizeT>
struct TableOfRecentCompares {
static const size_t kSize = kSizeT;
struct Pair {
T A, B;
};
void Insert(size_t Idx, T Arg1, T Arg2) {
Idx = Idx % kSize;
Table[Idx].A = Arg1;
Table[Idx].B = Arg2;
}
Pair Get(size_t I) { return Table[I % kSize]; }
Pair Table[kSize];
};
class TracePC {
public:
static const size_t kFeatureSetSize = ValueBitMap::kNumberOfItems;
void HandleTrace(uint32_t *guard, uintptr_t PC);
void HandleInit(uint32_t *start, uint32_t *stop);
void HandleCallerCallee(uintptr_t Caller, uintptr_t Callee);
void HandleValueProfile(size_t Value) { ValueProfileMap.AddValue(Value); }
template <class T> void HandleCmp(void *PC, T Arg1, T Arg2);
size_t GetTotalPCCoverage();
void SetUseCounters(bool UC) { UseCounters = UC; }
void SetUseValueProfile(bool VP) { UseValueProfile = VP; }
void SetPrintNewPCs(bool P) { DoPrintNewPCs = P; }
template <class Callback> size_t CollectFeatures(Callback CB);
bool UpdateValueProfileMap(ValueBitMap *MaxValueProfileMap) {
return UseValueProfile && MaxValueProfileMap->MergeFrom(ValueProfileMap);
}
void ResetMaps() {
ValueProfileMap.Reset();
memset(Counters, 0, sizeof(Counters));
}
void UpdateFeatureSet(size_t CurrentElementIdx, size_t CurrentElementSize);
void PrintFeatureSet();
void PrintModuleInfo();
void PrintCoverage();
void DumpCoverage();
void AddValueForMemcmp(void *caller_pc, const void *s1, const void *s2,
size_t n);
void AddValueForStrcmp(void *caller_pc, const char *s1, const char *s2,
size_t n);
bool UsingTracePcGuard() const {return NumModules; }
static const size_t kTORCSize = 1 << 5;
TableOfRecentCompares<uint32_t, kTORCSize> TORC4;
TableOfRecentCompares<uint64_t, kTORCSize> TORC8;
void PrintNewPCs();
size_t GetNumPCs() const { return Min(kNumPCs, NumGuards + 1); }
uintptr_t GetPC(size_t Idx) {
assert(Idx < GetNumPCs());
return PCs[Idx];
}
private:
bool UseCounters = false;
bool UseValueProfile = false;
bool DoPrintNewPCs = false;
struct Module {
uint32_t *Start, *Stop;
};
Module Modules[4096];
size_t NumModules; // linker-initialized.
size_t NumGuards; // linker-initialized.
static const size_t kNumCounters = 1 << 14;
alignas(8) uint8_t Counters[kNumCounters];
static const size_t kNumPCs = 1 << 24;
uintptr_t PCs[kNumPCs];
std::set<uintptr_t> *PrintedPCs;
ValueBitMap ValueProfileMap;
};
template <class Callback>
size_t TracePC::CollectFeatures(Callback CB) {
if (!UsingTracePcGuard()) return 0;
size_t Res = 0;
const size_t Step = 8;
assert(reinterpret_cast<uintptr_t>(Counters) % Step == 0);
size_t N = Min(kNumCounters, NumGuards + 1);
N = (N + Step - 1) & ~(Step - 1); // Round up.
for (size_t Idx = 0; Idx < N; Idx += Step) {
uint64_t Bundle = *reinterpret_cast<uint64_t*>(&Counters[Idx]);
if (!Bundle) continue;
for (size_t i = Idx; i < Idx + Step; i++) {
uint8_t Counter = (Bundle >> ((i - Idx) * 8)) & 0xff;
if (!Counter) continue;
Counters[i] = 0;
unsigned Bit = 0;
/**/ if (Counter >= 128) Bit = 7;
else if (Counter >= 32) Bit = 6;
else if (Counter >= 16) Bit = 5;
else if (Counter >= 8) Bit = 4;
else if (Counter >= 4) Bit = 3;
else if (Counter >= 3) Bit = 2;
else if (Counter >= 2) Bit = 1;
size_t Feature = (i * 8 + Bit);
if (CB(Feature))
Res++;
}
}
if (UseValueProfile)
ValueProfileMap.ForEach([&](size_t Idx) {
if (CB(NumGuards * 8 + Idx))
Res++;
});
return Res;
}
extern TracePC TPC;
} // namespace fuzzer
#endif // LLVM_FUZZER_TRACE_PC
+325
View File
@@ -0,0 +1,325 @@
//===- FuzzerTraceState.cpp - Trace-based fuzzer mutator ------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// Data tracing.
//===----------------------------------------------------------------------===//
#include "FuzzerDictionary.h"
#include "FuzzerInternal.h"
#include "FuzzerIO.h"
#include "FuzzerMutate.h"
#include "FuzzerRandom.h"
#include "FuzzerTracePC.h"
#include <algorithm>
#include <cstring>
#include <map>
#include <set>
#include <thread>
namespace fuzzer {
// For now, very simple: put Size bytes of Data at position Pos.
struct TraceBasedMutation {
uint32_t Pos;
Word W;
};
// Declared as static globals for faster checks inside the hooks.
static bool RecordingMemcmp = false;
static bool RecordingMemmem = false;
static bool DoingMyOwnMemmem = false;
ScopedDoingMyOwnMemmem::ScopedDoingMyOwnMemmem() { DoingMyOwnMemmem = true; }
ScopedDoingMyOwnMemmem::~ScopedDoingMyOwnMemmem() { DoingMyOwnMemmem = false; }
class TraceState {
public:
TraceState(MutationDispatcher &MD, const FuzzingOptions &Options,
const Fuzzer *F)
: MD(MD), Options(Options), F(F) {}
void TraceMemcmpCallback(size_t CmpSize, const uint8_t *Data1,
const uint8_t *Data2);
void TraceSwitchCallback(uintptr_t PC, size_t ValSizeInBits, uint64_t Val,
size_t NumCases, uint64_t *Cases);
int TryToAddDesiredData(uint64_t PresentData, uint64_t DesiredData,
size_t DataSize);
int TryToAddDesiredData(const uint8_t *PresentData,
const uint8_t *DesiredData, size_t DataSize);
void StartTraceRecording() {
if (!Options.UseMemcmp)
return;
RecordingMemcmp = Options.UseMemcmp;
RecordingMemmem = Options.UseMemmem;
NumMutations = 0;
InterestingWords.clear();
MD.ClearAutoDictionary();
}
void StopTraceRecording() {
if (!RecordingMemcmp)
return;
RecordingMemcmp = false;
for (size_t i = 0; i < NumMutations; i++) {
auto &M = Mutations[i];
if (Options.Verbosity >= 2) {
AutoDictUnitCounts[M.W]++;
AutoDictAdds++;
if ((AutoDictAdds & (AutoDictAdds - 1)) == 0) {
typedef std::pair<size_t, Word> CU;
std::vector<CU> CountedUnits;
for (auto &I : AutoDictUnitCounts)
CountedUnits.push_back(std::make_pair(I.second, I.first));
std::sort(CountedUnits.begin(), CountedUnits.end(),
[](const CU &a, const CU &b) { return a.first > b.first; });
Printf("AutoDict:\n");
for (auto &I : CountedUnits) {
Printf(" %zd ", I.first);
PrintASCII(I.second.data(), I.second.size());
Printf("\n");
}
}
}
MD.AddWordToAutoDictionary({M.W, M.Pos});
}
for (auto &W : InterestingWords)
MD.AddWordToAutoDictionary({W});
}
void AddMutation(uint32_t Pos, uint32_t Size, const uint8_t *Data) {
if (NumMutations >= kMaxMutations) return;
auto &M = Mutations[NumMutations++];
M.Pos = Pos;
M.W.Set(Data, Size);
}
void AddMutation(uint32_t Pos, uint32_t Size, uint64_t Data) {
assert(Size <= sizeof(Data));
AddMutation(Pos, Size, reinterpret_cast<uint8_t*>(&Data));
}
void AddInterestingWord(const uint8_t *Data, size_t Size) {
if (!RecordingMemmem || !F->InFuzzingThread()) return;
if (Size <= 1) return;
Size = std::min(Size, Word::GetMaxSize());
Word W(Data, Size);
InterestingWords.insert(W);
}
private:
bool IsTwoByteData(uint64_t Data) {
int64_t Signed = static_cast<int64_t>(Data);
Signed >>= 16;
return Signed == 0 || Signed == -1L;
}
// We don't want to create too many trace-based mutations as it is both
// expensive and useless. So after some number of mutations is collected,
// start rejecting some of them. The more there are mutations the more we
// reject.
bool WantToHandleOneMoreMutation() {
const size_t FirstN = 64;
// Gladly handle first N mutations.
if (NumMutations <= FirstN) return true;
size_t Diff = NumMutations - FirstN;
size_t DiffLog = sizeof(long) * 8 - __builtin_clzl((long)Diff);
assert(DiffLog > 0 && DiffLog < 64);
bool WantThisOne = MD.GetRand()(1 << DiffLog) == 0; // 1 out of DiffLog.
return WantThisOne;
}
static const size_t kMaxMutations = 1 << 16;
size_t NumMutations;
TraceBasedMutation Mutations[kMaxMutations];
// TODO: std::set is too inefficient, need to have a custom DS here.
std::set<Word> InterestingWords;
MutationDispatcher &MD;
const FuzzingOptions Options;
const Fuzzer *F;
std::map<Word, size_t> AutoDictUnitCounts;
size_t AutoDictAdds = 0;
};
int TraceState::TryToAddDesiredData(uint64_t PresentData, uint64_t DesiredData,
size_t DataSize) {
if (NumMutations >= kMaxMutations || !WantToHandleOneMoreMutation()) return 0;
ScopedDoingMyOwnMemmem scoped_doing_my_own_memmem;
const uint8_t *UnitData;
auto UnitSize = F->GetCurrentUnitInFuzzingThead(&UnitData);
int Res = 0;
const uint8_t *Beg = UnitData;
const uint8_t *End = Beg + UnitSize;
for (const uint8_t *Cur = Beg; Cur < End; Cur++) {
Cur = (uint8_t *)SearchMemory(Cur, End - Cur, &PresentData, DataSize);
if (!Cur)
break;
size_t Pos = Cur - Beg;
assert(Pos < UnitSize);
AddMutation(Pos, DataSize, DesiredData);
AddMutation(Pos, DataSize, DesiredData + 1);
AddMutation(Pos, DataSize, DesiredData - 1);
Res++;
}
return Res;
}
int TraceState::TryToAddDesiredData(const uint8_t *PresentData,
const uint8_t *DesiredData,
size_t DataSize) {
if (NumMutations >= kMaxMutations || !WantToHandleOneMoreMutation()) return 0;
ScopedDoingMyOwnMemmem scoped_doing_my_own_memmem;
const uint8_t *UnitData;
auto UnitSize = F->GetCurrentUnitInFuzzingThead(&UnitData);
int Res = 0;
const uint8_t *Beg = UnitData;
const uint8_t *End = Beg + UnitSize;
for (const uint8_t *Cur = Beg; Cur < End; Cur++) {
Cur = (uint8_t *)SearchMemory(Cur, End - Cur, PresentData, DataSize);
if (!Cur)
break;
size_t Pos = Cur - Beg;
assert(Pos < UnitSize);
AddMutation(Pos, DataSize, DesiredData);
Res++;
}
return Res;
}
void TraceState::TraceMemcmpCallback(size_t CmpSize, const uint8_t *Data1,
const uint8_t *Data2) {
if (!RecordingMemcmp || !F->InFuzzingThread()) return;
CmpSize = std::min(CmpSize, Word::GetMaxSize());
int Added2 = TryToAddDesiredData(Data1, Data2, CmpSize);
int Added1 = TryToAddDesiredData(Data2, Data1, CmpSize);
if ((Added1 || Added2) && Options.Verbosity >= 3) {
Printf("MemCmp Added %d%d: ", Added1, Added2);
if (Added1) PrintASCII(Data1, CmpSize);
if (Added2) PrintASCII(Data2, CmpSize);
Printf("\n");
}
}
void TraceState::TraceSwitchCallback(uintptr_t PC, size_t ValSizeInBits,
uint64_t Val, size_t NumCases,
uint64_t *Cases) {
if (F->InFuzzingThread()) return;
size_t ValSize = ValSizeInBits / 8;
bool TryShort = IsTwoByteData(Val);
for (size_t i = 0; i < NumCases; i++)
TryShort &= IsTwoByteData(Cases[i]);
if (Options.Verbosity >= 3)
Printf("TraceSwitch: %p %zd # %zd; TryShort %d\n", PC, Val, NumCases,
TryShort);
for (size_t i = 0; i < NumCases; i++) {
TryToAddDesiredData(Val, Cases[i], ValSize);
if (TryShort)
TryToAddDesiredData(Val, Cases[i], 2);
}
}
static TraceState *TS;
void Fuzzer::StartTraceRecording() {
if (!TS) return;
TS->StartTraceRecording();
}
void Fuzzer::StopTraceRecording() {
if (!TS) return;
TS->StopTraceRecording();
}
void Fuzzer::InitializeTraceState() {
if (!Options.UseMemcmp) return;
TS = new TraceState(MD, Options, this);
}
static size_t InternalStrnlen(const char *S, size_t MaxLen) {
size_t Len = 0;
for (; Len < MaxLen && S[Len]; Len++) {}
return Len;
}
} // namespace fuzzer
using fuzzer::TS;
using fuzzer::RecordingMemcmp;
extern "C" {
// We may need to avoid defining weak hooks to stay compatible with older clang.
#ifndef LLVM_FUZZER_DEFINES_SANITIZER_WEAK_HOOOKS
# define LLVM_FUZZER_DEFINES_SANITIZER_WEAK_HOOOKS 1
#endif
#if LLVM_FUZZER_DEFINES_SANITIZER_WEAK_HOOOKS
void __sanitizer_weak_hook_memcmp(void *caller_pc, const void *s1,
const void *s2, size_t n, int result) {
fuzzer::TPC.AddValueForMemcmp(caller_pc, s1, s2, n);
if (!RecordingMemcmp) return;
if (result == 0) return; // No reason to mutate.
if (n <= 1) return; // Not interesting.
TS->TraceMemcmpCallback(n, reinterpret_cast<const uint8_t *>(s1),
reinterpret_cast<const uint8_t *>(s2));
}
void __sanitizer_weak_hook_strncmp(void *caller_pc, const char *s1,
const char *s2, size_t n, int result) {
fuzzer::TPC.AddValueForStrcmp(caller_pc, s1, s2, n);
if (!RecordingMemcmp) return;
if (result == 0) return; // No reason to mutate.
size_t Len1 = fuzzer::InternalStrnlen(s1, n);
size_t Len2 = fuzzer::InternalStrnlen(s2, n);
n = std::min(n, Len1);
n = std::min(n, Len2);
if (n <= 1) return; // Not interesting.
TS->TraceMemcmpCallback(n, reinterpret_cast<const uint8_t *>(s1),
reinterpret_cast<const uint8_t *>(s2));
}
void __sanitizer_weak_hook_strcmp(void *caller_pc, const char *s1,
const char *s2, int result) {
fuzzer::TPC.AddValueForStrcmp(caller_pc, s1, s2, 64);
if (!RecordingMemcmp) return;
if (result == 0) return; // No reason to mutate.
size_t Len1 = strlen(s1);
size_t Len2 = strlen(s2);
size_t N = std::min(Len1, Len2);
if (N <= 1) return; // Not interesting.
TS->TraceMemcmpCallback(N, reinterpret_cast<const uint8_t *>(s1),
reinterpret_cast<const uint8_t *>(s2));
}
void __sanitizer_weak_hook_strncasecmp(void *called_pc, const char *s1,
const char *s2, size_t n, int result) {
return __sanitizer_weak_hook_strncmp(called_pc, s1, s2, n, result);
}
void __sanitizer_weak_hook_strcasecmp(void *called_pc, const char *s1,
const char *s2, int result) {
return __sanitizer_weak_hook_strcmp(called_pc, s1, s2, result);
}
void __sanitizer_weak_hook_strstr(void *called_pc, const char *s1,
const char *s2, char *result) {
TS->AddInterestingWord(reinterpret_cast<const uint8_t *>(s2), strlen(s2));
}
void __sanitizer_weak_hook_strcasestr(void *called_pc, const char *s1,
const char *s2, char *result) {
TS->AddInterestingWord(reinterpret_cast<const uint8_t *>(s2), strlen(s2));
}
void __sanitizer_weak_hook_memmem(void *called_pc, const void *s1, size_t len1,
const void *s2, size_t len2, void *result) {
if (fuzzer::DoingMyOwnMemmem) return;
TS->AddInterestingWord(reinterpret_cast<const uint8_t *>(s2), len2);
}
#endif // LLVM_FUZZER_DEFINES_SANITIZER_WEAK_HOOOKS
} // extern "C"
+218
View File
@@ -0,0 +1,218 @@
//===- FuzzerUtil.cpp - Misc utils ----------------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// Misc utils.
//===----------------------------------------------------------------------===//
#include "FuzzerUtil.h"
#include "FuzzerIO.h"
#include "FuzzerInternal.h"
#include <cassert>
#include <chrono>
#include <cstring>
#include <errno.h>
#include <signal.h>
#include <sstream>
#include <stdio.h>
#include <sys/types.h>
#include <thread>
namespace fuzzer {
void PrintHexArray(const uint8_t *Data, size_t Size,
const char *PrintAfter) {
for (size_t i = 0; i < Size; i++)
Printf("0x%x,", (unsigned)Data[i]);
Printf("%s", PrintAfter);
}
void Print(const Unit &v, const char *PrintAfter) {
PrintHexArray(v.data(), v.size(), PrintAfter);
}
void PrintASCIIByte(uint8_t Byte) {
if (Byte == '\\')
Printf("\\\\");
else if (Byte == '"')
Printf("\\\"");
else if (Byte >= 32 && Byte < 127)
Printf("%c", Byte);
else
Printf("\\x%02x", Byte);
}
void PrintASCII(const uint8_t *Data, size_t Size, const char *PrintAfter) {
for (size_t i = 0; i < Size; i++)
PrintASCIIByte(Data[i]);
Printf("%s", PrintAfter);
}
void PrintASCII(const Unit &U, const char *PrintAfter) {
PrintASCII(U.data(), U.size(), PrintAfter);
}
bool ToASCII(uint8_t *Data, size_t Size) {
bool Changed = false;
for (size_t i = 0; i < Size; i++) {
uint8_t &X = Data[i];
auto NewX = X;
NewX &= 127;
if (!isspace(NewX) && !isprint(NewX))
NewX = ' ';
Changed |= NewX != X;
X = NewX;
}
return Changed;
}
bool IsASCII(const Unit &U) { return IsASCII(U.data(), U.size()); }
bool IsASCII(const uint8_t *Data, size_t Size) {
for (size_t i = 0; i < Size; i++)
if (!(isprint(Data[i]) || isspace(Data[i]))) return false;
return true;
}
bool ParseOneDictionaryEntry(const std::string &Str, Unit *U) {
U->clear();
if (Str.empty()) return false;
size_t L = 0, R = Str.size() - 1; // We are parsing the range [L,R].
// Skip spaces from both sides.
while (L < R && isspace(Str[L])) L++;
while (R > L && isspace(Str[R])) R--;
if (R - L < 2) return false;
// Check the closing "
if (Str[R] != '"') return false;
R--;
// Find the opening "
while (L < R && Str[L] != '"') L++;
if (L >= R) return false;
assert(Str[L] == '\"');
L++;
assert(L <= R);
for (size_t Pos = L; Pos <= R; Pos++) {
uint8_t V = (uint8_t)Str[Pos];
if (!isprint(V) && !isspace(V)) return false;
if (V =='\\') {
// Handle '\\'
if (Pos + 1 <= R && (Str[Pos + 1] == '\\' || Str[Pos + 1] == '"')) {
U->push_back(Str[Pos + 1]);
Pos++;
continue;
}
// Handle '\xAB'
if (Pos + 3 <= R && Str[Pos + 1] == 'x'
&& isxdigit(Str[Pos + 2]) && isxdigit(Str[Pos + 3])) {
char Hex[] = "0xAA";
Hex[2] = Str[Pos + 2];
Hex[3] = Str[Pos + 3];
U->push_back(strtol(Hex, nullptr, 16));
Pos += 3;
continue;
}
return false; // Invalid escape.
} else {
// Any other character.
U->push_back(V);
}
}
return true;
}
bool ParseDictionaryFile(const std::string &Text, std::vector<Unit> *Units) {
if (Text.empty()) {
Printf("ParseDictionaryFile: file does not exist or is empty\n");
return false;
}
std::istringstream ISS(Text);
Units->clear();
Unit U;
int LineNo = 0;
std::string S;
while (std::getline(ISS, S, '\n')) {
LineNo++;
size_t Pos = 0;
while (Pos < S.size() && isspace(S[Pos])) Pos++; // Skip spaces.
if (Pos == S.size()) continue; // Empty line.
if (S[Pos] == '#') continue; // Comment line.
if (ParseOneDictionaryEntry(S, &U)) {
Units->push_back(U);
} else {
Printf("ParseDictionaryFile: error in line %d\n\t\t%s\n", LineNo,
S.c_str());
return false;
}
}
return true;
}
std::string Base64(const Unit &U) {
static const char Table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
std::string Res;
size_t i;
for (i = 0; i + 2 < U.size(); i += 3) {
uint32_t x = (U[i] << 16) + (U[i + 1] << 8) + U[i + 2];
Res += Table[(x >> 18) & 63];
Res += Table[(x >> 12) & 63];
Res += Table[(x >> 6) & 63];
Res += Table[x & 63];
}
if (i + 1 == U.size()) {
uint32_t x = (U[i] << 16);
Res += Table[(x >> 18) & 63];
Res += Table[(x >> 12) & 63];
Res += "==";
} else if (i + 2 == U.size()) {
uint32_t x = (U[i] << 16) + (U[i + 1] << 8);
Res += Table[(x >> 18) & 63];
Res += Table[(x >> 12) & 63];
Res += Table[(x >> 6) & 63];
Res += "=";
}
return Res;
}
std::string DescribePC(const char *SymbolizedFMT, uintptr_t PC) {
if (!EF->__sanitizer_symbolize_pc) return "<can not symbolize>";
char PcDescr[1024];
EF->__sanitizer_symbolize_pc(reinterpret_cast<void*>(PC),
SymbolizedFMT, PcDescr, sizeof(PcDescr));
PcDescr[sizeof(PcDescr) - 1] = 0; // Just in case.
return PcDescr;
}
void PrintPC(const char *SymbolizedFMT, const char *FallbackFMT, uintptr_t PC) {
if (EF->__sanitizer_symbolize_pc)
Printf("%s", DescribePC(SymbolizedFMT, PC).c_str());
else
Printf(FallbackFMT, PC);
}
unsigned NumberOfCpuCores() {
unsigned N = std::thread::hardware_concurrency();
if (!N) {
Printf("WARNING: std::thread::hardware_concurrency not well defined for "
"your platform. Assuming CPU count of 1.\n");
N = 1;
}
return N;
}
bool ExecuteCommandAndReadOutput(const std::string &Command, std::string *Out) {
FILE *Pipe = OpenProcessPipe(Command.c_str(), "r");
if (!Pipe) return false;
char Buff[1024];
size_t N;
while ((N = fread(Buff, 1, sizeof(Buff), Pipe)) > 0)
Out->append(Buff, N);
return true;
}
} // namespace fuzzer
+72
View File
@@ -0,0 +1,72 @@
//===- FuzzerUtil.h - Internal header for the Fuzzer Utils ------*- C++ -* ===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// Util functions.
//===----------------------------------------------------------------------===//
#ifndef LLVM_FUZZER_UTIL_H
#define LLVM_FUZZER_UTIL_H
#include "FuzzerDefs.h"
namespace fuzzer {
void PrintHexArray(const Unit &U, const char *PrintAfter = "");
void PrintHexArray(const uint8_t *Data, size_t Size,
const char *PrintAfter = "");
void PrintASCII(const uint8_t *Data, size_t Size, const char *PrintAfter = "");
void PrintASCII(const Unit &U, const char *PrintAfter = "");
// Changes U to contain only ASCII (isprint+isspace) characters.
// Returns true iff U has been changed.
bool ToASCII(uint8_t *Data, size_t Size);
bool IsASCII(const Unit &U);
bool IsASCII(const uint8_t *Data, size_t Size);
std::string Base64(const Unit &U);
void PrintPC(const char *SymbolizedFMT, const char *FallbackFMT, uintptr_t PC);
std::string DescribePC(const char *SymbolizedFMT, uintptr_t PC);
unsigned NumberOfCpuCores();
bool ExecuteCommandAndReadOutput(const std::string &Command, std::string *Out);
// Platform specific functions.
void SetSignalHandler(const FuzzingOptions& Options);
void SleepSeconds(int Seconds);
unsigned long GetPid();
size_t GetPeakRSSMb();
int ExecuteCommand(const std::string &Command);
FILE *OpenProcessPipe(const char *Command, const char *Mode);
const void *SearchMemory(const void *haystack, size_t haystacklen,
const void *needle, size_t needlelen);
std::string CloneArgsWithoutX(const std::vector<std::string> &Args,
const char *X1, const char *X2);
inline std::string CloneArgsWithoutX(const std::vector<std::string> &Args,
const char *X) {
return CloneArgsWithoutX(Args, X, X);
}
} // namespace fuzzer
#endif // LLVM_FUZZER_UTIL_H
+152
View File
@@ -0,0 +1,152 @@
//===- FuzzerUtilDarwin.cpp - Misc utils ----------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// Misc utils for Darwin.
//===----------------------------------------------------------------------===//
#include "FuzzerDefs.h"
#if LIBFUZZER_APPLE
#include "FuzzerIO.h"
#include <mutex>
#include <signal.h>
#include <spawn.h>
#include <sys/wait.h>
// There is no header for this on macOS so declare here
extern "C" char **environ;
namespace fuzzer {
static std::mutex SignalMutex;
// Global variables used to keep track of how signal handling should be
// restored. They should **not** be accessed without holding `SignalMutex`.
static int ActiveThreadCount = 0;
static struct sigaction OldSigIntAction;
static struct sigaction OldSigQuitAction;
static sigset_t OldBlockedSignalsSet;
// This is a reimplementation of Libc's `system()`. On Darwin the Libc
// implementation contains a mutex which prevents it from being used
// concurrently. This implementation **can** be used concurrently. It sets the
// signal handlers when the first thread enters and restores them when the last
// thread finishes execution of the function and ensures this is not racey by
// using a mutex.
int ExecuteCommand(const std::string &Command) {
posix_spawnattr_t SpawnAttributes;
if (posix_spawnattr_init(&SpawnAttributes))
return -1;
// Block and ignore signals of the current process when the first thread
// enters.
{
std::lock_guard<std::mutex> Lock(SignalMutex);
if (ActiveThreadCount == 0) {
static struct sigaction IgnoreSignalAction;
sigset_t BlockedSignalsSet;
memset(&IgnoreSignalAction, 0, sizeof(IgnoreSignalAction));
IgnoreSignalAction.sa_handler = SIG_IGN;
if (sigaction(SIGINT, &IgnoreSignalAction, &OldSigIntAction) == -1) {
Printf("Failed to ignore SIGINT\n");
(void)posix_spawnattr_destroy(&SpawnAttributes);
return -1;
}
if (sigaction(SIGQUIT, &IgnoreSignalAction, &OldSigQuitAction) == -1) {
Printf("Failed to ignore SIGQUIT\n");
// Try our best to restore the signal handlers.
(void)sigaction(SIGINT, &OldSigIntAction, NULL);
(void)posix_spawnattr_destroy(&SpawnAttributes);
return -1;
}
(void)sigemptyset(&BlockedSignalsSet);
(void)sigaddset(&BlockedSignalsSet, SIGCHLD);
if (sigprocmask(SIG_BLOCK, &BlockedSignalsSet, &OldBlockedSignalsSet) ==
-1) {
Printf("Failed to block SIGCHLD\n");
// Try our best to restore the signal handlers.
(void)sigaction(SIGQUIT, &OldSigQuitAction, NULL);
(void)sigaction(SIGINT, &OldSigIntAction, NULL);
(void)posix_spawnattr_destroy(&SpawnAttributes);
return -1;
}
}
++ActiveThreadCount;
}
// NOTE: Do not introduce any new `return` statements past this
// point. It is important that `ActiveThreadCount` always be decremented
// when leaving this function.
// Make sure the child process uses the default handlers for the
// following signals rather than inheriting what the parent has.
sigset_t DefaultSigSet;
(void)sigemptyset(&DefaultSigSet);
(void)sigaddset(&DefaultSigSet, SIGQUIT);
(void)sigaddset(&DefaultSigSet, SIGINT);
(void)posix_spawnattr_setsigdefault(&SpawnAttributes, &DefaultSigSet);
// Make sure the child process doesn't block SIGCHLD
(void)posix_spawnattr_setsigmask(&SpawnAttributes, &OldBlockedSignalsSet);
short SpawnFlags = POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK;
(void)posix_spawnattr_setflags(&SpawnAttributes, SpawnFlags);
pid_t Pid;
char **Environ = environ; // Read from global
const char *CommandCStr = Command.c_str();
const char *Argv[] = {"sh", "-c", CommandCStr, NULL};
int ErrorCode = 0, ProcessStatus = 0;
// FIXME: We probably shouldn't hardcode the shell path.
ErrorCode = posix_spawn(&Pid, "/bin/sh", NULL, &SpawnAttributes,
(char *const *)Argv, Environ);
(void)posix_spawnattr_destroy(&SpawnAttributes);
if (!ErrorCode) {
pid_t SavedPid = Pid;
do {
// Repeat until call completes uninterrupted.
Pid = waitpid(SavedPid, &ProcessStatus, /*options=*/0);
} while (Pid == -1 && errno == EINTR);
if (Pid == -1) {
// Fail for some other reason.
ProcessStatus = -1;
}
} else if (ErrorCode == ENOMEM || ErrorCode == EAGAIN) {
// Fork failure.
ProcessStatus = -1;
} else {
// Shell execution failure.
ProcessStatus = W_EXITCODE(127, 0);
}
// Restore the signal handlers of the current process when the last thread
// using this function finishes.
{
std::lock_guard<std::mutex> Lock(SignalMutex);
--ActiveThreadCount;
if (ActiveThreadCount == 0) {
bool FailedRestore = false;
if (sigaction(SIGINT, &OldSigIntAction, NULL) == -1) {
Printf("Failed to restore SIGINT handling\n");
FailedRestore = true;
}
if (sigaction(SIGQUIT, &OldSigQuitAction, NULL) == -1) {
Printf("Failed to restore SIGQUIT handling\n");
FailedRestore = true;
}
if (sigprocmask(SIG_BLOCK, &OldBlockedSignalsSet, NULL) == -1) {
Printf("Failed to unblock SIGCHLD\n");
FailedRestore = true;
}
if (FailedRestore)
ProcessStatus = -1;
}
}
return ProcessStatus;
}
} // namespace fuzzer
#endif // LIBFUZZER_APPLE

Some files were not shown because too many files have changed in this diff Show More