Commit 5f32f948 authored by Vladimir Glavnyy's avatar Vladimir Glavnyy Committed by Wouter van Oortmerssen

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
parent d6b1ce09
...@@ -2,6 +2,12 @@ env: ...@@ -2,6 +2,12 @@ env:
global: global:
# Set at the root level as this is ignored when set under matrix.env. # Set at the root level as this is ignored when set under matrix.env.
- GCC_VERSION="4.9" - 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 conan-linux: &conan-linux
os: linux os: linux
...@@ -79,8 +85,8 @@ matrix: ...@@ -79,8 +85,8 @@ matrix:
-DGRPC_INSTALL_PATH=$TRAVIS_BUILD_DIR/google/grpc/install -DGRPC_INSTALL_PATH=$TRAVIS_BUILD_DIR/google/grpc/install
-DPROTOBUF_DOWNLOAD_PATH=$TRAVIS_BUILD_DIR/google/grpc/third_party/protobuf -DPROTOBUF_DOWNLOAD_PATH=$TRAVIS_BUILD_DIR/google/grpc/third_party/protobuf
-DFLATBUFFERS_CODE_SANITIZE=ON -DFLATBUFFERS_CODE_SANITIZE=ON
- make - cmake --build . -- -j${JOBS}
- LD_LIBRARY_PATH=$TRAVIS_BUILD_DIR/google/grpc/install/lib make test ARGS=-V - LD_LIBRARY_PATH=$TRAVIS_BUILD_DIR/google/grpc/install/lib ctest --extra-verbose --output-on-failure
- bash .travis/check-generate-code.sh - 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 - 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: ...@@ -91,6 +97,7 @@ matrix:
matrix: matrix:
- BUILD_TYPE=Debug - BUILD_TYPE=Debug
- BUILD_TYPE=Release - BUILD_TYPE=Release
script: script:
- bash grpc/build_grpc.sh - bash grpc/build_grpc.sh
- cmake . - cmake .
...@@ -99,10 +106,9 @@ matrix: ...@@ -99,10 +106,9 @@ matrix:
-DGRPC_INSTALL_PATH=$TRAVIS_BUILD_DIR/google/grpc/install -DGRPC_INSTALL_PATH=$TRAVIS_BUILD_DIR/google/grpc/install
-DPROTOBUF_DOWNLOAD_PATH=$TRAVIS_BUILD_DIR/google/grpc/third_party/protobuf -DPROTOBUF_DOWNLOAD_PATH=$TRAVIS_BUILD_DIR/google/grpc/third_party/protobuf
-DFLATBUFFERS_CODE_SANITIZE=ON -DFLATBUFFERS_CODE_SANITIZE=ON
- make - cmake --build . -- -j${JOBS}
- ./flattests - DYLD_LIBRARY_PATH=$TRAVIS_BUILD_DIR/google/grpc/install/lib ctest --extra-verbose --output-on-failure
- bash .travis/check-generate-code.sh - bash .travis/check-generate-code.sh
- DYLD_LIBRARY_PATH=$TRAVIS_BUILD_DIR/google/grpc/install/lib ./grpctest
- <<: *conan-linux - <<: *conan-linux
env: CONAN_GCC_VERSIONS=4.9 CONAN_DOCKER_IMAGE=conanio/gcc49 env: CONAN_GCC_VERSIONS=4.9 CONAN_DOCKER_IMAGE=conanio/gcc49
...@@ -149,6 +155,7 @@ matrix: ...@@ -149,6 +155,7 @@ matrix:
- extra-android-m2repository - extra-android-m2repository
compiler: compiler:
- gcc - gcc
before_install: before_install:
- git clone https://github.com/urho3d/android-ndk.git $HOME/android-ndk-root - git clone https://github.com/urho3d/android-ndk.git $HOME/android-ndk-root
- export ANDROID_NDK_HOME=$HOME/android-ndk-root - export ANDROID_NDK_HOME=$HOME/android-ndk-root
......
cmake_minimum_required(VERSION 2.8) cmake_minimum_required(VERSION 2.8)
# generate compile_commands.json # generate compile_commands.json
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
include(CheckCXXSymbolExists)
project(FlatBuffers) project(FlatBuffers)
...@@ -35,6 +36,16 @@ if(DEFINED FLATBUFFERS_MAX_PARSING_DEPTH) ...@@ -35,6 +36,16 @@ if(DEFINED FLATBUFFERS_MAX_PARSING_DEPTH)
message(STATUS "FLATBUFFERS_MAX_PARSING_DEPTH: ${FLATBUFFERS_MAX_PARSING_DEPTH}") message(STATUS "FLATBUFFERS_MAX_PARSING_DEPTH: ${FLATBUFFERS_MAX_PARSING_DEPTH}")
endif() 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 set(FlatBuffers_Library_SRCS
include/flatbuffers/code_generators.h include/flatbuffers/code_generators.h
include/flatbuffers/base.h include/flatbuffers/base.h
...@@ -213,6 +224,7 @@ function(add_fsanitize_to_target _target _sanitizer) ...@@ -213,6 +224,7 @@ function(add_fsanitize_to_target _target _sanitizer)
target_link_libraries(${_target} PRIVATE target_link_libraries(${_target} PRIVATE
"-fsanitize${_sanitizer_flags}") "-fsanitize${_sanitizer_flags}")
set_property(TARGET ${_target} PROPERTY POSITION_INDEPENDENT_CODE ON) set_property(TARGET ${_target} PROPERTY POSITION_INDEPENDENT_CODE ON)
message(STATUS "Sanitizer ${_sanitizer_flags} added to ${_target}")
endif() endif()
endfunction() endfunction()
......
...@@ -499,11 +499,47 @@ To use scalars, simply wrap them in a struct. ...@@ -499,11 +499,47 @@ To use scalars, simply wrap them in a struct.
## Depth limit of nested objects and stack-overflow control ## Depth limit of nested objects and stack-overflow control
The parser of Flatbuffers schema or json-files is kind of recursive parser. 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. To avoid stack-overflow problem the parser has a built-in limiter of
Number of nested declarations in a schema or number of nested json-objects is limited. recursion depth. Number of nested declarations in a schema or number of
By default, this depth limit set to `64`. 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. It is possible to override this limit with `FLATBUFFERS_MAX_PARSING_DEPTH`
This definition can be helpful for testing purposes or embedded applications. definition. This definition can be helpful for testing purposes or embedded
For details see [build](@ref flatbuffers_guide_building) of CMake-based projects. 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> <br>
...@@ -195,15 +195,35 @@ ...@@ -195,15 +195,35 @@
#endif #endif
#endif // !FLATBUFFERS_HAS_NEW_STRTOD #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__) #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) #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 #else
#define __no_sanitize_undefined__(reason) #define __supress_ubsan__(type)
#endif #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 /// @endcond
/// @file /// @file
...@@ -287,13 +307,15 @@ template<typename T> T EndianScalar(T t) { ...@@ -287,13 +307,15 @@ template<typename T> T EndianScalar(T t) {
} }
template<typename 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) { T ReadScalar(const void *p) {
return EndianScalar(*reinterpret_cast<const T *>(p)); return EndianScalar(*reinterpret_cast<const T *>(p));
} }
template<typename 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")
void WriteScalar(void *p, T t) { void WriteScalar(void *p, T t) {
*reinterpret_cast<T *>(p) = EndianScalar(t); *reinterpret_cast<T *>(p) = EndianScalar(t);
} }
......
...@@ -17,18 +17,15 @@ ...@@ -17,18 +17,15 @@
#ifndef FLATBUFFERS_UTIL_H_ #ifndef FLATBUFFERS_UTIL_H_
#define FLATBUFFERS_UTIL_H_ #define FLATBUFFERS_UTIL_H_
#include <errno.h> // clang-format off
#include <stdint.h>
#include <stdlib.h>
#include <fstream>
#include <iomanip>
#ifndef FLATBUFFERS_PREFER_PRINTF #ifndef FLATBUFFERS_PREFER_PRINTF
# include <sstream> # include <sstream>
#else // FLATBUFFERS_PREFER_PRINTF #else // FLATBUFFERS_PREFER_PRINTF
# include <float.h> # include <float.h>
# include <stdio.h> # include <stdio.h>
#endif // FLATBUFFERS_PREFER_PRINTF #endif // FLATBUFFERS_PREFER_PRINTF
#include <string>
#ifdef _WIN32 #ifdef _WIN32
# ifndef WIN32_LEAN_AND_MEAN # ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN
...@@ -43,18 +40,21 @@ ...@@ -43,18 +40,21 @@
#else #else
# include <limits.h> # include <limits.h>
#endif #endif
// clang-format on
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <iomanip>
#include <fstream>
#include "flatbuffers/base.h" #include "flatbuffers/base.h"
namespace flatbuffers { 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. // @locale-independent functions for ASCII characters set.
// Check that integer scalar is in closed range: (a <= x <= b) // 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) { ...@@ -67,13 +67,13 @@ template<typename T> inline bool check_in_range(T x, T a, T b) {
} }
// Case-insensitive isalpha // 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). // ASCII only: alpha to upper case => reset bit 0x20 (~0x20 = 0xDF).
return check_in_range(c & 0xDF, 'a' & 0xDF, 'z' & 0xDF); return check_in_range(c & 0xDF, 'a' & 0xDF, 'z' & 0xDF);
} }
// Check (case-insensitive) that `c` is equal to alpha. // 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)); FLATBUFFERS_ASSERT(is_alpha(alpha));
// ASCII only: alpha to upper case => reset bit 0x20 (~0x20 = 0xDF). // ASCII only: alpha to upper case => reset bit 0x20 (~0x20 = 0xDF).
return ((c & 0xDF) == (alpha & 0xDF)); return ((c & 0xDF) == (alpha & 0xDF));
...@@ -84,15 +84,15 @@ static inline bool is_alpha_char(char c, char alpha) { ...@@ -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 // functions that are not affected by the currently installed C locale. although
// some implementations (e.g. Microsoft in 1252 codepage) may classify // some implementations (e.g. Microsoft in 1252 codepage) may classify
// additional single-byte characters as digits. // 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. // Replace by look-up table.
return is_digit(c) | check_in_range(c & 0xDF, 'a' & 0xDF, 'f' & 0xDF); return is_digit(c) | check_in_range(c & 0xDF, 'a' & 0xDF, 'f' & 0xDF);
} }
// Case-insensitive isalnum // 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 // @end-locale-independent functions for ASCII character set
...@@ -119,12 +119,12 @@ template<typename T> size_t NumToStringWidth(T t, int precision = 0) { ...@@ -119,12 +119,12 @@ template<typename T> size_t NumToStringWidth(T t, int precision = 0) {
return string_width; return string_width;
} }
template<typename T> std::string NumToStringImplWrapper(T t, const char* fmt, template<typename T>
int precision = 0) { std::string NumToStringImplWrapper(T t, const char *fmt, int precision = 0) {
size_t string_width = NumToStringWidth(t, precision); size_t string_width = NumToStringWidth(t, precision);
std::string s(string_width, 0x00); std::string s(string_width, 0x00);
// Allow snprintf to use std::string trailing null to detect buffer overflow // 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; return s;
} }
#endif // FLATBUFFERS_PREFER_PRINTF #endif // FLATBUFFERS_PREFER_PRINTF
...@@ -134,6 +134,7 @@ template<typename T> std::string NumToStringImplWrapper(T t, const char* fmt, ...@@ -134,6 +134,7 @@ template<typename T> std::string NumToStringImplWrapper(T t, const char* fmt,
// converted to a string of digits, and we don't use scientific notation. // converted to a string of digits, and we don't use scientific notation.
template<typename T> std::string NumToString(T t) { template<typename T> std::string NumToString(T t) {
// clang-format off // clang-format off
#ifndef FLATBUFFERS_PREFER_PRINTF #ifndef FLATBUFFERS_PREFER_PRINTF
std::stringstream ss; std::stringstream ss;
ss << t; ss << t;
...@@ -169,6 +170,7 @@ inline std::string NumToString<unsigned long long>(unsigned long long t) { ...@@ -169,6 +170,7 @@ inline std::string NumToString<unsigned long long>(unsigned long long t) {
// Special versions for floats/doubles. // Special versions for floats/doubles.
template<typename T> std::string FloatToString(T t, int precision) { template<typename T> std::string FloatToString(T t, int precision) {
// clang-format off // clang-format off
#ifndef FLATBUFFERS_PREFER_PRINTF #ifndef FLATBUFFERS_PREFER_PRINTF
// to_string() prints different numbers of digits for floats depending on // to_string() prints different numbers of digits for floats depending on
// platform and isn't available on Android, so we use stringstream // platform and isn't available on Android, so we use stringstream
...@@ -206,6 +208,7 @@ template<> inline std::string NumToString<float>(float t) { ...@@ -206,6 +208,7 @@ template<> inline std::string NumToString<float>(float t) {
inline std::string IntToStringHex(int i, int xdigits) { inline std::string IntToStringHex(int i, int xdigits) {
FLATBUFFERS_ASSERT(i >= 0); FLATBUFFERS_ASSERT(i >= 0);
// clang-format off // clang-format off
#ifndef FLATBUFFERS_PREFER_PRINTF #ifndef FLATBUFFERS_PREFER_PRINTF
std::stringstream ss; std::stringstream ss;
ss << std::setw(xdigits) << std::setfill('0') << std::hex << std::uppercase ss << std::setw(xdigits) << std::setfill('0') << std::hex << std::uppercase
...@@ -217,37 +220,71 @@ inline std::string IntToStringHex(int i, int xdigits) { ...@@ -217,37 +220,71 @@ inline std::string IntToStringHex(int i, int xdigits) {
// clang-format on // clang-format on
} }
static inline double strtod_impl(const char *str, char **str_end) { // clang-format off
// Result of strtod (printf, etc) depends from current C-locale. // Use locale independent functions {strtod_l, strtof_l, strtoll_l, strtoull_l}.
return strtod(str, str_end); #if defined(FLATBUFFERS_LOCALE_INDEPENDENT) && (FLATBUFFERS_LOCALE_INDEPENDENT > 0)
} class ClassicLocale {
#ifdef _MSC_VER
static inline float strtof_impl(const char *str, char **str_end) { typedef _locale_t locale_type;
// Use "strtof" for float and strtod for double to avoid double=>float #else
// rounding problems (see typedef locale_t locale_type; // POSIX.1-2008 locale_t type
// https://en.cppreference.com/w/cpp/numeric/fenv/feround) or problems with #endif
// std::numeric_limits<float>::is_iec559==false. Example: ClassicLocale();
// for (int mode : { FE_DOWNWARD, FE_TONEAREST, FE_TOWARDZERO, FE_UPWARD }){ ~ClassicLocale();
// const char *s = "-4e38"; locale_type locale_;
// std::fesetround(mode); static ClassicLocale instance_;
// std::cout << strtof(s, nullptr) << "; " << strtod(s, nullptr) << "; " public:
// << static_cast<float>(strtod(s, nullptr)) << "\n"; static locale_type Get() { return instance_.locale_; }
// } };
// 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 _MSC_VER
#ifdef FLATBUFFERS_HAS_NEW_STRTOD #define __strtoull_impl(s, pe, b) _strtoui64_l(s, pe, b, ClassicLocale::Get())
return strtof(str, str_end); #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 #else
return static_cast<float>(strtod_impl(str, str_end)); #define __strtoull_impl(s, pe, b) strtoull_l(s, pe, b, ClassicLocale::Get())
#endif // !FLATBUFFERS_HAS_NEW_STRTOD #define __strtoll_impl(s, pe, b) strtoll_l(s, pe, b, ClassicLocale::Get())
// clang-format on #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(). // Adaptor for strtoull()/strtoll().
// Flatbuffers accepts numbers with any count of leading zeros (-009 is -9), // Flatbuffers accepts numbers with any count of leading zeros (-009 is -9),
// while strtoll with base=0 interprets first leading zero as octal prefix. // 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) { ...@@ -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 // - 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. // range error occurs. In this case value MAX(T)/MIN(T) is returned.
template<typename T> template<typename T>
inline T StringToInteger64Impl(const char *const str, const char **endptr, inline bool StringToIntegerImpl(T *val, const char *const str,
const int base, const bool check_errno = true) { const int base = 0,
static_assert(flatbuffers::is_same<T, int64_t>::value || const bool check_errno = true) {
flatbuffers::is_same<T, uint64_t>::value, // T is int64_t or uint64_T
"Type T must be either int64_t or uint64_t"); FLATBUFFERS_ASSERT(str);
FLATBUFFERS_ASSERT(str && endptr); // endptr must be not null
if (base <= 0) { if (base <= 0) {
auto s = str; auto s = str;
while (*s && !is_digit(*s)) s++; while (*s && !is_digit(*s)) s++;
if (s[0] == '0' && is_alpha_char(s[1], 'X')) 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 // if a prefix not match, try base=10
return StringToInteger64Impl<T>(str, endptr, 10, check_errno); return StringToIntegerImpl(val, str, 10, check_errno);
} else { } else {
if (check_errno) errno = 0; // clear thread-local errno if (check_errno) errno = 0; // clear thread-local errno
// calculate result auto endptr = str;
T result; strtoval_impl(val, str, const_cast<char **>(&endptr), base);
if (IsConstTrue(flatbuffers::is_same<T, int64_t>::value)) { if ((*endptr != '\0') || (endptr == str)) {
// clang-format off *val = 0; // erase partial result
#ifdef _MSC_VER return false; // invalid string
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;
}
}
} }
// check for overflow // errno is out-of-range, return MAX/MIN
if (check_errno && errno) *endptr = str; // point it to start of input if (check_errno && errno) return false;
// erase partial result, but save an overflow return true;
if ((*endptr != str) && (**endptr != '\0')) result = 0;
return result;
} }
} }
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. // Convert a string to an instance of T.
// Return value (matched with StringToInteger64Impl and strtod): // Return value (matched with StringToInteger64Impl and strtod):
// - If successful, a numeric value corresponding to the str is returned. // - 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, ...@@ -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. // range error occurs. In this case value MAX(T)/MIN(T) is returned.
template<typename T> inline bool StringToNumber(const char *s, T *val) { template<typename T> inline bool StringToNumber(const char *s, T *val) {
FLATBUFFERS_ASSERT(s && val); FLATBUFFERS_ASSERT(s && val);
const char *end = nullptr; int64_t i64;
// The errno check isn't needed. strtoll will return MAX/MIN on overlow. // The errno check isn't needed, will return MAX/MIN on overflow.
const int64_t i = StringToInteger64Impl<int64_t>(s, &end, -1, false); if (StringToIntegerImpl(&i64, s, 0, false)) {
*val = static_cast<T>(i);
const auto done = (s != end) && (*end == '\0');
if (done) {
const int64_t max = flatbuffers::numeric_limits<T>::max(); const int64_t max = flatbuffers::numeric_limits<T>::max();
const int64_t min = flatbuffers::numeric_limits<T>::lowest(); const int64_t min = flatbuffers::numeric_limits<T>::lowest();
if (i > max) { if (i64 > max) {
*val = static_cast<T>(max); *val = static_cast<T>(max);
return false; return false;
} }
if (i < min) { if (i64 < min) {
// For unsigned types return max to distinguish from // For unsigned types return max to distinguish from
// "no conversion can be performed" when 0 is returned. // "no conversion can be performed" when 0 is returned.
*val = static_cast<T>(flatbuffers::is_unsigned<T>::value ? max : min); *val = static_cast<T>(flatbuffers::is_unsigned<T>::value ? max : min);
return false; return false;
} }
*val = static_cast<T>(i64);
return true;
} }
return done; *val = 0;
return false;
} }
template<> inline bool StringToNumber<int64_t>(const char *s, int64_t *val) {
const char *end = s; // request errno checking template<> inline bool StringToNumber<int64_t>(const char *str, int64_t *val) {
*val = StringToInteger64Impl<int64_t>(s, &end, -1); return StringToIntegerImpl(val, str);
return (s != end) && (*end == '\0');
} }
template<> inline bool StringToNumber<uint64_t>(const char *s, uint64_t *val) {
const char *end = s; // request errno checking template<>
*val = StringToInteger64Impl<uint64_t>(s, &end, -1); inline bool StringToNumber<uint64_t>(const char *str, uint64_t *val) {
return (s != end) && (*end == '\0'); 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;
} }
template<> inline bool StringToNumber<double>(const char *s, double *val) { template<> inline bool StringToNumber(const char *s, float *val) {
FLATBUFFERS_ASSERT(s && val); return StringToFloatImpl(val, s);
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<float>(const char *s, float *val) { template<> inline bool StringToNumber(const char *s, double *val) {
FLATBUFFERS_ASSERT(s && val); return StringToFloatImpl(val, s);
char *end = nullptr;
*val = strtof_impl(s, &end);
auto done = (s != end) && (*end == '\0');
if (!done) *val = 0; // erase partial result
return done;
} }
inline int64_t StringToInt(const char *str, const char **endptr = nullptr, inline int64_t StringToInt(const char *s, int base = 10) {
int base = 10) { int64_t val;
const char *ep = nullptr; return StringToIntegerImpl(&val, s, base) ? val : 0;
return StringToInteger64Impl<int64_t>(str, endptr ? endptr : &ep, base);
} }
inline uint64_t StringToUInt(const char *str, const char **endptr = nullptr, inline uint64_t StringToUInt(const char *s, int base = 10) {
int base = 10) { uint64_t val;
const char *ep = nullptr; return StringToIntegerImpl(&val, s, base) ? val : 0;
return StringToInteger64Impl<uint64_t>(str, endptr ? endptr : &ep, base);
} }
typedef bool (*LoadFileFunction)(const char *filename, bool binary, typedef bool (*LoadFileFunction)(const char *filename, bool binary,
...@@ -506,6 +524,7 @@ inline void EnsureDirExists(const std::string &filepath) { ...@@ -506,6 +524,7 @@ inline void EnsureDirExists(const std::string &filepath) {
auto parent = StripFileName(filepath); auto parent = StripFileName(filepath);
if (parent.length()) EnsureDirExists(parent); if (parent.length()) EnsureDirExists(parent);
// clang-format off // clang-format off
#ifdef _WIN32 #ifdef _WIN32
(void)_mkdir(filepath.c_str()); (void)_mkdir(filepath.c_str());
#else #else
...@@ -518,6 +537,7 @@ inline void EnsureDirExists(const std::string &filepath) { ...@@ -518,6 +537,7 @@ inline void EnsureDirExists(const std::string &filepath) {
// Returns the input path if the absolute path couldn't be resolved. // Returns the input path if the absolute path couldn't be resolved.
inline std::string AbsolutePath(const std::string &filepath) { inline std::string AbsolutePath(const std::string &filepath) {
// clang-format off // clang-format off
#ifdef FLATBUFFERS_NO_ABSOLUTE_PATH_RESOLUTION #ifdef FLATBUFFERS_NO_ABSOLUTE_PATH_RESOLUTION
return filepath; return filepath;
#else #else
...@@ -576,7 +596,8 @@ inline int FromUTF8(const char **in) { ...@@ -576,7 +596,8 @@ inline int FromUTF8(const char **in) {
break; 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)++; if (!len) return *(*in)++;
// UTF-8 encoded values with a length are between 2 and 4 bytes. // UTF-8 encoded values with a length are between 2 and 4 bytes.
if (len < 2 || len > 4) { return -1; } if (len < 2 || len > 4) { return -1; }
...@@ -707,6 +728,19 @@ inline bool EscapeString(const char *s, size_t length, std::string *_text, ...@@ -707,6 +728,19 @@ inline bool EscapeString(const char *s, size_t length, std::string *_text,
return true; 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 } // namespace flatbuffers
#endif // FLATBUFFERS_UTIL_H_ #endif // FLATBUFFERS_UTIL_H_
/* /*
* Copyright 2014 Google Inc. All rights reserved. * Copyright 2014 Google Inc. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
...@@ -219,7 +219,7 @@ CheckedError Parser::ParseHexNum(int nibbles, uint64_t *val) { ...@@ -219,7 +219,7 @@ CheckedError Parser::ParseHexNum(int nibbles, uint64_t *val) {
return Error("escape code must be followed by " + NumToString(nibbles) + return Error("escape code must be followed by " + NumToString(nibbles) +
" hex digits"); " hex digits");
std::string target(cursor_, cursor_ + nibbles); std::string target(cursor_, cursor_ + nibbles);
*val = StringToUInt(target.c_str(), nullptr, 16); *val = StringToUInt(target.c_str(), 16);
cursor_ += nibbles; cursor_ += nibbles;
return NoError(); return NoError();
} }
......
...@@ -14,6 +14,8 @@ ...@@ -14,6 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
#include <clocale>
#include "flatbuffers/util.h" #include "flatbuffers/util.h"
namespace flatbuffers { namespace flatbuffers {
...@@ -58,6 +60,7 @@ bool FileExists(const char *name) { ...@@ -58,6 +60,7 @@ bool FileExists(const char *name) {
bool DirExists(const char *name) { bool DirExists(const char *name) {
// clang-format off // clang-format off
#ifdef _WIN32 #ifdef _WIN32
#define flatbuffers_stat _stat #define flatbuffers_stat _stat
#define FLATBUFFERS_S_IFDIR _S_IFDIR #define FLATBUFFERS_S_IFDIR _S_IFDIR
...@@ -85,4 +88,50 @@ FileExistsFunction SetFileExistsFunction( ...@@ -85,4 +88,50 @@ FileExistsFunction SetFileExistsFunction(
return previous_function; 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 } // namespace flatbuffers
...@@ -81,17 +81,6 @@ target_compile_definitions(flatbuffers PRIVATE FLATBUFFERS_MAX_PARSING_DEPTH=8) ...@@ -81,17 +81,6 @@ target_compile_definitions(flatbuffers PRIVATE FLATBUFFERS_MAX_PARSING_DEPTH=8)
# Setup fuzzer tests. # 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) add_executable(scalar_fuzzer flatbuffers_scalar_fuzzer.cc)
target_link_libraries(scalar_fuzzer PRIVATE flatbuffers) target_link_libraries(scalar_fuzzer PRIVATE flatbuffers)
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include <string> #include <string>
#include "flatbuffers/idl.h" #include "flatbuffers/idl.h"
#include "test_init.h"
static constexpr uint8_t flags_strict_json = 0x01; static constexpr uint8_t flags_strict_json = 0x01;
static constexpr uint8_t flags_skip_unexpected_fields_in_json = 0x02; static constexpr uint8_t flags_skip_unexpected_fields_in_json = 0x02;
...@@ -17,12 +18,8 @@ static constexpr uint8_t flags_allow_non_utf8 = 0x04; ...@@ -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_6 = 0x40;
// static constexpr uint8_t flags_flag_7 = 0x80; // static constexpr uint8_t flags_flag_7 = 0x80;
// See readme.md and CMakeLists.txt for details. // Utility for test run.
#ifdef FUZZ_TEST_LOCALE OneTimeTestInit OneTimeTestInit::one_time_init_;
static constexpr const char *test_locale = (FUZZ_TEST_LOCALE);
#else
static constexpr const char *test_locale = nullptr;
#endif
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Reserve one byte for Parser flags and one byte for repetition counter. // 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) { ...@@ -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 // Each test should pass at least two times to ensure that the parser doesn't
// have any hidden-states or locale-depended effects. // have any hidden-states or locale-depended effects.
for (auto cnt = 0; cnt < (extra_rep_number + 2); cnt++) { 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. // Set new locale.
if (use_locale) { if (use_locale) {
FLATBUFFERS_ASSERT(!!std::setlocale(LC_ALL, test_locale)); FLATBUFFERS_ASSERT(setlocale(LC_ALL, OneTimeTestInit::test_locale()));
} }
// Check Parser. // Check Parser.
parser.Parse(parse_input); parser.Parse(parse_input);
// Restore locale. // Restore locale.
if (use_locale) { FLATBUFFERS_ASSERT(!!std::setlocale(LC_ALL, "C")); } if (use_locale) { FLATBUFFERS_ASSERT(setlocale(LC_ALL, "C")); }
} }
return 0; return 0;
......
...@@ -8,55 +8,13 @@ ...@@ -8,55 +8,13 @@
#include <string> #include <string>
#include "flatbuffers/idl.h" #include "flatbuffers/idl.h"
#include "fuzzer_assert.h" #include "test_init.h"
#include "test_assert.h"
static_assert(__has_feature(memory_sanitizer) ||
__has_feature(address_sanitizer),
"sanitizer disabled");
static constexpr uint8_t flags_scalar_type = 0x0F; // type of scalar value static constexpr uint8_t flags_scalar_type = 0x0F; // type of scalar value
static constexpr uint8_t flags_quotes_kind = 0x10; // quote " or ' static constexpr uint8_t flags_quotes_kind = 0x10; // quote " or '
// reserved for future: json {named} or [unnamed] // reserved for future: json {named} or [unnamed]
// static constexpr uint8_t flags_json_bracer = 0x20; // 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. // Find all 'subj' sub-strings and replace first character of sub-string.
// BreakSequence("testest","tes", 'X') -> "XesXest". // BreakSequence("testest","tes", 'X') -> "XesXest".
// BreakSequence("xxx","xx", 'Y') -> "YYx". // BreakSequence("xxx","xx", 'Y') -> "YYx".
...@@ -248,6 +206,9 @@ bool Parse(flatbuffers::Parser &parser, const std::string &json, ...@@ -248,6 +206,9 @@ bool Parse(flatbuffers::Parser &parser, const std::string &json,
return done; return done;
} }
// Utility for test run.
OneTimeTestInit OneTimeTestInit::one_time_init_;
// llvm std::regex have problem with stack overflow, limit maximum length. // llvm std::regex have problem with stack overflow, limit maximum length.
// ./scalar_fuzzer -max_len=3000 // ./scalar_fuzzer -max_len=3000
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { 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) { ...@@ -299,23 +260,26 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// have any hidden-states or locale-depended effects. // have any hidden-states or locale-depended effects.
for (auto cnt = 0; cnt < (extra_rep_number + 2); cnt++) { for (auto cnt = 0; cnt < (extra_rep_number + 2); cnt++) {
// Each even run (0,2,4..) will test locale independed code. // 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. // Set new locale.
if (use_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. // Parse original input as-is.
auto orig_scalar = "{ \"Y\" : " + input + " }"; auto orig_scalar = "{ \"Y\" : " + input + " }";
std::string orig_back; std::string orig_back;
auto orig_done = Parse(parser, orig_scalar, &orig_back); auto orig_done = Parse(parser, orig_scalar, &orig_back);
if (recheck.res != orig_done) { if (recheck.res != orig_done) {
// look for "does not fit" or "doesn't fit" or "out of range" // look for "does not fit" or "doesn't fit" or "out of range"
auto parser_not_fit = auto not_fit =
(orig_back.find("does not fit") == std::string::npos) || (true == recheck.res)
(orig_back.find("out of range") == std::string::npos); ? ((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, TEST_OUTPUT_LINE("Stage 1 failed: Parser(%d) != Regex(%d)", orig_done,
recheck.res); recheck.res);
TEST_EQ_STR(orig_back.c_str(), TEST_EQ_STR(orig_back.c_str(),
...@@ -344,6 +308,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { ...@@ -344,6 +308,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
auto fix_scalar = "{ \"Y\" : " + qouted_input + " }"; auto fix_scalar = "{ \"Y\" : " + qouted_input + " }";
std::string fix_back; std::string fix_back;
auto fix_done = Parse(parser, fix_scalar, &fix_back); auto fix_done = Parse(parser, fix_scalar, &fix_back);
if (orig_done != fix_done) { if (orig_done != fix_done) {
TEST_OUTPUT_LINE("Stage 2 failed: Parser(%d) != Regex(%d)", fix_done, TEST_OUTPUT_LINE("Stage 2 failed: Parser(%d) != Regex(%d)", fix_done,
orig_done); orig_done);
...@@ -353,9 +318,34 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { ...@@ -353,9 +318,34 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
TEST_EQ_FUNC(fix_done, orig_done); TEST_EQ_FUNC(fix_done, orig_done);
} }
// Restore locale. // Create new parser and test default value
if (use_locale) { FLATBUFFERS_ASSERT(!!std::setlocale(LC_ALL, "C")); } 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; return 0;
} }
...@@ -15,27 +15,19 @@ The fuzzer section include three tests: ...@@ -15,27 +15,19 @@ The fuzzer section include three tests:
- `parser_fuzzer` checks stability of schema and json parser under various inputs; - `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; - `scalar_parser` focused on validation of the parser while parse numeric scalars in schema and/or json files;
## Build tests with locales ## Run tests with a specific locale
Flatbuffers library use only printable-ASCII characters as characters of grammar alphabet for type and data declaration. The grammar of the Flatbuffers library is based on printable-ASCII characters.
This alphabet is fully compatible with JSON specification and make schema declaration fully portable. By design, the Flatbuffers library should be independent of the global or thread locales used by an end-user application.
Flatbuffers library is independent from global or thread locales used by end-user application. Set environment variable `FLATBUFFERS_TEST_LOCALE` to run a fuzzer with a specific C-locale:
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:
```sh ```sh
cmake .. -DFUZZ_TEST_LOCALE="ru_RU.CP1251" >FLATBUFFERS_TEST_LOCALE="" ./scalar_parser
``` >FLATBUFFERS_TEST_LOCALE="ru_RU.CP1251" ./parser_fuzzer
If use VSCode, use `cmake.configureSettings` section of workspace settings:
```json
"cmake.configureSettings": {
"FUZZ_TEST_LOCALE" : "ru_RU.CP1251"
}
``` ```
## Run fuzzer ## Run fuzzer
These are examples of fuzzer run. These are examples of running a fuzzer.
Flags may vary and depend from version of libFuzzer library. Flags may vary and depend on a version of the libFuzzer library.
For detail, run a fuzzer test with help flag: `./parser_fuzzer -help=1` 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/` `./verifier_fuzzer -reduce_depth=1 -use_value_profile=1 -shrink=1 ../.corpus_verifier/`
...@@ -44,17 +36,19 @@ For detail, run a fuzzer test with help flag: `./parser_fuzzer -help=1` ...@@ -44,17 +36,19 @@ 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/` `./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/` `./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 ## Merge (minimize) corpus
The **libFuzzer** allow to filter (minimize) corpus with help of `-merge` flag: The **libFuzzer** allow to filter (minimize) corpus with help of `-merge` flag:
> -merge > -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. 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. Defaults to 0. This flag can be used to minimize a corpus.
Merge several seeds to one: Merge several seeds to one (a new collected corpus to the seed collection, for example):
`./scalar_fuzzer -merge=1 ../.corpus/ ../.seed_1/ ../.seed_2/` `./scalar_fuzzer -merge=1 ../.seed_parser/ ../.corpus_parser/`
## Know limitations ## 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. - LLVM 7.0 std::regex library has problem with stack overflow, maximum length of input for `scalar_fuzzer` run should be limited to 3000.
......
#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_
\ No newline at end of file
...@@ -1535,7 +1535,7 @@ void ValidFloatTest() { ...@@ -1535,7 +1535,7 @@ void ValidFloatTest() {
// Old MSVC versions may have problem with this check. // Old MSVC versions may have problem with this check.
// https://www.exploringbinary.com/visual-c-plus-plus-strtod-still-broken/ // https://www.exploringbinary.com/visual-c-plus-plus-strtod-still-broken/
TEST_EQ(TestValue<double>("{ Y:6.9294956446009195e15 }", "double"), TEST_EQ(TestValue<double>("{ Y:6.9294956446009195e15 }", "double"),
6929495644600920); 6929495644600920.0);
// check nan's // check nan's
TEST_EQ(std::isnan(TestValue<double>("{ Y:nan }", "double")), true); TEST_EQ(std::isnan(TestValue<double>("{ Y:nan }", "double")), true);
TEST_EQ(std::isnan(TestValue<float>("{ Y:nan }", "float")), true); TEST_EQ(std::isnan(TestValue<float>("{ Y:nan }", "float")), true);
...@@ -1663,6 +1663,7 @@ void NumericUtilsTestInteger(const char *lower, const char *upper) { ...@@ -1663,6 +1663,7 @@ void NumericUtilsTestInteger(const char *lower, const char *upper) {
template<typename T> template<typename T>
void NumericUtilsTestFloat(const char *lower, const char *upper) { void NumericUtilsTestFloat(const char *lower, const char *upper) {
T f; T f;
TEST_EQ(flatbuffers::StringToNumber("", &f), false);
TEST_EQ(flatbuffers::StringToNumber("1q", &f), false); TEST_EQ(flatbuffers::StringToNumber("1q", &f), false);
TEST_EQ(f, 0); TEST_EQ(f, 0);
TEST_EQ(flatbuffers::StringToNumber(upper, &f), true); TEST_EQ(flatbuffers::StringToNumber(upper, &f), true);
...@@ -2458,6 +2459,18 @@ int FlatBufferTests() { ...@@ -2458,6 +2459,18 @@ int FlatBufferTests() {
int main(int /*argc*/, const char * /*argv*/ []) { int main(int /*argc*/, const char * /*argv*/ []) {
InitTestEngine(); 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(); FlatBufferTests();
FlatBufferBuilderTest(); FlatBufferBuilderTest();
......
...@@ -17,8 +17,7 @@ void TestFail(const char *expval, const char *val, const char *exp, ...@@ -17,8 +17,7 @@ void TestFail(const char *expval, const char *val, const char *exp,
testing_fails++; testing_fails++;
// Notify, emulate 'gtest::OnTestPartResult' event handler. // Notify, emulate 'gtest::OnTestPartResult' event handler.
if(fail_listener_) if (fail_listener_) (*fail_listener_)(expval, val, exp, file, line, func);
(*fail_listener_)(expval, val, exp, file, line, func);
assert(0); // ignored in Release if NDEBUG defined assert(0); // ignored in Release if NDEBUG defined
} }
......
#ifndef TEST_ASSERT_H #ifndef TEST_ASSERT_H
#define TEST_ASSERT_H #define TEST_ASSERT_H
#include "flatbuffers/flatbuffers.h"
#include "flatbuffers/util.h" #include "flatbuffers/util.h"
// clang-format off
#ifdef __ANDROID__ #ifdef __ANDROID__
#include <android/log.h> #include <android/log.h>
#define TEST_OUTPUT_LINE(...) \ #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 #define FLATBUFFERS_NO_FILE_TESTS
#else #else
#define TEST_OUTPUT_LINE(...) \ #define TEST_OUTPUT_LINE(...) \
{ printf(__VA_ARGS__); printf("\n"); } { printf(__VA_ARGS__); printf("\n"); }
#endif #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 // clang-format on
extern int testing_fails; extern int testing_fails;
...@@ -20,8 +35,8 @@ extern int testing_fails; ...@@ -20,8 +35,8 @@ extern int testing_fails;
// Listener of TestFail, like 'gtest::OnTestPartResult' event handler. // Listener of TestFail, like 'gtest::OnTestPartResult' event handler.
// Called in TestFail after a failed assertion. // Called in TestFail after a failed assertion.
typedef bool (*TestFailEventListener)(const char *expval, const char *val, typedef bool (*TestFailEventListener)(const char *expval, const char *val,
const char *exp, const char *file, int line, const char *exp, const char *file,
const char *func); int line, const char *func);
// Prepare test engine (MSVC assertion setup, etc). // Prepare test engine (MSVC assertion setup, etc).
// listener - this function will be notified on each TestFail call. // 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, ...@@ -35,23 +50,12 @@ void TestEqStr(const char *expval, const char *val, const char *exp,
const char *file, int line); const char *file, int line);
template<typename T, typename U> 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) { if (U(expval) != val) {
TestFail(flatbuffers::NumToString(expval).c_str(), TestFail(flatbuffers::NumToString(expval).c_str(),
flatbuffers::NumToString(val).c_str(), exp, file, line, func); flatbuffers::NumToString(val).c_str(), exp, file, line, func);
} }
} }
#define TEST_EQ(exp, val) TestEq(exp, val, #exp, __FILE__, __LINE__) #endif // !TEST_ASSERT_H
#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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment