Merge commit '0f6aab9da6fe982218a01f4a5b896e65fcced437' as 'third_party/flatbuffers'

This commit is contained in:
Siarhei Fedartsou
2024-06-22 13:33:34 +02:00
1814 changed files with 326902 additions and 0 deletions
@@ -0,0 +1,20 @@
# The generated fuzzers
*_fuzzer
# Findings from the fuzzers
oom-*
slow-unit-*
crash-*
# Individual fuzzer logs if job=N
fuzz-*.log
.clangd/
# These are copied from tests/annotated_binary/ and should use the latest ones.
annotated_binary.bfbs
annotated_binary.bin
test_64bit.bin
monster_test.bfbs
+234
View File
@@ -0,0 +1,234 @@
cmake_minimum_required(VERSION 3.9)
set(CMAKE_VERBOSE_MAKEFILE ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
project(FlatBuffersFuzzerTests)
option(BUILD_DEBUGGER "Compile a debugger with main() and without libFuzzer" OFF)
if(NOT DEFINED FLATBUFFERS_MAX_PARSING_DEPTH)
# Force checking of RecursionError in the test
set(FLATBUFFERS_MAX_PARSING_DEPTH 24)
endif()
message(STATUS "FLATBUFFERS_MAX_PARSING_DEPTH: ${FLATBUFFERS_MAX_PARSING_DEPTH}")
# Usage '-fsanitize=address' doesn't allowed with '-fsanitize=memory'.
# MemorySanitizer will not work out-of-the-box, and will instead report false
# positives coming from uninstrumented code. Need to re-build both C++ standard
# library: https://github.com/google/sanitizers/wiki/MemorySanitizerLibcxxHowTo
option(USE_ASAN "Use fuzzers with ASASN" OFF)
option(USE_MSAN "Use fuzzers with MSASN" OFF)
option(OSS_FUZZ "Set this option to use flags by oss-fuzz" OFF)
# Use Clang linker.
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=lld")
# add_link_options(-stdlib=libc++)
add_compile_options(
# -stdlib=libc++ # Use Clang libc++ instead of GNU.
-std=c++17
-Wall
-pedantic
-Werror
-Wextra
-Wno-unused-parameter
-fsigned-char
-fno-omit-frame-pointer
-g # Generate source-level debug information
# -flto # enable link-time optimisation
)
# https://llvm.org/docs/Passes.html save IR to see call graph make one bitcode
# file:> llvm-link *.bc -o out.bc print call-graph:> opt out.bc -analyze -print-
# callgraph &> callgraph.txt set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -save-temps
# -flto")
# A special target with fuzzer+sanitizer flags.
add_library(fuzzer_config INTERFACE)
target_compile_options(
fuzzer_config
INTERFACE
$<$<NOT:$<BOOL:${OSS_FUZZ}>>:
-fsanitize-coverage=trace-cmp
>
$<$<BOOL:${USE_ASAN}>:
-fsanitize=fuzzer,undefined,address
>
$<$<BOOL:${USE_MSAN}>:
-fsanitize=fuzzer,undefined,memory
-fsanitize-memory-track-origins=2
>
$<$<BOOL:${OSS_FUZZ}>:
${CXX}
${CXXFLAGS}
>
)
target_link_libraries(
fuzzer_config
INTERFACE
$<$<BOOL:${USE_ASAN}>:
-fsanitize=fuzzer,undefined,address
>
$<$<BOOL:${USE_MSAN}>:
-fsanitize=fuzzer,undefined,memory
>
$<$<BOOL:${OSS_FUZZ}>:
$ENV{LIB_FUZZING_ENGINE}
>
)
set(FLATBUFFERS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../")
set(FlatBuffers_Library_SRCS
${FLATBUFFERS_DIR}/include/flatbuffers/allocator.h
${FLATBUFFERS_DIR}/include/flatbuffers/array.h
${FLATBUFFERS_DIR}/include/flatbuffers/base.h
${FLATBUFFERS_DIR}/include/flatbuffers/buffer.h
${FLATBUFFERS_DIR}/include/flatbuffers/buffer_ref.h
${FLATBUFFERS_DIR}/include/flatbuffers/default_allocator.h
${FLATBUFFERS_DIR}/include/flatbuffers/detached_buffer.h
${FLATBUFFERS_DIR}/include/flatbuffers/flatbuffer_builder.h
${FLATBUFFERS_DIR}/include/flatbuffers/flatbuffers.h
${FLATBUFFERS_DIR}/include/flatbuffers/flexbuffers.h
${FLATBUFFERS_DIR}/include/flatbuffers/flex_flat_util.h
${FLATBUFFERS_DIR}/include/flatbuffers/hash.h
${FLATBUFFERS_DIR}/include/flatbuffers/idl.h
${FLATBUFFERS_DIR}/include/flatbuffers/minireflect.h
${FLATBUFFERS_DIR}/include/flatbuffers/reflection.h
${FLATBUFFERS_DIR}/include/flatbuffers/reflection_generated.h
${FLATBUFFERS_DIR}/include/flatbuffers/registry.h
${FLATBUFFERS_DIR}/include/flatbuffers/stl_emulation.h
${FLATBUFFERS_DIR}/include/flatbuffers/string.h
${FLATBUFFERS_DIR}/include/flatbuffers/struct.h
${FLATBUFFERS_DIR}/include/flatbuffers/table.h
${FLATBUFFERS_DIR}/include/flatbuffers/util.h
${FLATBUFFERS_DIR}/include/flatbuffers/vector.h
${FLATBUFFERS_DIR}/include/flatbuffers/vector_downward.h
${FLATBUFFERS_DIR}/include/flatbuffers/verifier.h
${FLATBUFFERS_DIR}/src/idl_parser.cpp
${FLATBUFFERS_DIR}/src/idl_gen_text.cpp
${FLATBUFFERS_DIR}/src/reflection.cpp
${FLATBUFFERS_DIR}/src/binary_annotator.h
${FLATBUFFERS_DIR}/src/binary_annotator.cpp
${FLATBUFFERS_DIR}/src/util.cpp
${FLATBUFFERS_DIR}/tests/test_assert.cpp
${FLATBUFFERS_DIR}/tests/64bit/test_64bit_bfbs_generated.h
)
include_directories(${FLATBUFFERS_DIR}/include)
include_directories(${FLATBUFFERS_DIR}/tests)
include_directories(${FLATBUFFERS_DIR}/src)
add_library(flatbuffers_fuzzed STATIC ${FlatBuffers_Library_SRCS})
# Use PUBLIC to force 'fuzzer_config' for all dependent targets
target_link_libraries(flatbuffers_fuzzed PUBLIC fuzzer_config)
# FLATBUFFERS_ASSERT should assert in Release as well. Redefine
# FLATBUFFERS_ASSERT macro definition. Declare as PUBLIC to cover asserts in all
# included header files.
target_compile_definitions(
flatbuffers_fuzzed
PUBLIC
FLATBUFFERS_ASSERT=fuzzer_assert_impl
FLATBUFFERS_ASSERT_INCLUDE="${CMAKE_CURRENT_SOURCE_DIR}/fuzzer_assert.h"
PRIVATE
FLATBUFFERS_MAX_PARSING_DEPTH=${FLATBUFFERS_MAX_PARSING_DEPTH}
)
# Setup fuzzer tests.
add_executable(scalar_fuzzer flatbuffers_scalar_fuzzer.cc)
target_link_libraries(scalar_fuzzer PRIVATE flatbuffers_fuzzed)
add_executable(parser_fuzzer flatbuffers_parser_fuzzer.cc)
target_link_libraries(parser_fuzzer PRIVATE flatbuffers_fuzzed)
add_executable(verifier_fuzzer flatbuffers_verifier_fuzzer.cc)
target_link_libraries(verifier_fuzzer PRIVATE flatbuffers_fuzzed)
add_executable(flexverifier_fuzzer flexbuffers_verifier_fuzzer.cc)
target_link_libraries(flexverifier_fuzzer PRIVATE flatbuffers_fuzzed)
add_executable(monster_fuzzer flatbuffers_monster_fuzzer.cc)
target_link_libraries(monster_fuzzer PRIVATE flatbuffers_fuzzed)
add_custom_command(
TARGET monster_fuzzer PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_SOURCE_DIR}/../monster_test.bfbs
${CMAKE_CURRENT_BINARY_DIR}/monster_test.bfbs)
add_executable(annotator_fuzzer flatbuffers_annotator_fuzzer.cc)
target_link_libraries(annotator_fuzzer PRIVATE flatbuffers_fuzzed)
add_custom_command(
TARGET annotator_fuzzer PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_SOURCE_DIR}/../annotated_binary/annotated_binary.bfbs
${CMAKE_CURRENT_BINARY_DIR}/annotated_binary.bfbs
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_SOURCE_DIR}/../annotated_binary/annotated_binary.bin
${CMAKE_CURRENT_BINARY_DIR}/seed_annotator/annotated_binary.bin
)
add_executable(64bit_fuzzer flatbuffers_64bit_fuzzer.cc)
target_link_libraries(64bit_fuzzer PRIVATE flatbuffers_fuzzed)
add_custom_command(
TARGET 64bit_fuzzer PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_SOURCE_DIR}/../64bit/test_64bit.bin
${CMAKE_CURRENT_BINARY_DIR}/seed_64bit/test_64bit.bin
)
# Build debugger for weird cases found with fuzzer.
if(BUILD_DEBUGGER)
add_library(flatbuffers_nonfuzz STATIC ${FlatBuffers_Library_SRCS})
target_compile_options(
flatbuffers_nonfuzz
PUBLIC
$<$<BOOL:${USE_ASAN}>:
-fsanitize=undefined,address
>
-fno-limit-debug-info
)
target_link_libraries(
flatbuffers_nonfuzz
PUBLIC
$<$<BOOL:${USE_ASAN}>:
-fsanitize=undefined,address
>
)
target_compile_definitions(
flatbuffers_nonfuzz
PUBLIC
FLATBUFFERS_ASSERT=fuzzer_assert_impl
FLATBUFFERS_ASSERT_INCLUDE="${CMAKE_CURRENT_SOURCE_DIR}/fuzzer_assert.h"
PRIVATE
FLATBUFFERS_MAX_PARSING_DEPTH=${FLATBUFFERS_MAX_PARSING_DEPTH}
)
add_executable(scalar_debug
flatbuffers_scalar_fuzzer.cc
scalar_debug.cpp
)
target_link_libraries(scalar_debug PRIVATE flatbuffers_nonfuzz)
add_executable(monster_debug
flatbuffers_monster_fuzzer.cc
monster_debug.cpp
)
target_link_libraries(monster_debug PRIVATE flatbuffers_nonfuzz)
add_custom_command(
TARGET monster_debug PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_SOURCE_DIR}/../monster_test.bfbs
${CMAKE_CURRENT_BINARY_DIR}/monster_test.bfbs)
endif(BUILD_DEBUGGER)
@@ -0,0 +1,121 @@
#include <cstdint>
#include <filesystem>
#include <type_traits>
#include "64bit/test_64bit_bfbs_generated.h"
#include "64bit/test_64bit_generated.h"
#include "flatbuffers/base.h"
#include "flatbuffers/flatbuffer_builder.h"
#include "flatbuffers/flatbuffers.h"
#include "flatbuffers/reflection.h"
#include "flatbuffers/verifier.h"
#include "test_assert.h"
#include "test_init.h"
OneTimeTestInit OneTimeTestInit::one_time_init_;
static RootTableBinarySchema schema;
static constexpr uint8_t flags_sized_prefixed = 0b00000001;
static const uint64_t kFnvPrime = 0x00000100000001b3ULL;
static const uint64_t kOffsetBasis = 0xcbf29ce484222645ULL;
namespace flatbuffers {
template<typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
uint64_t Hash(T value, uint64_t hash) {
return (hash * kFnvPrime) ^ value;
}
uint64_t Hash(double value, uint64_t hash) {
static_assert(sizeof(double) == sizeof(uint64_t));
return (hash * kFnvPrime) ^ static_cast<uint64_t>(value);
}
uint64_t Hash(const flatbuffers::String *value, uint64_t hash) {
if (value == nullptr) { return hash * kFnvPrime; }
for (auto &c : value->str()) { hash = Hash(static_cast<uint8_t>(c), hash); }
return hash;
}
uint64_t Hash(const LeafStruct *value, uint64_t hash) {
if (value == nullptr) { return hash * kFnvPrime; }
hash = Hash(value->a(), hash);
hash = Hash(value->b(), hash);
return hash;
}
template<typename T> uint64_t Hash(const Vector<T> *value, uint64_t hash) {
if (value == nullptr) { return hash * kFnvPrime; }
for (const T c : *value) { hash = Hash(c, hash); }
return hash;
}
template<typename T> uint64_t Hash(const Vector64<T> *value, uint64_t hash) {
if (value == nullptr) { return hash * kFnvPrime; }
for (const T c : *value) { hash = Hash(c, hash); }
return hash;
}
uint64_t Hash(const RootTable *value, uint64_t hash) {
if (value == nullptr) { return hash * kFnvPrime; }
// Hash all the fields so we can exercise all parts of the code.
hash = Hash(value->far_vector(), hash);
hash = Hash(value->a(), hash);
hash = Hash(value->far_string(), hash);
hash = Hash(value->big_vector(), hash);
hash = Hash(value->near_string(), hash);
hash = Hash(value->nested_root(), hash);
hash = Hash(value->far_struct_vector(), hash);
hash = Hash(value->big_struct_vector(), hash);
return hash;
}
static int AccessBuffer(const uint8_t *data, size_t size,
bool is_size_prefixed) {
const RootTable *root_table =
is_size_prefixed ? GetSizePrefixedRootTable(data) : GetRootTable(data);
TEST_NOTNULL(root_table);
uint64_t hash = kOffsetBasis;
hash = Hash(root_table, hash);
hash = Hash(root_table->nested_root_nested_root(), hash);
return 0;
}
extern "C" int LLVMFuzzerInitialize(int *, char ***argv) {
Verifier verifier(schema.begin(), schema.size());
TEST_EQ(true, reflection::VerifySchemaBuffer(verifier));
return 0;
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size < FLATBUFFERS_MIN_BUFFER_SIZE) { return 0; }
// Take the first bit of data as a flag to control things.
const uint8_t flags = data[0];
data++;
size--;
Verifier::Options options;
options.assert = true;
options.check_alignment = true;
options.check_nested_flatbuffers = true;
Verifier verifier(data, size, options);
const bool is_size_prefixed = flags & flags_sized_prefixed;
// Filter out data that isn't valid.
if ((is_size_prefixed && !VerifySizePrefixedRootTableBuffer(verifier)) ||
!VerifyRootTableBuffer(verifier)) {
return 0;
}
return AccessBuffer(data, size, is_size_prefixed);
}
} // namespace flatbuffers
@@ -0,0 +1,53 @@
#include <filesystem>
#include <string>
#include "binary_annotator.h"
#include "test_init.h"
static std::filesystem::path exe_path_;
static const uint8_t *schema_bfbs_;
static size_t schema_bfbs_length_;
bool TestFileExists(std::filesystem::path file_path) {
if (file_path.has_filename() && std::filesystem::exists(file_path))
return true;
TEST_OUTPUT_LINE("@DEBUG: file '%s' not found", file_path.string().c_str());
for (const auto &entry :
std::filesystem::directory_iterator(file_path.parent_path())) {
TEST_OUTPUT_LINE("@DEBUG: parent path entry: '%s'",
entry.path().string().c_str());
}
return false;
}
std::string LoadBinarySchema(const char *file_name) {
const auto file_path = exe_path_.parent_path() / file_name;
TEST_EQ(true, TestFileExists(file_path));
std::string schemafile;
TEST_EQ(true,
flatbuffers::LoadFile(file_path.string().c_str(), true, &schemafile));
flatbuffers::Verifier verifier(
reinterpret_cast<const uint8_t *>(schemafile.c_str()), schemafile.size());
TEST_EQ(true, reflection::VerifySchemaBuffer(verifier));
return schemafile;
}
extern "C" int LLVMFuzzerInitialize(int *, char ***argv) {
exe_path_ = (*argv)[0];
static const std::string schema_file =
LoadBinarySchema("annotated_binary.bfbs");
schema_bfbs_ = reinterpret_cast<const uint8_t *>(schema_file.c_str());
schema_bfbs_length_ = schema_file.size();
return 0;
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
flatbuffers::BinaryAnnotator annotator(schema_bfbs_, schema_bfbs_length_,
data, size, false);
annotator.Annotate();
return 0;
}
@@ -0,0 +1,144 @@
/*
* Copyright 2014 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stddef.h>
#include <stdint.h>
#include <clocale>
#include <filesystem>
#include <string>
#include "cpp17/generated_cpp17/monster_test_generated.h"
#include "flatbuffers/idl.h"
#include "test_assert.h"
#include "test_init.h"
namespace fs = std::filesystem;
// Utility for test run.
OneTimeTestInit OneTimeTestInit::one_time_init_;
// The current executable path (see LLVMFuzzerInitialize).
static fs::path exe_path_;
static flatbuffers::Parser parser_;
namespace {
static constexpr size_t kMinInputLength = 1;
static constexpr size_t kMaxInputLength = 16384;
static constexpr uint8_t flags_strict_json = 0x80;
static constexpr uint8_t flags_skip_unexpected_fields_in_json = 0x40;
static constexpr uint8_t flags_allow_non_utf8 = 0x20;
bool TestFileExists(fs::path file_path) {
if (file_path.has_filename() && fs::exists(file_path)) return true;
TEST_OUTPUT_LINE("@DEBUG: file '%s' not found", file_path.string().c_str());
for (const auto &entry : fs::directory_iterator(file_path.parent_path())) {
TEST_OUTPUT_LINE("@DEBUG: parent path entry: '%s'",
entry.path().string().c_str());
}
return false;
}
std::string LoadBinarySchema(const char *file_name) {
const auto file_path = exe_path_.parent_path() / file_name;
TEST_EQ(true, TestFileExists(file_path));
std::string schemafile;
TEST_EQ(true,
flatbuffers::LoadFile(file_path.string().c_str(), true, &schemafile));
flatbuffers::Verifier verifier(
reinterpret_cast<const uint8_t *>(schemafile.c_str()), schemafile.size());
TEST_EQ(true, reflection::VerifySchemaBuffer(verifier));
return schemafile;
}
std::string do_test(const flatbuffers::IDLOptions &opts,
const std::string input_json, const bool check_parser) {
// (re)define parser options
parser_.opts = opts;
std::string jsongen;
if (parser_.ParseJson(input_json.c_str())) {
flatbuffers::Verifier verifier(parser_.builder_.GetBufferPointer(),
parser_.builder_.GetSize());
TEST_EQ(true, MyGame::Example::VerifyMonsterBuffer(verifier));
TEST_NULL(
GenText(parser_, parser_.builder_.GetBufferPointer(), &jsongen));
} else if (check_parser) {
TEST_OUTPUT_LINE("parser failed with JSON:\n%s", input_json.c_str());
TEST_EQ_STR("", parser_.error_.c_str());
TEST_ASSERT(false);
}
return jsongen;
};
} // namespace
// https://google.github.io/oss-fuzz/further-reading/fuzzer-environment/
// Current working directory
// You should not make any assumptions about the current working directory of
// your fuzz target. If you need to load data files, please use argv[0] to get
// the directory where your fuzz target executable is located.
// You must not modify argv[0].
extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) {
(void)argc;
exe_path_ = (*argv)[0];
static const std::string schemafile = LoadBinarySchema("monster_test.bfbs");
// parse schema first, so we can use it to parse the data after
parser_.Deserialize(reinterpret_cast<const uint8_t *>(schemafile.c_str()),
schemafile.size());
return 0;
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Reserve one byte for Parser flags and one byte for repetition counter.
if (size < 3) return 0;
const uint8_t flags = data[0];
(void)data[1]; // reserved
data += 2;
size -= 2; // bypass
const std::string original(reinterpret_cast<const char *>(data), size);
auto input = std::string(original.c_str()); // until '\0'
if (input.size() < kMinInputLength || input.size() > kMaxInputLength)
return 0;
flatbuffers::IDLOptions opts;
opts.strict_json = (flags & flags_strict_json);
opts.skip_unexpected_fields_in_json =
(flags & flags_skip_unexpected_fields_in_json);
opts.allow_non_utf8 = (flags & flags_allow_non_utf8);
do {
const std::string jsongen_1 = do_test(opts, input, false);
if (!jsongen_1.empty()) {
const std::string jsongen_2 = do_test(opts, jsongen_1, true);
if (jsongen_1 != jsongen_2 && !opts.output_default_scalars_in_json) {
// This gets tricky when the jsongen_1 includes a default-value, as the
// generated jsongen_2 doesn't emit default-values. So enable default
// scalars and re-run it.
opts.output_default_scalars_in_json = true;
continue;
}
TEST_EQ(jsongen_1, jsongen_2);
}
} while (0);
return 0;
}
@@ -0,0 +1,55 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stddef.h>
#include <stdint.h>
#include <clocale>
#include <string>
#include "flatbuffers/idl.h"
#include "test_init.h"
static constexpr size_t kMinInputLength = 1;
static constexpr size_t kMaxInputLength = 16384;
static constexpr uint8_t flags_strict_json = 0x80;
static constexpr uint8_t flags_skip_unexpected_fields_in_json = 0x40;
static constexpr uint8_t flags_allow_non_utf8 = 0x20;
// 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.
if (size < 3) return 0;
const uint8_t flags = data[0];
(void)data[1]; // reserved
data += 2;
size -= 2; // bypass
const std::string original(reinterpret_cast<const char *>(data), size);
auto input = std::string(original.c_str()); // until '\0'
if (input.size() < kMinInputLength || input.size() > kMaxInputLength)
return 0;
flatbuffers::IDLOptions opts;
opts.strict_json = (flags & flags_strict_json);
opts.skip_unexpected_fields_in_json =
(flags & flags_skip_unexpected_fields_in_json);
opts.allow_non_utf8 = (flags & flags_allow_non_utf8);
flatbuffers::Parser parser(opts);
// Guarantee 0-termination in the input.
auto parse_input = input.c_str();
// Check Parser.
parser.Parse(parse_input);
// TODO:
// Need to add additional checks for inputs passed Parse(parse_input) successfully:
// 1. Serialization to bfbs.
// 2. Generation of a default object.
// 3. Verification of the object using reflection.
// 3. Printing to json.
return 0;
}
@@ -0,0 +1,375 @@
/*
* Copyright 2014 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <clocale>
#include <memory>
#include <regex>
#include <string>
#include "flatbuffers/idl.h"
#include "test_init.h"
static constexpr size_t kMinInputLength = 1;
static constexpr size_t kMaxInputLength = 3000;
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;
// Find all 'subj' sub-strings and replace first character of sub-string.
// BreakSequence("testest","tes", 'X') -> "XesXest".
// BreakSequence("xxx","xx", 'Y') -> "YYx".
static void BreakSequence(std::string &s, const char *subj, char repl) {
size_t pos = 0;
while (pos = s.find(subj, pos), pos != std::string::npos) {
s.at(pos) = repl;
pos++;
}
}
// Remove all leading and trailing symbols matched with pattern set.
// StripString("xy{xy}y", "xy") -> "{xy}"
static std::string StripString(const std::string &s, const char *pattern,
size_t *pos = nullptr) {
if (pos) *pos = 0;
// leading
auto first = s.find_first_not_of(pattern);
if (std::string::npos == first) return "";
if (pos) *pos = first;
// trailing
auto last = s.find_last_not_of(pattern);
assert(last < s.length());
assert(first <= last);
return s.substr(first, last - first + 1);
}
class RegexMatcher {
protected:
virtual bool MatchNumber(const std::string &input) const = 0;
public:
virtual ~RegexMatcher() = default;
struct MatchResult {
size_t pos{ 0 };
size_t len{ 0 };
bool res{ false };
bool quoted{ false };
};
MatchResult Match(const std::string &input) const {
MatchResult r;
// strip leading and trailing "spaces" accepted by flatbuffer
auto test = StripString(input, "\t\r\n ", &r.pos);
r.len = test.size();
// check quotes
if (test.size() >= 2) {
auto fch = test.front();
auto lch = test.back();
r.quoted = (fch == lch) && (fch == '\'' || fch == '\"');
if (r.quoted) {
// remove quotes for regex test
test = test.substr(1, test.size() - 2);
}
}
// Fast check:
if (test.empty()) return r;
// A string with a valid scalar shouldn't have non-ascii or non-printable
// symbols.
for (auto c : test) {
if ((c < ' ') || (c > '~')) return r;
}
// Check with regex
r.res = MatchNumber(test);
return r;
}
bool MatchRegexList(const std::string &input,
const std::vector<std::regex> &re_list) const {
auto str = StripString(input, " ");
if (str.empty()) return false;
for (auto &re : re_list) {
std::smatch match;
if (std::regex_match(str, match, re)) return true;
}
return false;
}
};
class IntegerRegex : public RegexMatcher {
protected:
bool MatchNumber(const std::string &input) const override {
static const std::vector<std::regex> re_list = {
std::regex{ R"(^[-+]?[0-9]+$)", std::regex_constants::optimize },
std::regex{ R"(^[-+]?0[xX][0-9a-fA-F]+$)",
std::regex_constants::optimize }
};
return MatchRegexList(input, re_list);
}
public:
IntegerRegex() = default;
virtual ~IntegerRegex() = default;
};
class UIntegerRegex : public RegexMatcher {
protected:
bool MatchNumber(const std::string &input) const override {
static const std::vector<std::regex> re_list = {
std::regex{ R"(^[+]?[0-9]+$)", std::regex_constants::optimize },
std::regex{ R"(^[+]?0[xX][0-9a-fA-F]+$)",
std::regex_constants::optimize },
// accept -0 number
std::regex{ R"(^[-](?:0[xX])?0+$)", std::regex_constants::optimize }
};
return MatchRegexList(input, re_list);
}
public:
UIntegerRegex() = default;
virtual ~UIntegerRegex() = default;
};
class BooleanRegex : public IntegerRegex {
protected:
bool MatchNumber(const std::string &input) const override {
if (input == "true" || input == "false") return true;
return IntegerRegex::MatchNumber(input);
}
public:
BooleanRegex() = default;
virtual ~BooleanRegex() = default;
};
class FloatRegex : public RegexMatcher {
protected:
bool MatchNumber(const std::string &input) const override {
static const std::vector<std::regex> re_list = {
// hex-float
std::regex{
R"(^[-+]?0[xX](?:(?:[.][0-9a-fA-F]+)|(?:[0-9a-fA-F]+[.][0-9a-fA-F]*)|(?:[0-9a-fA-F]+))[pP][-+]?[0-9]+$)",
std::regex_constants::optimize },
// dec-float
std::regex{
R"(^[-+]?(?:(?:[.][0-9]+)|(?:[0-9]+[.][0-9]*)|(?:[0-9]+))(?:[eE][-+]?[0-9]+)?$)",
std::regex_constants::optimize },
std::regex{ R"(^[-+]?(?:nan|inf|infinity)$)",
std::regex_constants::optimize | std::regex_constants::icase }
};
return MatchRegexList(input, re_list);
}
public:
FloatRegex() = default;
virtual ~FloatRegex() = default;
};
class ScalarReferenceResult {
private:
ScalarReferenceResult(const char *_type, RegexMatcher::MatchResult _matched)
: type(_type), matched(_matched) {}
public:
// Decode scalar type and check if the input string satisfies the scalar type.
static ScalarReferenceResult Check(uint8_t code, const std::string &input) {
switch (code) {
case 0x0: return { "double", FloatRegex().Match(input) };
case 0x1: return { "float", FloatRegex().Match(input) };
case 0x2: return { "int8", IntegerRegex().Match(input) };
case 0x3: return { "int16", IntegerRegex().Match(input) };
case 0x4: return { "int32", IntegerRegex().Match(input) };
case 0x5: return { "int64", IntegerRegex().Match(input) };
case 0x6: return { "uint8", UIntegerRegex().Match(input) };
case 0x7: return { "uint16", UIntegerRegex().Match(input) };
case 0x8: return { "uint32", UIntegerRegex().Match(input) };
case 0x9: return { "uint64", UIntegerRegex().Match(input) };
case 0xA: return { "bool", BooleanRegex().Match(input) };
default: return { "float", FloatRegex().Match(input) };
};
}
const char *type;
const RegexMatcher::MatchResult matched;
};
bool Parse(flatbuffers::Parser &parser, const std::string &json,
std::string *_text) {
auto done = parser.ParseJson(json.c_str());
if (done) {
TEST_NULL(GenText(parser, parser.builder_.GetBufferPointer(), _text));
} else {
*_text = parser.error_;
}
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) {
// Reserve one byte for Parser flags and one byte for repetition counter.
if (size < 3) return 0;
const uint8_t flags = data[0];
// normalize to ascii alphabet
const int extra_rep_number =
std::max(5, (data[1] > '0' ? (data[1] - '0') : 0));
data += 2;
size -= 2; // bypass
// Guarantee 0-termination.
const std::string original(reinterpret_cast<const char *>(data), size);
auto input = std::string(original.c_str()); // until '\0'
if (input.size() < kMinInputLength || input.size() > kMaxInputLength)
return 0;
// Break comments in json to avoid complexity with regex matcher.
// The string " 12345 /* text */" will be accepted if insert it to string
// expression: "table X { Y: " + " 12345 /* text */" + "; }.
// But strings like this will complicate regex matcher.
// We reject this by transform "/* text */ 12345" to "@* text */ 12345".
BreakSequence(input, "//", '@'); // "//" -> "@/"
BreakSequence(input, "/*", '@'); // "/*" -> "@*"
// { "$schema: "text" } is exceptional case.
// This key:value ignored by the parser. Numbers can not have $.
BreakSequence(input, "$schema", '@'); // "$schema" -> "@schema"
// Break all known scalar functions (todo: add them to regex?):
for (auto f : { "deg", "rad", "sin", "cos", "tan", "asin", "acos", "atan" }) {
BreakSequence(input, f, '_'); // ident -> ident
}
// Extract type of scalar from 'flags' and check if the input string satisfies
// the scalar type.
const auto ref_res =
ScalarReferenceResult::Check(flags & flags_scalar_type, input);
auto &recheck = ref_res.matched;
// Create parser
flatbuffers::IDLOptions opts;
opts.force_defaults = true;
opts.output_default_scalars_in_json = true;
opts.indent_step = -1;
opts.strict_json = true;
flatbuffers::Parser parser(opts);
auto schema =
"table X { Y: " + std::string(ref_res.type) + "; } root_type X;";
TEST_EQ_FUNC(parser.Parse(schema.c_str()), true);
// The fuzzer can adjust the number repetition if a side-effects have found.
// 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++) {
// 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(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 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 == not_fit) {
TEST_OUTPUT_LINE("Stage 1 failed: Parser(%d) != Regex(%d)", orig_done,
recheck.res);
TEST_EQ_STR(orig_back.c_str(),
input.substr(recheck.pos, recheck.len).c_str());
TEST_EQ_FUNC(orig_done, recheck.res);
}
}
// Try to make quoted string and test it.
std::string qouted_input;
if (true == recheck.quoted) {
// we can't simply remove quotes, they may be nested "'12'".
// Original string "\'12\'" converted to "'12'".
// The string can be an invalid string by JSON rules, but after quotes
// removed can transform to valid.
assert(recheck.len >= 2);
} else {
const auto quote = (flags & flags_quotes_kind) ? '\"' : '\'';
qouted_input = input; // copy
qouted_input.insert(recheck.pos + recheck.len, 1, quote);
qouted_input.insert(recheck.pos, 1, quote);
}
// Test quoted version of the string
if (!qouted_input.empty()) {
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);
TEST_EQ_STR(fix_back.c_str(), orig_back.c_str());
}
if (orig_done) { TEST_EQ_STR(fix_back.c_str(), orig_back.c_str()); }
TEST_EQ_FUNC(fix_done, orig_done);
}
// 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(!GenText(
parser, parser.builder_.GetBufferPointer(), &ref_string));
FLATBUFFERS_ASSERT(!GenText(
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;
}
@@ -0,0 +1,14 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stddef.h>
#include <stdint.h>
#include <string>
#include "cpp17/generated_cpp17/monster_test_generated.h"
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
flatbuffers::Verifier verifier(data, size);
MyGame::Example::VerifyMonsterBuffer(verifier);
return 0;
}
@@ -0,0 +1,19 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stddef.h>
#include <stdint.h>
#include <string>
#include "flatbuffers/flexbuffers.h"
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
std::vector<uint8_t> reuse_tracker;
// Check both with and without reuse tracker paths.
flexbuffers::VerifyBuffer(data, size, &reuse_tracker);
// FIXME: we can't really verify this path, because the fuzzer will
// construct buffers that time out.
// Add a simple #define to bound the number of steps just for the fuzzer?
//flexbuffers::VerifyBuffer(data, size, nullptr);
return 0;
}
+14
View File
@@ -0,0 +1,14 @@
#ifndef FUZZER_ASSERT_IMPL_H_
#define FUZZER_ASSERT_IMPL_H_
#if defined(_MSC_VER)
extern "C" void __debugbreak();
#define __builtin_trap __debugbreak
#else // Clang
extern "C" void __builtin_trap(void);
#endif
// Declare Debug/Release independed assert macro.
#define fuzzer_assert_impl(x) (!!(x) ? static_cast<void>(0) : __builtin_trap())
#endif // !FUZZER_ASSERT_IMPL_H_
+30
View File
@@ -0,0 +1,30 @@
#include <iostream>
#include <assert.h>
#include "flatbuffers/util.h"
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
int main(int argc, char *argv[]) {
if (argc < 2) {
std::cerr << "Usage: monster_debug <path to fuzzer crash file>\n";
return 0;
}
std::string crash_file_name(argv[1]);
std::string crash_file_data;
auto done =
flatbuffers::LoadFile(crash_file_name.c_str(), true, &crash_file_data);
if (!done) {
std::cerr << "Can not load file: '" << crash_file_name << "'";
return -1;
}
if (crash_file_data.size() < 3) {
std::cerr << "Invalid file data: '" << crash_file_data << "'";
return -2;
}
auto rc = LLVMFuzzerTestOneInput(
reinterpret_cast<const uint8_t *>(crash_file_data.data()),
crash_file_data.size());
std::cout << "LLVMFuzzerTestOneInput finished with code " << rc << "\n\n";
return rc;
}
@@ -0,0 +1,60 @@
"{"
"}"
"["
"]"
"\""
"'"
"\\"
"//"
":"
","
" "
"\\n"
"\\r"
"/*"
"*/"
"true"
"false"
"null"
"\\u"
"\\b"
"\\f"
"\\t"
"."
"e"
"e+"
"e-"
"E"
"E+"
"E-"
"0x"
"p"
"a"
"b"
"Monster"
"pos"
"hp"
"name"
"weapons"
"damage"
"equipped_type"
"equipped"
"inventory"
"vector_of_longs"
"vector_of_doubles"
"test_type"
"test"
"test1"
"test2"
"test4"
"test3"
"test5"
"enemy"
"Weapon"
"Green"
"Red"
"Blue"
"testarrayofstring"
"testarrayofbools"
"testbool"
"flex"
+101
View File
@@ -0,0 +1,101 @@
"struct"
"table"
"enum"
"union"
"include"
"namespace"
"attribute"
"null"
"NULL"
"byte"
"int8"
"ubyte"
"uint8"
"bool"
"short"
"int16"
"ushort"
"uint16"
"int"
"int32"
"uint"
"uint32"
"float"
"float32"
"long"
"int64"
"ulong"
"uint64"
"double"
"float64"
"root_type"
"file_identifier"
"file_extension"
"{"
"}"
"["
"]"
"\""
"'"
"\\"
"//"
":"
","
" "
"\\n"
"\\r"
"/*"
"*/"
"true"
"false"
"null"
"\\u"
"\\b"
"\\f"
"\\t"
"."
"e"
"e+"
"e-"
"E"
"E+"
"E-"
"0x"
"p"
"a"
"b"
"Monster"
"pos"
"hp"
"name"
"weapons"
"damage"
"equipped_type"
"equipped"
"inventory"
"vector_of_longs"
"vector_of_doubles"
"test_type"
"test"
"test1"
"test2"
"test4"
"test3"
"test5"
"enemy"
"Weapon"
"Green"
"Red"
"Blue"
"testarrayofstring"
"testarrayofbools"
"testbool"
"testhashs32_fnv1"
"testhashu32_fnv1"
"testhashs64_fnv1"
"testhashu64_fnv1"
"testhashs32_fnv1a"
"testhashu32_fnv1a"
"testhashs64_fnv1a"
"testhashu64_fnv1a"
"flex"
+79
View File
@@ -0,0 +1,79 @@
# Test Flatbuffers library with help of libFuzzer
Test suite of Flatbuffers library has fuzzer section with tests are based on libFuzzer library.
> LibFuzzer is in-process, coverage-guided, evolutionary fuzzing engine.
LibFuzzer is linked with the library under test, and feeds fuzzed inputs to the library via a specific fuzzing entrypoint (aka “target function”);
the fuzzer then tracks which areas of the code are reached, and generates mutations on the corpus of input data in order to maximize the code coverage.
The code coverage information for libFuzzer is provided by LLVMs SanitizerCoverage instrumentation.
For details about **libFuzzer** see: https://llvm.org/docs/LibFuzzer.html
To build and run these tests LLVM compiler (with clang frontend) and CMake should be installed before.
The fuzzer section include four tests:
- `annotator_fuzzer` checks that inputs given to the flatc --annotate are always parsable;
- `verifier_fuzzer` checks stability of deserialization engine for `Monster` schema;
- `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;
- `flexverifier_fuzzer` checks stability of deserialization engine for FlexBuffers only;
## Build
```sh
cd tests/fuzzer
CC=clang CXX=clang++ cmake . -DCMAKE_BUILD_TYPE=Debug -DUSE_ASAN=ON
```
## 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
>FLATBUFFERS_TEST_LOCALE="" ./scalar_parser
>FLATBUFFERS_TEST_LOCALE="ru_RU.CP1251" ./parser_fuzzer
```
## Run fuzzer
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 ../.corpus_verifier/ ../.seed_verifier/`
`./parser_fuzzer -only_ascii=1 -max_len=500 -dict=../parser_fbs.dict ../.corpus_parser/ ../.seed_parser/`
`./monster_fuzzer -only_ascii=1 -max_len=500 -dict=../monster_json.dict ../.corpus_monster/ ../.seed_monster/`
`./scalar_fuzzer -use_value_profile=1 -max_len=500 -dict=../scalar_json.dict ../.corpus_scalar/ ../.seed_scalar/`
Flag `-only_ascii=1` is useful for fast number-compatibility checking while run `scalar_fuzzer`.
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 corpuses to a seed directory (a new collected corpus to the seed collection, for example):
`./verifier_fuzzer -merge=1 ../.seed_verifier/ ../.corpus_verifier/`
`./parser_fuzzer -merge=1 ../.seed_parser/ ../.corpus_parser/`
`./monster_fuzzer -merge=1 ../.seed_monster/ ../.corpus_monster/`
`./scalar_fuzzer -merge=1 ../.seed_scalar/ ../.corpus_scalar/`
## 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.
Example: `./scalar_fuzzer -max_len=3000`
# Fuzzing control
## Set timeout or memory limit
`-timeout=10 -rss_limit_mb=2048 -jobs=4 -workers=4`.
## Force stop on first UBSAN error
- `export UBSAN_OPTIONS=halt_on_error=1`
- `export ASAN_OPTIONS=halt_on_error=1`
+28
View File
@@ -0,0 +1,28 @@
#include <iostream>
#include "flatbuffers/util.h"
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
int main(int argc, char *argv[]) {
if (argc < 2) {
std::cerr << "Usage: scalar_debug <path to fuzzer crash file>\n";
return 0;
}
std::string crash_file_name(argv[1]);
std::string crash_file_data;
auto done =
flatbuffers::LoadFile(crash_file_name.c_str(), true, &crash_file_data);
if (!done) {
std::cerr << "Can not load file: '" << crash_file_name << "'";
return -1;
}
if (crash_file_data.size() < 3) {
std::cerr << "Invalid file data: '" << crash_file_data << "'";
return -2;
}
auto rc = LLVMFuzzerTestOneInput(
reinterpret_cast<const uint8_t *>(crash_file_data.data()),
crash_file_data.size());
std::cout << "LLVMFuzzerTestOneInput finished with code " << rc << "\n\n";
return rc;
}
+37
View File
@@ -0,0 +1,37 @@
"-"
"+"
"."
"e"
"e+"
"e-"
"E"
"E+"
"E-"
"0x"
"-0x"
"p"
"0"
"1"
"2"
"3"
"4"
"5"
"6"
"7"
"8"
"9"
"a"
"b"
"c"
"d"
"e"
"f"
"nan"
"-nan"
"+nan"
"inf"
"+inf"
"-inf"
"infinity"
"+infinity"
"-infinity"
+52
View File
@@ -0,0 +1,52 @@
#ifndef FUZZER_TEST_INIT_H_
#define FUZZER_TEST_INIT_H_
#include "fuzzer_assert.h"
#include "test_assert.h"
// 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 = nullptr) {
(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_