forked from BigfootDev/flatbuffers
Make the Parser independent from the global C-locale (#5028)
* Make the Parser independent from the global C-locale * Set a specific test locale using the environment variable FLATBUFFERS_TEST_LOCALE * Remove redundant static qualifiers
This commit is contained in:
committed by
Wouter van Oortmerssen
parent
d6b1ce09cf
commit
5f32f94810
19
.travis.yml
19
.travis.yml
@@ -2,6 +2,12 @@ env:
|
||||
global:
|
||||
# Set at the root level as this is ignored when set under matrix.env.
|
||||
- GCC_VERSION="4.9"
|
||||
# Fail on first error if UBSAN or ASAN enabled for a target
|
||||
- UBSAN_OPTIONS=halt_on_error=1
|
||||
- ASAN_OPTIONS=halt_on_error=1
|
||||
# Travis machines have 2 cores
|
||||
- JOBS=2
|
||||
- MAKEFLAGS="-j 2"
|
||||
|
||||
conan-linux: &conan-linux
|
||||
os: linux
|
||||
@@ -53,7 +59,7 @@ matrix:
|
||||
# branch: master
|
||||
- language: cpp
|
||||
os:
|
||||
- linux
|
||||
- linux
|
||||
|
||||
compiler:
|
||||
- gcc
|
||||
@@ -79,8 +85,8 @@ matrix:
|
||||
-DGRPC_INSTALL_PATH=$TRAVIS_BUILD_DIR/google/grpc/install
|
||||
-DPROTOBUF_DOWNLOAD_PATH=$TRAVIS_BUILD_DIR/google/grpc/third_party/protobuf
|
||||
-DFLATBUFFERS_CODE_SANITIZE=ON
|
||||
- make
|
||||
- LD_LIBRARY_PATH=$TRAVIS_BUILD_DIR/google/grpc/install/lib make test ARGS=-V
|
||||
- cmake --build . -- -j${JOBS}
|
||||
- LD_LIBRARY_PATH=$TRAVIS_BUILD_DIR/google/grpc/install/lib ctest --extra-verbose --output-on-failure
|
||||
- bash .travis/check-generate-code.sh
|
||||
- if [ "$CONAN" == "true" ] && [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo pip install conan && conan create . flatbuffers/${TRAVIS_BRANCH}@google/testing -s build_type=$BUILD_TYPE -tf conan/test_package; fi
|
||||
|
||||
@@ -91,6 +97,7 @@ matrix:
|
||||
matrix:
|
||||
- BUILD_TYPE=Debug
|
||||
- BUILD_TYPE=Release
|
||||
|
||||
script:
|
||||
- bash grpc/build_grpc.sh
|
||||
- cmake .
|
||||
@@ -99,10 +106,9 @@ matrix:
|
||||
-DGRPC_INSTALL_PATH=$TRAVIS_BUILD_DIR/google/grpc/install
|
||||
-DPROTOBUF_DOWNLOAD_PATH=$TRAVIS_BUILD_DIR/google/grpc/third_party/protobuf
|
||||
-DFLATBUFFERS_CODE_SANITIZE=ON
|
||||
- make
|
||||
- ./flattests
|
||||
- cmake --build . -- -j${JOBS}
|
||||
- DYLD_LIBRARY_PATH=$TRAVIS_BUILD_DIR/google/grpc/install/lib ctest --extra-verbose --output-on-failure
|
||||
- bash .travis/check-generate-code.sh
|
||||
- DYLD_LIBRARY_PATH=$TRAVIS_BUILD_DIR/google/grpc/install/lib ./grpctest
|
||||
|
||||
- <<: *conan-linux
|
||||
env: CONAN_GCC_VERSIONS=4.9 CONAN_DOCKER_IMAGE=conanio/gcc49
|
||||
@@ -149,6 +155,7 @@ matrix:
|
||||
- extra-android-m2repository
|
||||
compiler:
|
||||
- gcc
|
||||
|
||||
before_install:
|
||||
- git clone https://github.com/urho3d/android-ndk.git $HOME/android-ndk-root
|
||||
- export ANDROID_NDK_HOME=$HOME/android-ndk-root
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
cmake_minimum_required(VERSION 2.8)
|
||||
# generate compile_commands.json
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
include(CheckCXXSymbolExists)
|
||||
|
||||
project(FlatBuffers)
|
||||
|
||||
@@ -35,6 +36,16 @@ if(DEFINED FLATBUFFERS_MAX_PARSING_DEPTH)
|
||||
message(STATUS "FLATBUFFERS_MAX_PARSING_DEPTH: ${FLATBUFFERS_MAX_PARSING_DEPTH}")
|
||||
endif()
|
||||
|
||||
# Auto-detect locale-narrow 'strtod_l' function.
|
||||
if(NOT DEFINED FLATBUFFERS_LOCALE_INDEPENDENT)
|
||||
if(MSVC)
|
||||
check_cxx_symbol_exists(_strtof_l stdlib.h FLATBUFFERS_LOCALE_INDEPENDENT)
|
||||
else()
|
||||
check_cxx_symbol_exists(strtof_l stdlib.h FLATBUFFERS_LOCALE_INDEPENDENT)
|
||||
endif()
|
||||
endif()
|
||||
add_definitions(-DFLATBUFFERS_LOCALE_INDEPENDENT=$<BOOL:${FLATBUFFERS_LOCALE_INDEPENDENT}>)
|
||||
|
||||
set(FlatBuffers_Library_SRCS
|
||||
include/flatbuffers/code_generators.h
|
||||
include/flatbuffers/base.h
|
||||
@@ -213,6 +224,7 @@ function(add_fsanitize_to_target _target _sanitizer)
|
||||
target_link_libraries(${_target} PRIVATE
|
||||
"-fsanitize${_sanitizer_flags}")
|
||||
set_property(TARGET ${_target} PROPERTY POSITION_INDEPENDENT_CODE ON)
|
||||
message(STATUS "Sanitizer ${_sanitizer_flags} added to ${_target}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
|
||||
@@ -499,11 +499,47 @@ To use scalars, simply wrap them in a struct.
|
||||
|
||||
## Depth limit of nested objects and stack-overflow control
|
||||
The parser of Flatbuffers schema or json-files is kind of recursive parser.
|
||||
To avoid stack-overflow problem the parser has a built-in limiter of recursion depth.
|
||||
Number of nested declarations in a schema or number of nested json-objects is limited.
|
||||
By default, this depth limit set to `64`.
|
||||
It is possible to override this limit with `FLATBUFFERS_MAX_PARSING_DEPTH` definition.
|
||||
This definition can be helpful for testing purposes or embedded applications.
|
||||
For details see [build](@ref flatbuffers_guide_building) of CMake-based projects.
|
||||
To avoid stack-overflow problem the parser has a built-in limiter of
|
||||
recursion depth. Number of nested declarations in a schema or number of
|
||||
nested json-objects is limited. By default, this depth limit set to `64`.
|
||||
It is possible to override this limit with `FLATBUFFERS_MAX_PARSING_DEPTH`
|
||||
definition. This definition can be helpful for testing purposes or embedded
|
||||
applications. For details see [build](@ref flatbuffers_guide_building) of
|
||||
CMake-based projects.
|
||||
|
||||
## Dependence from C-locale {#flatbuffers_locale_cpp}
|
||||
The Flatbuffers [grammar](@ref flatbuffers grammar) uses ASCII
|
||||
character set for identifiers, alphanumeric literals, reserved words.
|
||||
|
||||
Internal implementation of the Flatbuffers depends from functions which
|
||||
depend from C-locale: `strtod()` or `strtof()`, for example.
|
||||
The library expects the dot `.` symbol as the separator of an integer
|
||||
part from the fractional part of a float number.
|
||||
Another separator symbols (`,` for example) will break the compatibility
|
||||
and may lead to an error while parsing a Flatbuffers schema or a json file.
|
||||
|
||||
The Standard C locale is a global resource, there is only one locale for
|
||||
the entire application. Some modern compilers and platforms have
|
||||
locale-independent or locale-narrow functions `strtof_l`, `strtod_l`,
|
||||
`strtoll_l`, `strtoull_l` to resolve this dependency.
|
||||
These functions use specified locale rather than the global or per-thread
|
||||
locale instead. They are part of POSIX-2008 but not part of the C/C++
|
||||
standard library, therefore, may be missing on some platforms.
|
||||
|
||||
The Flatbuffers library try to detect these functions at configuration and
|
||||
compile time:
|
||||
- `_MSC_VER >= 1900`: check MSVC2012 or higher for MSVC buid
|
||||
- `_XOPEN_SOURCE>=700`: check POSIX-2008 for GCC/Clang build
|
||||
- `check_cxx_symbol_exists(strtof_l stdlib.h)`: CMake check of `strtod_f`
|
||||
|
||||
After detection, the definition `FLATBUFFERS_LOCALE_INDEPENDENT` will be
|
||||
set to `0` or `1`.
|
||||
|
||||
It is possible to test the compatibility of the Flatbuffers library with
|
||||
a specific locale using the environment variable `FLATBUFFERS_TEST_LOCALE`:
|
||||
```sh
|
||||
>FLATBUFFERS_TEST_LOCALE="" ./flattests
|
||||
>FLATBUFFERS_TEST_LOCALE="ru_RU.CP1251" ./flattests
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
@@ -195,15 +195,35 @@
|
||||
#endif
|
||||
#endif // !FLATBUFFERS_HAS_NEW_STRTOD
|
||||
|
||||
// Suppress sanitizer directives.
|
||||
#ifndef FLATBUFFERS_LOCALE_INDEPENDENT
|
||||
// Enable locale independent functions {strtof_l, strtod_l,strtoll_l, strtoull_l}.
|
||||
// They are part of the POSIX-2008 but not part of the C/C++ standard.
|
||||
// GCC/Clang have definition (_XOPEN_SOURCE>=700) if POSIX-2008.
|
||||
#if ((defined(_MSC_VER) && _MSC_VER >= 1800) || \
|
||||
(defined(_XOPEN_SOURCE) && (_XOPEN_SOURCE>=700)))
|
||||
#define FLATBUFFERS_LOCALE_INDEPENDENT 1
|
||||
#else
|
||||
#define FLATBUFFERS_LOCALE_INDEPENDENT 0
|
||||
#endif
|
||||
#endif // !FLATBUFFERS_LOCALE_INDEPENDENT
|
||||
|
||||
// Suppress Undefined Behavior Sanitizer (recoverable only). Usage:
|
||||
// - __supress_ubsan__("undefined")
|
||||
// - __supress_ubsan__("signed-integer-overflow")
|
||||
#if defined(__clang__)
|
||||
#define __no_sanitize_undefined__(reason) __attribute__((no_sanitize("undefined")))
|
||||
#define __supress_ubsan__(type) __attribute__((no_sanitize(type)))
|
||||
#elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 408)
|
||||
#define __no_sanitize_undefined__(reason) __attribute__((no_sanitize_undefined))
|
||||
#define __supress_ubsan__(type) __attribute__((no_sanitize_undefined))
|
||||
#else
|
||||
#define __no_sanitize_undefined__(reason)
|
||||
#define __supress_ubsan__(type)
|
||||
#endif
|
||||
|
||||
// This is constexpr function used for checking compile-time constants.
|
||||
// Avoid `#pragma warning(disable: 4127) // C4127: expression is constant`.
|
||||
template<typename T> FLATBUFFERS_CONSTEXPR inline bool IsConstTrue(T t) {
|
||||
return !!t;
|
||||
}
|
||||
|
||||
/// @endcond
|
||||
|
||||
/// @file
|
||||
@@ -287,13 +307,15 @@ template<typename T> T EndianScalar(T t) {
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__no_sanitize_undefined__("C++ aliasing type rules, see std::bit_cast<>")
|
||||
// UBSAN: C++ aliasing type rules, see std::bit_cast<> for details.
|
||||
__supress_ubsan__("alignment")
|
||||
T ReadScalar(const void *p) {
|
||||
return EndianScalar(*reinterpret_cast<const T *>(p));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__no_sanitize_undefined__("C++ aliasing type rules, see std::bit_cast<>")
|
||||
// UBSAN: C++ aliasing type rules, see std::bit_cast<> for details.
|
||||
__supress_ubsan__("alignment")
|
||||
void WriteScalar(void *p, T t) {
|
||||
*reinterpret_cast<T *>(p) = EndianScalar(t);
|
||||
}
|
||||
|
||||
@@ -17,18 +17,15 @@
|
||||
#ifndef FLATBUFFERS_UTIL_H_
|
||||
#define FLATBUFFERS_UTIL_H_
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
// clang-format off
|
||||
|
||||
#ifndef FLATBUFFERS_PREFER_PRINTF
|
||||
# include <sstream>
|
||||
#else // FLATBUFFERS_PREFER_PRINTF
|
||||
# include <float.h>
|
||||
# include <stdio.h>
|
||||
#endif // FLATBUFFERS_PREFER_PRINTF
|
||||
#include <string>
|
||||
|
||||
#ifdef _WIN32
|
||||
# ifndef WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
@@ -43,18 +40,21 @@
|
||||
#else
|
||||
# include <limits.h>
|
||||
#endif
|
||||
// clang-format on
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <iomanip>
|
||||
#include <fstream>
|
||||
|
||||
#include "flatbuffers/base.h"
|
||||
|
||||
namespace flatbuffers {
|
||||
|
||||
// Avoid `#pragma warning(disable: 4127) // C4127: expression is constant`.
|
||||
template<typename T> FLATBUFFERS_CONSTEXPR inline bool IsConstTrue(const T &t) {
|
||||
return !!t;
|
||||
}
|
||||
|
||||
// @locale-independent functions for ASCII characters set.
|
||||
|
||||
// Check that integer scalar is in closed range: (a <= x <= b)
|
||||
@@ -67,13 +67,13 @@ template<typename T> inline bool check_in_range(T x, T a, T b) {
|
||||
}
|
||||
|
||||
// Case-insensitive isalpha
|
||||
static inline bool is_alpha(char c) {
|
||||
inline bool is_alpha(char c) {
|
||||
// ASCII only: alpha to upper case => reset bit 0x20 (~0x20 = 0xDF).
|
||||
return check_in_range(c & 0xDF, 'a' & 0xDF, 'z' & 0xDF);
|
||||
}
|
||||
|
||||
// Check (case-insensitive) that `c` is equal to alpha.
|
||||
static inline bool is_alpha_char(char c, char alpha) {
|
||||
inline bool is_alpha_char(char c, char alpha) {
|
||||
FLATBUFFERS_ASSERT(is_alpha(alpha));
|
||||
// ASCII only: alpha to upper case => reset bit 0x20 (~0x20 = 0xDF).
|
||||
return ((c & 0xDF) == (alpha & 0xDF));
|
||||
@@ -84,15 +84,15 @@ static inline bool is_alpha_char(char c, char alpha) {
|
||||
// functions that are not affected by the currently installed C locale. although
|
||||
// some implementations (e.g. Microsoft in 1252 codepage) may classify
|
||||
// additional single-byte characters as digits.
|
||||
static inline bool is_digit(char c) { return check_in_range(c, '0', '9'); }
|
||||
inline bool is_digit(char c) { return check_in_range(c, '0', '9'); }
|
||||
|
||||
static inline bool is_xdigit(char c) {
|
||||
inline bool is_xdigit(char c) {
|
||||
// Replace by look-up table.
|
||||
return is_digit(c) | check_in_range(c & 0xDF, 'a' & 0xDF, 'f' & 0xDF);
|
||||
}
|
||||
|
||||
// Case-insensitive isalnum
|
||||
static inline bool is_alnum(char c) { return is_alpha(c) || is_digit(c); }
|
||||
inline bool is_alnum(char c) { return is_alpha(c) || is_digit(c); }
|
||||
|
||||
// @end-locale-independent functions for ASCII character set
|
||||
|
||||
@@ -119,21 +119,22 @@ template<typename T> size_t NumToStringWidth(T t, int precision = 0) {
|
||||
return string_width;
|
||||
}
|
||||
|
||||
template<typename T> std::string NumToStringImplWrapper(T t, const char* fmt,
|
||||
int precision = 0) {
|
||||
template<typename T>
|
||||
std::string NumToStringImplWrapper(T t, const char *fmt, int precision = 0) {
|
||||
size_t string_width = NumToStringWidth(t, precision);
|
||||
std::string s(string_width, 0x00);
|
||||
// Allow snprintf to use std::string trailing null to detect buffer overflow
|
||||
snprintf(const_cast<char*>(s.data()), (s.size()+1), fmt, precision, t);
|
||||
snprintf(const_cast<char *>(s.data()), (s.size() + 1), fmt, precision, t);
|
||||
return s;
|
||||
}
|
||||
#endif // FLATBUFFERS_PREFER_PRINTF
|
||||
#endif // FLATBUFFERS_PREFER_PRINTF
|
||||
|
||||
// Convert an integer or floating point value to a string.
|
||||
// In contrast to std::stringstream, "char" values are
|
||||
// converted to a string of digits, and we don't use scientific notation.
|
||||
template<typename T> std::string NumToString(T t) {
|
||||
// clang-format off
|
||||
|
||||
#ifndef FLATBUFFERS_PREFER_PRINTF
|
||||
std::stringstream ss;
|
||||
ss << t;
|
||||
@@ -169,6 +170,7 @@ inline std::string NumToString<unsigned long long>(unsigned long long t) {
|
||||
// Special versions for floats/doubles.
|
||||
template<typename T> std::string FloatToString(T t, int precision) {
|
||||
// clang-format off
|
||||
|
||||
#ifndef FLATBUFFERS_PREFER_PRINTF
|
||||
// to_string() prints different numbers of digits for floats depending on
|
||||
// platform and isn't available on Android, so we use stringstream
|
||||
@@ -206,6 +208,7 @@ template<> inline std::string NumToString<float>(float t) {
|
||||
inline std::string IntToStringHex(int i, int xdigits) {
|
||||
FLATBUFFERS_ASSERT(i >= 0);
|
||||
// clang-format off
|
||||
|
||||
#ifndef FLATBUFFERS_PREFER_PRINTF
|
||||
std::stringstream ss;
|
||||
ss << std::setw(xdigits) << std::setfill('0') << std::hex << std::uppercase
|
||||
@@ -217,37 +220,71 @@ inline std::string IntToStringHex(int i, int xdigits) {
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
static inline double strtod_impl(const char *str, char **str_end) {
|
||||
// Result of strtod (printf, etc) depends from current C-locale.
|
||||
return strtod(str, str_end);
|
||||
}
|
||||
// clang-format off
|
||||
// Use locale independent functions {strtod_l, strtof_l, strtoll_l, strtoull_l}.
|
||||
#if defined(FLATBUFFERS_LOCALE_INDEPENDENT) && (FLATBUFFERS_LOCALE_INDEPENDENT > 0)
|
||||
class ClassicLocale {
|
||||
#ifdef _MSC_VER
|
||||
typedef _locale_t locale_type;
|
||||
#else
|
||||
typedef locale_t locale_type; // POSIX.1-2008 locale_t type
|
||||
#endif
|
||||
ClassicLocale();
|
||||
~ClassicLocale();
|
||||
locale_type locale_;
|
||||
static ClassicLocale instance_;
|
||||
public:
|
||||
static locale_type Get() { return instance_.locale_; }
|
||||
};
|
||||
|
||||
static inline float strtof_impl(const char *str, char **str_end) {
|
||||
// Use "strtof" for float and strtod for double to avoid double=>float
|
||||
// rounding problems (see
|
||||
// https://en.cppreference.com/w/cpp/numeric/fenv/feround) or problems with
|
||||
// std::numeric_limits<float>::is_iec559==false. Example:
|
||||
// for (int mode : { FE_DOWNWARD, FE_TONEAREST, FE_TOWARDZERO, FE_UPWARD }){
|
||||
// const char *s = "-4e38";
|
||||
// std::fesetround(mode);
|
||||
// std::cout << strtof(s, nullptr) << "; " << strtod(s, nullptr) << "; "
|
||||
// << static_cast<float>(strtod(s, nullptr)) << "\n";
|
||||
// }
|
||||
// Gives:
|
||||
// -inf; -4e+38; -inf
|
||||
// -inf; -4e+38; -inf
|
||||
// -inf; -4e+38; -3.40282e+38
|
||||
// -inf; -4e+38; -3.40282e+38
|
||||
|
||||
// clang-format off
|
||||
#ifdef FLATBUFFERS_HAS_NEW_STRTOD
|
||||
return strtof(str, str_end);
|
||||
#ifdef _MSC_VER
|
||||
#define __strtoull_impl(s, pe, b) _strtoui64_l(s, pe, b, ClassicLocale::Get())
|
||||
#define __strtoll_impl(s, pe, b) _strtoi64_l(s, pe, b, ClassicLocale::Get())
|
||||
#define __strtod_impl(s, pe) _strtod_l(s, pe, ClassicLocale::Get())
|
||||
#define __strtof_impl(s, pe) _strtof_l(s, pe, ClassicLocale::Get())
|
||||
#else
|
||||
return static_cast<float>(strtod_impl(str, str_end));
|
||||
#endif // !FLATBUFFERS_HAS_NEW_STRTOD
|
||||
// clang-format on
|
||||
#define __strtoull_impl(s, pe, b) strtoull_l(s, pe, b, ClassicLocale::Get())
|
||||
#define __strtoll_impl(s, pe, b) strtoll_l(s, pe, b, ClassicLocale::Get())
|
||||
#define __strtod_impl(s, pe) strtod_l(s, pe, ClassicLocale::Get())
|
||||
#define __strtof_impl(s, pe) strtof_l(s, pe, ClassicLocale::Get())
|
||||
#endif
|
||||
#else
|
||||
#define __strtod_impl(s, pe) strtod(s, pe)
|
||||
#define __strtof_impl(s, pe) static_cast<float>(strtod(s, pe))
|
||||
#ifdef _MSC_VER
|
||||
#define __strtoull_impl(s, pe, b) _strtoui64(s, pe, b)
|
||||
#define __strtoll_impl(s, pe, b) _strtoi64(s, pe, b)
|
||||
#else
|
||||
#define __strtoull_impl(s, pe, b) strtoull(s, pe, b)
|
||||
#define __strtoll_impl(s, pe, b) strtoll(s, pe, b)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
inline void strtoval_impl(int64_t *val, const char *str, char **endptr,
|
||||
int base) {
|
||||
*val = __strtoll_impl(str, endptr, base);
|
||||
}
|
||||
|
||||
inline void strtoval_impl(uint64_t *val, const char *str, char **endptr,
|
||||
int base) {
|
||||
*val = __strtoull_impl(str, endptr, base);
|
||||
}
|
||||
|
||||
inline void strtoval_impl(double *val, const char *str, char **endptr) {
|
||||
*val = __strtod_impl(str, endptr);
|
||||
}
|
||||
|
||||
// UBSAN: double to float is safe if numeric_limits<float>::is_iec559 is true.
|
||||
__supress_ubsan__("float-cast-overflow")
|
||||
inline void strtoval_impl(float *val, const char *str, char **endptr) {
|
||||
*val = __strtof_impl(str, endptr);
|
||||
}
|
||||
#undef __strtoull_impl
|
||||
#undef __strtoll_impl
|
||||
#undef __strtod_impl
|
||||
#undef __strtof_impl
|
||||
// clang-format on
|
||||
|
||||
// Adaptor for strtoull()/strtoll().
|
||||
// Flatbuffers accepts numbers with any count of leading zeros (-009 is -9),
|
||||
// while strtoll with base=0 interprets first leading zero as octal prefix.
|
||||
@@ -261,66 +298,43 @@ static inline float strtof_impl(const char *str, char **str_end) {
|
||||
// - If the converted value falls out of range of corresponding return type, a
|
||||
// range error occurs. In this case value MAX(T)/MIN(T) is returned.
|
||||
template<typename T>
|
||||
inline T StringToInteger64Impl(const char *const str, const char **endptr,
|
||||
const int base, const bool check_errno = true) {
|
||||
static_assert(flatbuffers::is_same<T, int64_t>::value ||
|
||||
flatbuffers::is_same<T, uint64_t>::value,
|
||||
"Type T must be either int64_t or uint64_t");
|
||||
FLATBUFFERS_ASSERT(str && endptr); // endptr must be not null
|
||||
inline bool StringToIntegerImpl(T *val, const char *const str,
|
||||
const int base = 0,
|
||||
const bool check_errno = true) {
|
||||
// T is int64_t or uint64_T
|
||||
FLATBUFFERS_ASSERT(str);
|
||||
if (base <= 0) {
|
||||
auto s = str;
|
||||
while (*s && !is_digit(*s)) s++;
|
||||
if (s[0] == '0' && is_alpha_char(s[1], 'X'))
|
||||
return StringToInteger64Impl<T>(str, endptr, 16, check_errno);
|
||||
return StringToIntegerImpl(val, str, 16, check_errno);
|
||||
// if a prefix not match, try base=10
|
||||
return StringToInteger64Impl<T>(str, endptr, 10, check_errno);
|
||||
return StringToIntegerImpl(val, str, 10, check_errno);
|
||||
} else {
|
||||
if (check_errno) errno = 0; // clear thread-local errno
|
||||
// calculate result
|
||||
T result;
|
||||
if (IsConstTrue(flatbuffers::is_same<T, int64_t>::value)) {
|
||||
// clang-format off
|
||||
#ifdef _MSC_VER
|
||||
result = _strtoi64(str, const_cast<char**>(endptr), base);
|
||||
#else
|
||||
result = strtoll(str, const_cast<char**>(endptr), base);
|
||||
#endif
|
||||
// clang-format on
|
||||
} else { // T is uint64_t
|
||||
// clang-format off
|
||||
#ifdef _MSC_VER
|
||||
result = _strtoui64(str, const_cast<char**>(endptr), base);
|
||||
#else
|
||||
result = strtoull(str, const_cast<char**>(endptr), base);
|
||||
#endif
|
||||
// clang-format on
|
||||
|
||||
// The strtoull accepts negative numbers:
|
||||
// If the minus sign was part of the input sequence, the numeric value
|
||||
// calculated from the sequence of digits is negated as if by unary minus
|
||||
// in the result type, which applies unsigned integer wraparound rules.
|
||||
// Fix this behaviour (except -0).
|
||||
if ((**endptr == '\0') && (0 != result)) {
|
||||
auto s = str;
|
||||
while (*s && !is_digit(*s)) s++;
|
||||
s = (s > str) ? (s - 1) : s; // step back to one symbol
|
||||
if (*s == '-') {
|
||||
// For unsigned types return max to distinguish from
|
||||
// "no conversion can be performed".
|
||||
result = flatbuffers::numeric_limits<T>::max();
|
||||
// point to the start of string, like errno
|
||||
*endptr = str;
|
||||
}
|
||||
}
|
||||
auto endptr = str;
|
||||
strtoval_impl(val, str, const_cast<char **>(&endptr), base);
|
||||
if ((*endptr != '\0') || (endptr == str)) {
|
||||
*val = 0; // erase partial result
|
||||
return false; // invalid string
|
||||
}
|
||||
// check for overflow
|
||||
if (check_errno && errno) *endptr = str; // point it to start of input
|
||||
// erase partial result, but save an overflow
|
||||
if ((*endptr != str) && (**endptr != '\0')) result = 0;
|
||||
return result;
|
||||
// errno is out-of-range, return MAX/MIN
|
||||
if (check_errno && errno) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline bool StringToFloatImpl(T *val, const char *const str) {
|
||||
// Type T must be either float or double.
|
||||
FLATBUFFERS_ASSERT(str && val);
|
||||
auto end = str;
|
||||
strtoval_impl(val, str, const_cast<char **>(&end));
|
||||
auto done = (end != str) && (*end == '\0');
|
||||
if (!done) *val = 0; // erase partial result
|
||||
return done;
|
||||
}
|
||||
|
||||
// Convert a string to an instance of T.
|
||||
// Return value (matched with StringToInteger64Impl and strtod):
|
||||
// - If successful, a numeric value corresponding to the str is returned.
|
||||
@@ -329,66 +343,70 @@ inline T StringToInteger64Impl(const char *const str, const char **endptr,
|
||||
// range error occurs. In this case value MAX(T)/MIN(T) is returned.
|
||||
template<typename T> inline bool StringToNumber(const char *s, T *val) {
|
||||
FLATBUFFERS_ASSERT(s && val);
|
||||
const char *end = nullptr;
|
||||
// The errno check isn't needed. strtoll will return MAX/MIN on overlow.
|
||||
const int64_t i = StringToInteger64Impl<int64_t>(s, &end, -1, false);
|
||||
*val = static_cast<T>(i);
|
||||
const auto done = (s != end) && (*end == '\0');
|
||||
if (done) {
|
||||
int64_t i64;
|
||||
// The errno check isn't needed, will return MAX/MIN on overflow.
|
||||
if (StringToIntegerImpl(&i64, s, 0, false)) {
|
||||
const int64_t max = flatbuffers::numeric_limits<T>::max();
|
||||
const int64_t min = flatbuffers::numeric_limits<T>::lowest();
|
||||
if (i > max) {
|
||||
if (i64 > max) {
|
||||
*val = static_cast<T>(max);
|
||||
return false;
|
||||
}
|
||||
if (i < min) {
|
||||
if (i64 < min) {
|
||||
// For unsigned types return max to distinguish from
|
||||
// "no conversion can be performed" when 0 is returned.
|
||||
*val = static_cast<T>(flatbuffers::is_unsigned<T>::value ? max : min);
|
||||
return false;
|
||||
}
|
||||
*val = static_cast<T>(i64);
|
||||
return true;
|
||||
}
|
||||
return done;
|
||||
}
|
||||
template<> inline bool StringToNumber<int64_t>(const char *s, int64_t *val) {
|
||||
const char *end = s; // request errno checking
|
||||
*val = StringToInteger64Impl<int64_t>(s, &end, -1);
|
||||
return (s != end) && (*end == '\0');
|
||||
}
|
||||
template<> inline bool StringToNumber<uint64_t>(const char *s, uint64_t *val) {
|
||||
const char *end = s; // request errno checking
|
||||
*val = StringToInteger64Impl<uint64_t>(s, &end, -1);
|
||||
return (s != end) && (*end == '\0');
|
||||
*val = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
template<> inline bool StringToNumber<double>(const char *s, double *val) {
|
||||
FLATBUFFERS_ASSERT(s && val);
|
||||
char *end = nullptr;
|
||||
*val = strtod_impl(s, &end);
|
||||
auto done = (s != end) && (*end == '\0');
|
||||
if (!done) *val = 0; // erase partial result
|
||||
return done;
|
||||
template<> inline bool StringToNumber<int64_t>(const char *str, int64_t *val) {
|
||||
return StringToIntegerImpl(val, str);
|
||||
}
|
||||
|
||||
template<> inline bool StringToNumber<float>(const char *s, float *val) {
|
||||
FLATBUFFERS_ASSERT(s && val);
|
||||
char *end = nullptr;
|
||||
*val = strtof_impl(s, &end);
|
||||
auto done = (s != end) && (*end == '\0');
|
||||
if (!done) *val = 0; // erase partial result
|
||||
return done;
|
||||
template<>
|
||||
inline bool StringToNumber<uint64_t>(const char *str, uint64_t *val) {
|
||||
if (!StringToIntegerImpl(val, str)) return false;
|
||||
// The strtoull accepts negative numbers:
|
||||
// If the minus sign was part of the input sequence, the numeric value
|
||||
// calculated from the sequence of digits is negated as if by unary minus
|
||||
// in the result type, which applies unsigned integer wraparound rules.
|
||||
// Fix this behaviour (except -0).
|
||||
if (*val) {
|
||||
auto s = str;
|
||||
while (*s && !is_digit(*s)) s++;
|
||||
s = (s > str) ? (s - 1) : s; // step back to one symbol
|
||||
if (*s == '-') {
|
||||
// For unsigned types return the max to distinguish from
|
||||
// "no conversion can be performed".
|
||||
*val = flatbuffers::numeric_limits<uint64_t>::max();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inline int64_t StringToInt(const char *str, const char **endptr = nullptr,
|
||||
int base = 10) {
|
||||
const char *ep = nullptr;
|
||||
return StringToInteger64Impl<int64_t>(str, endptr ? endptr : &ep, base);
|
||||
template<> inline bool StringToNumber(const char *s, float *val) {
|
||||
return StringToFloatImpl(val, s);
|
||||
}
|
||||
|
||||
inline uint64_t StringToUInt(const char *str, const char **endptr = nullptr,
|
||||
int base = 10) {
|
||||
const char *ep = nullptr;
|
||||
return StringToInteger64Impl<uint64_t>(str, endptr ? endptr : &ep, base);
|
||||
template<> inline bool StringToNumber(const char *s, double *val) {
|
||||
return StringToFloatImpl(val, s);
|
||||
}
|
||||
|
||||
inline int64_t StringToInt(const char *s, int base = 10) {
|
||||
int64_t val;
|
||||
return StringToIntegerImpl(&val, s, base) ? val : 0;
|
||||
}
|
||||
|
||||
inline uint64_t StringToUInt(const char *s, int base = 10) {
|
||||
uint64_t val;
|
||||
return StringToIntegerImpl(&val, s, base) ? val : 0;
|
||||
}
|
||||
|
||||
typedef bool (*LoadFileFunction)(const char *filename, bool binary,
|
||||
@@ -506,6 +524,7 @@ inline void EnsureDirExists(const std::string &filepath) {
|
||||
auto parent = StripFileName(filepath);
|
||||
if (parent.length()) EnsureDirExists(parent);
|
||||
// clang-format off
|
||||
|
||||
#ifdef _WIN32
|
||||
(void)_mkdir(filepath.c_str());
|
||||
#else
|
||||
@@ -518,6 +537,7 @@ inline void EnsureDirExists(const std::string &filepath) {
|
||||
// Returns the input path if the absolute path couldn't be resolved.
|
||||
inline std::string AbsolutePath(const std::string &filepath) {
|
||||
// clang-format off
|
||||
|
||||
#ifdef FLATBUFFERS_NO_ABSOLUTE_PATH_RESOLUTION
|
||||
return filepath;
|
||||
#else
|
||||
@@ -576,7 +596,8 @@ inline int FromUTF8(const char **in) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((static_cast<unsigned char>(**in) << len) & 0x80) return -1; // Bit after leading 1's must be 0.
|
||||
if ((static_cast<unsigned char>(**in) << len) & 0x80)
|
||||
return -1; // Bit after leading 1's must be 0.
|
||||
if (!len) return *(*in)++;
|
||||
// UTF-8 encoded values with a length are between 2 and 4 bytes.
|
||||
if (len < 2 || len > 4) { return -1; }
|
||||
@@ -635,7 +656,7 @@ inline std::string WordWrap(const std::string in, size_t max_length,
|
||||
|
||||
return wrapped;
|
||||
}
|
||||
#endif // !FLATBUFFERS_PREFER_PRINTF
|
||||
#endif // !FLATBUFFERS_PREFER_PRINTF
|
||||
|
||||
inline bool EscapeString(const char *s, size_t length, std::string *_text,
|
||||
bool allow_non_utf8, bool natural_utf8) {
|
||||
@@ -707,6 +728,19 @@ inline bool EscapeString(const char *s, size_t length, std::string *_text,
|
||||
return true;
|
||||
}
|
||||
|
||||
// Remove paired quotes in a string: "text"|'text' -> text.
|
||||
std::string RemoveStringQuotes(const std::string &s);
|
||||
|
||||
// Change th global C-locale to locale with name <locale_name>.
|
||||
// Returns an actual locale name in <_value>, useful if locale_name is "" or
|
||||
// null.
|
||||
bool SetGlobalTestLocale(const char *locale_name,
|
||||
std::string *_value = nullptr);
|
||||
|
||||
// Read (or test) a value of environment variable.
|
||||
bool ReadEnvironmentVariable(const char *var_name,
|
||||
std::string *_value = nullptr);
|
||||
|
||||
} // namespace flatbuffers
|
||||
|
||||
#endif // FLATBUFFERS_UTIL_H_
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Copyright 2014 Google Inc. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -219,7 +219,7 @@ CheckedError Parser::ParseHexNum(int nibbles, uint64_t *val) {
|
||||
return Error("escape code must be followed by " + NumToString(nibbles) +
|
||||
" hex digits");
|
||||
std::string target(cursor_, cursor_ + nibbles);
|
||||
*val = StringToUInt(target.c_str(), nullptr, 16);
|
||||
*val = StringToUInt(target.c_str(), 16);
|
||||
cursor_ += nibbles;
|
||||
return NoError();
|
||||
}
|
||||
|
||||
49
src/util.cpp
49
src/util.cpp
@@ -14,6 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <clocale>
|
||||
|
||||
#include "flatbuffers/util.h"
|
||||
|
||||
namespace flatbuffers {
|
||||
@@ -58,6 +60,7 @@ bool FileExists(const char *name) {
|
||||
|
||||
bool DirExists(const char *name) {
|
||||
// clang-format off
|
||||
|
||||
#ifdef _WIN32
|
||||
#define flatbuffers_stat _stat
|
||||
#define FLATBUFFERS_S_IFDIR _S_IFDIR
|
||||
@@ -85,4 +88,50 @@ FileExistsFunction SetFileExistsFunction(
|
||||
return previous_function;
|
||||
}
|
||||
|
||||
// Locale-independent code.
|
||||
#if defined(FLATBUFFERS_LOCALE_INDEPENDENT) && \
|
||||
(FLATBUFFERS_LOCALE_INDEPENDENT > 0)
|
||||
|
||||
// clang-format off
|
||||
// Allocate locale instance at startup of application.
|
||||
ClassicLocale ClassicLocale::instance_;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
ClassicLocale::ClassicLocale()
|
||||
: locale_(_create_locale(LC_ALL, "C")) {}
|
||||
ClassicLocale::~ClassicLocale() { _free_locale(locale_); }
|
||||
#else
|
||||
ClassicLocale::ClassicLocale()
|
||||
: locale_(newlocale(LC_ALL, "C", nullptr)) {}
|
||||
ClassicLocale::~ClassicLocale() { freelocale(locale_); }
|
||||
#endif
|
||||
// clang-format on
|
||||
|
||||
#endif // !FLATBUFFERS_LOCALE_INDEPENDENT
|
||||
|
||||
std::string RemoveStringQuotes(const std::string &s) {
|
||||
auto ch = *s.c_str();
|
||||
return ((s.size() >= 2) && (ch == '\"' || ch == '\'') &&
|
||||
(ch == string_back(s)))
|
||||
? s.substr(1, s.length() - 2)
|
||||
: s;
|
||||
}
|
||||
|
||||
bool SetGlobalTestLocale(const char *locale_name, std::string *_value) {
|
||||
const auto the_locale = setlocale(LC_ALL, locale_name);
|
||||
if (!the_locale) return false;
|
||||
if (_value) *_value = std::string(the_locale);
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
# pragma warning(disable : 4996) // _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
bool ReadEnvironmentVariable(const char *var_name, std::string *_value) {
|
||||
auto env_str = std::getenv(var_name);
|
||||
if (!env_str) return false;
|
||||
if (_value) *_value = std::string(env_str);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace flatbuffers
|
||||
|
||||
@@ -81,17 +81,6 @@ target_compile_definitions(flatbuffers PRIVATE FLATBUFFERS_MAX_PARSING_DEPTH=8)
|
||||
|
||||
# Setup fuzzer tests.
|
||||
|
||||
# Change default ASCII locale (affects to isalpha, isalnum, decimal
|
||||
# delimiters, other). https://en.cppreference.com/w/cpp/locale/setlocale
|
||||
if(DEFINED FUZZ_TEST_LOCALE)
|
||||
# Enable locale independent code and define locale for tests.
|
||||
# -DFUZZ_TEST_LOCALE="" - enable, but test with default locale
|
||||
# -DFUZZ_TEST_LOCALE="ru_RU.CP1251" - enable and test with ru_RU.CP1251
|
||||
# Locale was installed before (Ubuntu):>sudo locale-gen ru_RU.CP1251
|
||||
add_definitions(-DFUZZ_TEST_LOCALE=\"${FUZZ_TEST_LOCALE}\")
|
||||
endif()
|
||||
message(STATUS "FUZZ_TEST_LOCALE: ${FUZZ_TEST_LOCALE}")
|
||||
|
||||
add_executable(scalar_fuzzer flatbuffers_scalar_fuzzer.cc)
|
||||
target_link_libraries(scalar_fuzzer PRIVATE flatbuffers)
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <string>
|
||||
|
||||
#include "flatbuffers/idl.h"
|
||||
#include "test_init.h"
|
||||
|
||||
static constexpr uint8_t flags_strict_json = 0x01;
|
||||
static constexpr uint8_t flags_skip_unexpected_fields_in_json = 0x02;
|
||||
@@ -17,12 +18,8 @@ static constexpr uint8_t flags_allow_non_utf8 = 0x04;
|
||||
// static constexpr uint8_t flags_flag_6 = 0x40;
|
||||
// static constexpr uint8_t flags_flag_7 = 0x80;
|
||||
|
||||
// See readme.md and CMakeLists.txt for details.
|
||||
#ifdef FUZZ_TEST_LOCALE
|
||||
static constexpr const char *test_locale = (FUZZ_TEST_LOCALE);
|
||||
#else
|
||||
static constexpr const char *test_locale = nullptr;
|
||||
#endif
|
||||
// Utility for test run.
|
||||
OneTimeTestInit OneTimeTestInit::one_time_init_;
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
// Reserve one byte for Parser flags and one byte for repetition counter.
|
||||
@@ -52,17 +49,18 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
// Each test should pass at least two times to ensure that the parser doesn't
|
||||
// have any hidden-states or locale-depended effects.
|
||||
for (auto cnt = 0; cnt < (extra_rep_number + 2); cnt++) {
|
||||
auto use_locale = !!test_locale && (0 == (cnt % 2));
|
||||
// Each even run (0,2,4..) will test locale independed code.
|
||||
auto use_locale = !!OneTimeTestInit::test_locale() && (0 == (cnt % 2));
|
||||
// Set new locale.
|
||||
if (use_locale) {
|
||||
FLATBUFFERS_ASSERT(!!std::setlocale(LC_ALL, test_locale));
|
||||
FLATBUFFERS_ASSERT(setlocale(LC_ALL, OneTimeTestInit::test_locale()));
|
||||
}
|
||||
|
||||
// Check Parser.
|
||||
parser.Parse(parse_input);
|
||||
|
||||
// Restore locale.
|
||||
if (use_locale) { FLATBUFFERS_ASSERT(!!std::setlocale(LC_ALL, "C")); }
|
||||
if (use_locale) { FLATBUFFERS_ASSERT(setlocale(LC_ALL, "C")); }
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -8,55 +8,13 @@
|
||||
#include <string>
|
||||
|
||||
#include "flatbuffers/idl.h"
|
||||
#include "fuzzer_assert.h"
|
||||
#include "test_assert.h"
|
||||
|
||||
static_assert(__has_feature(memory_sanitizer) ||
|
||||
__has_feature(address_sanitizer),
|
||||
"sanitizer disabled");
|
||||
#include "test_init.h"
|
||||
|
||||
static constexpr uint8_t flags_scalar_type = 0x0F; // type of scalar value
|
||||
static constexpr uint8_t flags_quotes_kind = 0x10; // quote " or '
|
||||
// reserved for future: json {named} or [unnamed]
|
||||
// static constexpr uint8_t flags_json_bracer = 0x20;
|
||||
|
||||
// See readme.md and CMakeLists.txt for details.
|
||||
#ifdef FUZZ_TEST_LOCALE
|
||||
static constexpr const char *test_locale = (FUZZ_TEST_LOCALE);
|
||||
#else
|
||||
static constexpr const char *test_locale = nullptr;
|
||||
#endif
|
||||
|
||||
// Utility for test run.
|
||||
struct OneTimeTestInit {
|
||||
// Declare trap for the flatbuffers test engine.
|
||||
// This hook terminate program both in Debug and Release.
|
||||
static bool TestFailListener(const char *expval, const char *val,
|
||||
const char *exp, const char *file, int line,
|
||||
const char *func = 0) {
|
||||
(void)expval;
|
||||
(void)val;
|
||||
(void)exp;
|
||||
(void)file;
|
||||
(void)line;
|
||||
(void)func;
|
||||
// FLATBUFFERS_ASSERT also redefined to be fully independed from library
|
||||
// implementation (see test_assert.h for details).
|
||||
fuzzer_assert_impl(false); // terminate
|
||||
return false;
|
||||
}
|
||||
|
||||
OneTimeTestInit() {
|
||||
// Fuzzer test should not depend from the test engine implementation.
|
||||
// This hook will terminate test if TEST_EQ/TEST_ASSERT asserted.
|
||||
InitTestEngine(OneTimeTestInit::TestFailListener);
|
||||
}
|
||||
|
||||
static OneTimeTestInit one_time_init_;
|
||||
};
|
||||
|
||||
OneTimeTestInit OneTimeTestInit::one_time_init_;
|
||||
|
||||
// Find all 'subj' sub-strings and replace first character of sub-string.
|
||||
// BreakSequence("testest","tes", 'X') -> "XesXest".
|
||||
// BreakSequence("xxx","xx", 'Y') -> "YYx".
|
||||
@@ -248,6 +206,9 @@ bool Parse(flatbuffers::Parser &parser, const std::string &json,
|
||||
return done;
|
||||
}
|
||||
|
||||
// Utility for test run.
|
||||
OneTimeTestInit OneTimeTestInit::one_time_init_;
|
||||
|
||||
// llvm std::regex have problem with stack overflow, limit maximum length.
|
||||
// ./scalar_fuzzer -max_len=3000
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
@@ -299,23 +260,26 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
// have any hidden-states or locale-depended effects.
|
||||
for (auto cnt = 0; cnt < (extra_rep_number + 2); cnt++) {
|
||||
// Each even run (0,2,4..) will test locale independed code.
|
||||
auto use_locale = !!test_locale && (0 == (cnt % 2));
|
||||
auto use_locale = !!OneTimeTestInit::test_locale() && (0 == (cnt % 2));
|
||||
// Set new locale.
|
||||
if (use_locale) {
|
||||
FLATBUFFERS_ASSERT(!!std::setlocale(LC_ALL, test_locale));
|
||||
FLATBUFFERS_ASSERT(setlocale(LC_ALL, OneTimeTestInit::test_locale()));
|
||||
}
|
||||
|
||||
// Parse original input as-is.
|
||||
auto orig_scalar = "{ \"Y\" : " + input + " }";
|
||||
std::string orig_back;
|
||||
auto orig_done = Parse(parser, orig_scalar, &orig_back);
|
||||
|
||||
if (recheck.res != orig_done) {
|
||||
// look for "does not fit" or "doesn't fit" or "out of range"
|
||||
auto parser_not_fit =
|
||||
(orig_back.find("does not fit") == std::string::npos) ||
|
||||
(orig_back.find("out of range") == std::string::npos);
|
||||
auto not_fit =
|
||||
(true == recheck.res)
|
||||
? ((orig_back.find("does not fit") != std::string::npos) ||
|
||||
(orig_back.find("out of range") != std::string::npos))
|
||||
: false;
|
||||
|
||||
if ((false == recheck.res) || (false == parser_not_fit)) {
|
||||
if (false == not_fit) {
|
||||
TEST_OUTPUT_LINE("Stage 1 failed: Parser(%d) != Regex(%d)", orig_done,
|
||||
recheck.res);
|
||||
TEST_EQ_STR(orig_back.c_str(),
|
||||
@@ -344,6 +308,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
auto fix_scalar = "{ \"Y\" : " + qouted_input + " }";
|
||||
std::string fix_back;
|
||||
auto fix_done = Parse(parser, fix_scalar, &fix_back);
|
||||
|
||||
if (orig_done != fix_done) {
|
||||
TEST_OUTPUT_LINE("Stage 2 failed: Parser(%d) != Regex(%d)", fix_done,
|
||||
orig_done);
|
||||
@@ -353,9 +318,34 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
TEST_EQ_FUNC(fix_done, orig_done);
|
||||
}
|
||||
|
||||
// Restore locale.
|
||||
if (use_locale) { FLATBUFFERS_ASSERT(!!std::setlocale(LC_ALL, "C")); }
|
||||
}
|
||||
// Create new parser and test default value
|
||||
if (true == orig_done) {
|
||||
flatbuffers::Parser def_parser(opts); // re-use options
|
||||
auto def_schema = "table X { Y: " + std::string(ref_res.type) + " = " +
|
||||
input + "; } root_type X;" +
|
||||
"{}"; // <- with empty json {}!
|
||||
|
||||
auto def_done = def_parser.Parse(def_schema.c_str());
|
||||
if (false == def_done) {
|
||||
TEST_OUTPUT_LINE("Stage 3.1 failed with _error = %s",
|
||||
def_parser.error_.c_str());
|
||||
FLATBUFFERS_ASSERT(false);
|
||||
}
|
||||
// Compare with print.
|
||||
std::string ref_string, def_string;
|
||||
FLATBUFFERS_ASSERT(GenerateText(
|
||||
parser, parser.builder_.GetBufferPointer(), &ref_string));
|
||||
FLATBUFFERS_ASSERT(GenerateText(
|
||||
def_parser, def_parser.builder_.GetBufferPointer(), &def_string));
|
||||
if (ref_string != def_string) {
|
||||
TEST_OUTPUT_LINE("Stage 3.2 failed: '%s' != '%s'", def_string.c_str(),
|
||||
ref_string.c_str());
|
||||
FLATBUFFERS_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Restore locale.
|
||||
if (use_locale) { FLATBUFFERS_ASSERT(setlocale(LC_ALL, "C")); }
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -15,27 +15,19 @@ The fuzzer section include three tests:
|
||||
- `parser_fuzzer` checks stability of schema and json parser under various inputs;
|
||||
- `scalar_parser` focused on validation of the parser while parse numeric scalars in schema and/or json files;
|
||||
|
||||
## Build tests with locales
|
||||
Flatbuffers library use only printable-ASCII characters as characters of grammar alphabet for type and data declaration.
|
||||
This alphabet is fully compatible with JSON specification and make schema declaration fully portable.
|
||||
Flatbuffers library is independent from global or thread locales used by end-user application.
|
||||
To run fuzzer tests with selected C-locale under test pass `-DFUZZ_TEST_LOCALE="<locale name>"` to CMake when configuring.
|
||||
Selected locale must be installed in system before use.
|
||||
Command line:
|
||||
## Run tests with a specific locale
|
||||
The grammar of the Flatbuffers library is based on printable-ASCII characters.
|
||||
By design, the Flatbuffers library should be independent of the global or thread locales used by an end-user application.
|
||||
Set environment variable `FLATBUFFERS_TEST_LOCALE` to run a fuzzer with a specific C-locale:
|
||||
```sh
|
||||
cmake .. -DFUZZ_TEST_LOCALE="ru_RU.CP1251"
|
||||
```
|
||||
If use VSCode, use `cmake.configureSettings` section of workspace settings:
|
||||
```json
|
||||
"cmake.configureSettings": {
|
||||
"FUZZ_TEST_LOCALE" : "ru_RU.CP1251"
|
||||
}
|
||||
>FLATBUFFERS_TEST_LOCALE="" ./scalar_parser
|
||||
>FLATBUFFERS_TEST_LOCALE="ru_RU.CP1251" ./parser_fuzzer
|
||||
```
|
||||
|
||||
## Run fuzzer
|
||||
These are examples of fuzzer run.
|
||||
Flags may vary and depend from version of libFuzzer library.
|
||||
For detail, run a fuzzer test with help flag: `./parser_fuzzer -help=1`
|
||||
These are examples of running a fuzzer.
|
||||
Flags may vary and depend on a version of the libFuzzer library.
|
||||
For details, run a fuzzer with `-help` flag: `./parser_fuzzer -help=1`
|
||||
|
||||
`./verifier_fuzzer -reduce_depth=1 -use_value_profile=1 -shrink=1 ../.corpus_verifier/`
|
||||
|
||||
@@ -43,18 +35,20 @@ For detail, run a fuzzer test with help flag: `./parser_fuzzer -help=1`
|
||||
|
||||
`./scalar_fuzzer -reduce_depth=1 -use_value_profile=1 -shrink=1 -max_len=3000 ../.corpus_parser/ ../.seed_parser/`
|
||||
|
||||
Flag `-only_ascii=1` is useful for fast number-compatibility checking while run `scalar_fuzzer`:
|
||||
|
||||
Flag `-only_ascii=1` is useful for fast number-compatibility checking while run `scalar_fuzzer`:
|
||||
`./scalar_fuzzer -only_ascii=1 -reduce_depth=1 -use_value_profile=1 -shrink=1 -max_len=3000 -timeout=10 -rss_limit_mb=2048 -jobs=2 ../.corpus_parser/ ../.seed_parser/`
|
||||
|
||||
Run with a specific C-locale:
|
||||
`FLATBUFFERS_TEST_LOCALE="ru_RU.CP1251" ./scalar_fuzzer -reduce_depth=1 -use_value_profile=1 -shrink=1 -max_len=3000 -timeout=10 -rss_limit_mb=2048 ../.corpus_parser/ ../.seed_parser/`
|
||||
|
||||
## Merge (minimize) corpus
|
||||
The **libFuzzer** allow to filter (minimize) corpus with help of `-merge` flag:
|
||||
> -merge
|
||||
If set to 1, any corpus inputs from the 2nd, 3rd etc. corpus directories that trigger new code coverage will be merged into the first corpus directory.
|
||||
Defaults to 0. This flag can be used to minimize a corpus.
|
||||
|
||||
Merge several seeds to one:
|
||||
`./scalar_fuzzer -merge=1 ../.corpus/ ../.seed_1/ ../.seed_2/`
|
||||
Merge several seeds to one (a new collected corpus to the seed collection, for example):
|
||||
`./scalar_fuzzer -merge=1 ../.seed_parser/ ../.corpus_parser/`
|
||||
|
||||
## Know limitations
|
||||
- LLVM 7.0 std::regex library has problem with stack overflow, maximum length of input for `scalar_fuzzer` run should be limited to 3000.
|
||||
|
||||
56
tests/fuzzer/test_init.h
Normal file
56
tests/fuzzer/test_init.h
Normal file
@@ -0,0 +1,56 @@
|
||||
|
||||
#ifndef FUZZER_TEST_INIT_H_
|
||||
#define FUZZER_TEST_INIT_H_
|
||||
|
||||
#include "fuzzer_assert.h"
|
||||
#include "test_assert.h"
|
||||
|
||||
static_assert(__has_feature(memory_sanitizer) ||
|
||||
__has_feature(address_sanitizer),
|
||||
"sanitizer disabled");
|
||||
|
||||
// Utility for test run.
|
||||
struct OneTimeTestInit {
|
||||
// Declare trap for the Flatbuffers test engine.
|
||||
// This hook terminate program both in Debug and Release.
|
||||
static bool TestFailListener(const char *expval, const char *val,
|
||||
const char *exp, const char *file, int line,
|
||||
const char *func = 0) {
|
||||
(void)expval;
|
||||
(void)val;
|
||||
(void)exp;
|
||||
(void)file;
|
||||
(void)line;
|
||||
(void)func;
|
||||
// FLATBUFFERS_ASSERT redefined to be fully independent of the Flatbuffers
|
||||
// library implementation (see test_assert.h for details).
|
||||
fuzzer_assert_impl(false); // terminate
|
||||
return false;
|
||||
}
|
||||
|
||||
OneTimeTestInit() : has_locale_(false) {
|
||||
// Fuzzer test should be independent of the test engine implementation.
|
||||
// This hook will terminate test if TEST_EQ/TEST_ASSERT asserted.
|
||||
InitTestEngine(OneTimeTestInit::TestFailListener);
|
||||
|
||||
// Read a locale for the test.
|
||||
if (flatbuffers::ReadEnvironmentVariable("FLATBUFFERS_TEST_LOCALE",
|
||||
&test_locale_)) {
|
||||
TEST_OUTPUT_LINE("The environment variable FLATBUFFERS_TEST_LOCALE=%s",
|
||||
test_locale_.c_str());
|
||||
test_locale_ = flatbuffers::RemoveStringQuotes(test_locale_);
|
||||
has_locale_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
static const char *test_locale() {
|
||||
return one_time_init_.has_locale_ ? nullptr
|
||||
: one_time_init_.test_locale_.c_str();
|
||||
}
|
||||
|
||||
bool has_locale_;
|
||||
std::string test_locale_;
|
||||
static OneTimeTestInit one_time_init_;
|
||||
};
|
||||
|
||||
#endif // !FUZZER_TEST_INIT_H_
|
||||
@@ -1535,7 +1535,7 @@ void ValidFloatTest() {
|
||||
// Old MSVC versions may have problem with this check.
|
||||
// https://www.exploringbinary.com/visual-c-plus-plus-strtod-still-broken/
|
||||
TEST_EQ(TestValue<double>("{ Y:6.9294956446009195e15 }", "double"),
|
||||
6929495644600920);
|
||||
6929495644600920.0);
|
||||
// check nan's
|
||||
TEST_EQ(std::isnan(TestValue<double>("{ Y:nan }", "double")), true);
|
||||
TEST_EQ(std::isnan(TestValue<float>("{ Y:nan }", "float")), true);
|
||||
@@ -1663,6 +1663,7 @@ void NumericUtilsTestInteger(const char *lower, const char *upper) {
|
||||
template<typename T>
|
||||
void NumericUtilsTestFloat(const char *lower, const char *upper) {
|
||||
T f;
|
||||
TEST_EQ(flatbuffers::StringToNumber("", &f), false);
|
||||
TEST_EQ(flatbuffers::StringToNumber("1q", &f), false);
|
||||
TEST_EQ(f, 0);
|
||||
TEST_EQ(flatbuffers::StringToNumber(upper, &f), true);
|
||||
@@ -2458,6 +2459,18 @@ int FlatBufferTests() {
|
||||
int main(int /*argc*/, const char * /*argv*/ []) {
|
||||
InitTestEngine();
|
||||
|
||||
std::string req_locale;
|
||||
if (flatbuffers::ReadEnvironmentVariable("FLATBUFFERS_TEST_LOCALE",
|
||||
&req_locale)) {
|
||||
TEST_OUTPUT_LINE("The environment variable FLATBUFFERS_TEST_LOCALE=%s",
|
||||
req_locale.c_str());
|
||||
req_locale = flatbuffers::RemoveStringQuotes(req_locale);
|
||||
std::string the_locale;
|
||||
TEST_ASSERT_FUNC(
|
||||
flatbuffers::SetGlobalTestLocale(req_locale.c_str(), &the_locale));
|
||||
TEST_OUTPUT_LINE("The global C-locale changed: %s", the_locale.c_str());
|
||||
}
|
||||
|
||||
FlatBufferTests();
|
||||
FlatBufferBuilderTest();
|
||||
|
||||
|
||||
@@ -17,10 +17,9 @@ void TestFail(const char *expval, const char *val, const char *exp,
|
||||
testing_fails++;
|
||||
|
||||
// Notify, emulate 'gtest::OnTestPartResult' event handler.
|
||||
if(fail_listener_)
|
||||
(*fail_listener_)(expval, val, exp, file, line, func);
|
||||
if (fail_listener_) (*fail_listener_)(expval, val, exp, file, line, func);
|
||||
|
||||
assert(0); // ignored in Release if NDEBUG defined
|
||||
assert(0); // ignored in Release if NDEBUG defined
|
||||
}
|
||||
|
||||
void TestEqStr(const char *expval, const char *val, const char *exp,
|
||||
|
||||
@@ -1,18 +1,33 @@
|
||||
#ifndef TEST_ASSERT_H
|
||||
#define TEST_ASSERT_H
|
||||
|
||||
#include "flatbuffers/flatbuffers.h"
|
||||
#include "flatbuffers/util.h"
|
||||
|
||||
// clang-format off
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <android/log.h>
|
||||
#include <android/log.h>
|
||||
#define TEST_OUTPUT_LINE(...) \
|
||||
__android_log_print(ANDROID_LOG_INFO, "FlatBuffers", __VA_ARGS__)
|
||||
__android_log_print(ANDROID_LOG_INFO, "FlatBuffers", __VA_ARGS__)
|
||||
#define FLATBUFFERS_NO_FILE_TESTS
|
||||
#else
|
||||
#define TEST_OUTPUT_LINE(...) \
|
||||
{ printf(__VA_ARGS__); printf("\n"); }
|
||||
#define TEST_OUTPUT_LINE(...) \
|
||||
{ printf(__VA_ARGS__); printf("\n"); }
|
||||
#endif
|
||||
|
||||
#define TEST_EQ(exp, val) TestEq(exp, val, #exp, __FILE__, __LINE__)
|
||||
#define TEST_ASSERT(exp) TestEq(exp, true, #exp, __FILE__, __LINE__)
|
||||
#define TEST_NOTNULL(exp) TestEq(exp == NULL, false, #exp, __FILE__, __LINE__)
|
||||
#define TEST_EQ_STR(exp, val) TestEqStr(exp, val, #exp, __FILE__, __LINE__)
|
||||
|
||||
#ifdef WIN32
|
||||
#define TEST_ASSERT_FUNC(exp) TestEq(exp, true, #exp, __FILE__, __LINE__, __FUNCTION__)
|
||||
#define TEST_EQ_FUNC(exp, val) TestEq(exp, val, #exp, __FILE__, __LINE__, __FUNCTION__)
|
||||
#else
|
||||
#define TEST_ASSERT_FUNC(exp) TestEq(exp, true, #exp, __FILE__, __LINE__, __PRETTY_FUNCTION__)
|
||||
#define TEST_EQ_FUNC(exp, val) TestEq(exp, val, #exp, __FILE__, __LINE__, __PRETTY_FUNCTION__)
|
||||
#endif
|
||||
|
||||
// clang-format on
|
||||
|
||||
extern int testing_fails;
|
||||
@@ -20,8 +35,8 @@ extern int testing_fails;
|
||||
// Listener of TestFail, like 'gtest::OnTestPartResult' event handler.
|
||||
// Called in TestFail after a failed assertion.
|
||||
typedef bool (*TestFailEventListener)(const char *expval, const char *val,
|
||||
const char *exp, const char *file, int line,
|
||||
const char *func);
|
||||
const char *exp, const char *file,
|
||||
int line, const char *func);
|
||||
|
||||
// Prepare test engine (MSVC assertion setup, etc).
|
||||
// listener - this function will be notified on each TestFail call.
|
||||
@@ -35,23 +50,12 @@ void TestEqStr(const char *expval, const char *val, const char *exp,
|
||||
const char *file, int line);
|
||||
|
||||
template<typename T, typename U>
|
||||
void TestEq(T expval, U val, const char *exp, const char *file, int line, const char *func = 0) {
|
||||
void TestEq(T expval, U val, const char *exp, const char *file, int line,
|
||||
const char *func = 0) {
|
||||
if (U(expval) != val) {
|
||||
TestFail(flatbuffers::NumToString(expval).c_str(),
|
||||
flatbuffers::NumToString(val).c_str(), exp, file, line, func);
|
||||
}
|
||||
}
|
||||
|
||||
#define TEST_EQ(exp, val) TestEq(exp, val, #exp, __FILE__, __LINE__)
|
||||
#define TEST_ASSERT(exp) TestEq(exp, true, #exp, __FILE__, __LINE__)
|
||||
#ifdef WIN32
|
||||
#define TEST_ASSERT_FUNC(exp) TestEq(exp, true, #exp, __FILE__, __LINE__, __FUNCTION__)
|
||||
#define TEST_EQ_FUNC(exp, val) TestEq(exp, val, #exp, __FILE__, __LINE__, __FUNCTION__)
|
||||
#else
|
||||
#define TEST_ASSERT_FUNC(exp) TestEq(exp, true, #exp, __FILE__, __LINE__, __PRETTY_FUNCTION__)
|
||||
#define TEST_EQ_FUNC(exp, val) TestEq(exp, val, #exp, __FILE__, __LINE__, __PRETTY_FUNCTION__)
|
||||
#endif
|
||||
#define TEST_NOTNULL(exp) TestEq(exp == NULL, false, #exp, __FILE__, __LINE__)
|
||||
#define TEST_EQ_STR(exp, val) TestEqStr(exp, val, #exp, __FILE__, __LINE__)
|
||||
|
||||
#endif // TEST_ASSERT_H
|
||||
#endif // !TEST_ASSERT_H
|
||||
|
||||
Reference in New Issue
Block a user