From 0fb1d44bc42a828a1ce8669e3fa001de2b1bd8bd Mon Sep 17 00:00:00 2001 From: Vladimir Glavnyy <31897320+vglavnyy@users.noreply.github.com> Date: Tue, 30 Oct 2018 01:29:05 +0700 Subject: [PATCH] Add 'fsanitize=address,undefined' to tests and flatc targets (#5009) * Add '-fsanitize' optional flags to flattests and flatc targets Control: -DFLATBUFFERS_CODE_SANITIZE=(ON | OFF | "=memory,undefined") Travis-CI: building with -DFLATBUFFERS_CODE_SANITIZE=ON * Fix -pie flag * Cleanup --- .travis.yml | 16 ++++++++++++++-- CMakeLists.txt | 33 ++++++++++++++++++++++++++++++++- include/flatbuffers/base.h | 17 +++++++++++++++-- tests/test.cpp | 11 +++++++++-- 4 files changed, 70 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 743e9cdb5..40d38302f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -73,7 +73,13 @@ matrix: script: - bash grpc/build_grpc.sh - - cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DFLATBUFFERS_BUILD_GRPCTEST=ON -DGRPC_INSTALL_PATH=$TRAVIS_BUILD_DIR/google/grpc/install -DPROTOBUF_DOWNLOAD_PATH=$TRAVIS_BUILD_DIR/google/grpc/third_party/protobuf . && make + - cmake . + -DCMAKE_BUILD_TYPE=$BUILD_TYPE + -DFLATBUFFERS_BUILD_GRPCTEST=ON + -DGRPC_INSTALL_PATH=$TRAVIS_BUILD_DIR/google/grpc/install + -DPROTOBUF_DOWNLOAD_PATH=$TRAVIS_BUILD_DIR/google/grpc/third_party/protobuf + -DFLATBUFFERS_CODE_SANITIZE=ON + - make - LD_LIBRARY_PATH=$TRAVIS_BUILD_DIR/google/grpc/install/lib make test ARGS=-V - bash .travis/check-generate-code.sh - if [ "$CONAN" == "true" ] && [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo pip install conan && conan create . google/testing -s build_type=$BUILD_TYPE -tf conan/test_package; fi @@ -87,7 +93,13 @@ matrix: - BUILD_TYPE=Release script: - bash grpc/build_grpc.sh - - cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DFLATBUFFERS_BUILD_GRPCTEST=ON -DGRPC_INSTALL_PATH=$TRAVIS_BUILD_DIR/google/grpc/install -DPROTOBUF_DOWNLOAD_PATH=$TRAVIS_BUILD_DIR/google/grpc/third_party/protobuf . && make + - cmake . + -DCMAKE_BUILD_TYPE=$BUILD_TYPE + -DFLATBUFFERS_BUILD_GRPCTEST=ON + -DGRPC_INSTALL_PATH=$TRAVIS_BUILD_DIR/google/grpc/install + -DPROTOBUF_DOWNLOAD_PATH=$TRAVIS_BUILD_DIR/google/grpc/third_party/protobuf + -DFLATBUFFERS_CODE_SANITIZE=ON + - make - ./flattests - bash .travis/check-generate-code.sh - DYLD_LIBRARY_PATH=$TRAVIS_BUILD_DIR/google/grpc/install/lib ./grpctest diff --git a/CMakeLists.txt b/CMakeLists.txt index d88097b96..2a548fed2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,6 @@ cmake_minimum_required(VERSION 2.8) +# generate compile_commands.json +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) project(FlatBuffers) @@ -16,6 +18,10 @@ option(FLATBUFFERS_BUILD_SHAREDLIB "Enable the build of the flatbuffers shared library" OFF) option(FLATBUFFERS_LIBCXX_WITH_CLANG "Force libc++ when using Clang" ON) +# NOTE: Sanitizer check only works on Linux & OSX (gcc & llvm). +option(FLATBUFFERS_CODE_SANITIZE + "Add '-fsanitize' flags to 'flattests' and 'flatc' targets." + OFF) if(NOT FLATBUFFERS_BUILD_FLATC AND FLATBUFFERS_BUILD_TESTS) message(WARNING @@ -190,6 +196,26 @@ if(FLATBUFFERS_CODE_COVERAGE) "${CMAKE_EXE_LINKER_FLAGS} -fprofile-arcs -ftest-coverage") endif() +function(add_fsanitize_to_target _target _sanitizer) + # FLATBUFFERS_CODE_SANITIZE: boolean {ON,OFF,YES,NO} or string with list of sanitizer. + # List of sanitizer is string starts with '=': "=address,undefined,thread,memory". + if((${CMAKE_CXX_COMPILER_ID} MATCHES "Clang") OR + ((${CMAKE_CXX_COMPILER_ID} MATCHES "GNU") AND NOT (CMAKE_CXX_COMPILER_VERSION VERSION_LESS "4.8")) + ) + set(_sanitizer_flags "=address,undefined") + if(_sanitizer MATCHES "=.*") + # override default by user-defined sanitizer list + set(_sanitizer_flags ${_sanitizer}) + endif() + target_compile_options(${_target} PRIVATE + -g -fsigned-char -fno-omit-frame-pointer + "-fsanitize${_sanitizer_flags}") + target_link_libraries(${_target} PRIVATE + "-fsanitize${_sanitizer_flags}") + set_property(TARGET ${_target} PROPERTY POSITION_INDEPENDENT_CODE ON) + endif() +endfunction() + if(BIICODE) include(biicode/cmake/biicode.cmake) return() @@ -209,6 +235,9 @@ endif() if(FLATBUFFERS_BUILD_FLATC) add_executable(flatc ${FlatBuffers_Compiler_SRCS}) target_compile_options(flatc PRIVATE "${FLATBUFFERS_PRIVATE_CXX_FLAGS}") + if(FLATBUFFERS_CODE_SANITIZE) + add_fsanitize_to_target(flatc ${FLATBUFFERS_CODE_SANITIZE}) + endif() if(NOT FLATBUFFERS_FLATC_EXECUTABLE) set(FLATBUFFERS_FLATC_EXECUTABLE $) endif() @@ -264,7 +293,9 @@ if(FLATBUFFERS_BUILD_TESTS) compile_flatbuffers_schema_to_cpp(tests/monster_test.fbs) include_directories(${CMAKE_CURRENT_BINARY_DIR}/tests) add_executable(flattests ${FlatBuffers_Tests_SRCS}) - target_compile_options(flattests PRIVATE "${FLATBUFFERS_PRIVATE_CXX_FLAGS}") + if(FLATBUFFERS_CODE_SANITIZE) + add_fsanitize_to_target(flattests ${FLATBUFFERS_CODE_SANITIZE}) + endif() set_property(TARGET flattests PROPERTY COMPILE_DEFINITIONS FLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE FLATBUFFERS_DEBUG_VERIFICATION_FAILURE=1) diff --git a/include/flatbuffers/base.h b/include/flatbuffers/base.h index 7e6dc53ef..f6eccc6c2 100644 --- a/include/flatbuffers/base.h +++ b/include/flatbuffers/base.h @@ -195,6 +195,15 @@ #endif #endif // !FLATBUFFERS_HAS_NEW_STRTOD +// Suppress sanitizer directives. +#if defined(__clang__) + #define __no_sanitize_undefined__(reason) __attribute__((no_sanitize("undefined"))) +#elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 408) + #define __no_sanitize_undefined__(reason) __attribute__((no_sanitize_undefined)) +#else + #define __no_sanitize_undefined__(reason) +#endif + /// @endcond /// @file @@ -277,11 +286,15 @@ template T EndianScalar(T t) { #endif } -template T ReadScalar(const void *p) { +template +__no_sanitize_undefined__("C++ aliasing type rules, see std::bit_cast<>") +T ReadScalar(const void *p) { return EndianScalar(*reinterpret_cast(p)); } -template void WriteScalar(void *p, T t) { +template +__no_sanitize_undefined__("C++ aliasing type rules, see std::bit_cast<>") +void WriteScalar(void *p, T t) { *reinterpret_cast(p) = EndianScalar(t); } diff --git a/tests/test.cpp b/tests/test.cpp index 49f414c06..90c791736 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -1346,8 +1346,15 @@ void EnumNamesTest() { TEST_EQ_STR("Red", EnumNameColor(Color_Red)); TEST_EQ_STR("Green", EnumNameColor(Color_Green)); TEST_EQ_STR("Blue", EnumNameColor(Color_Blue)); - TEST_EQ_STR("", EnumNameColor(static_cast(-1))); - TEST_EQ_STR("", EnumNameColor(static_cast(1000))); + // Check that Color to string don't crash while decode a mixture of Colors. + // 1) Example::Color enum is enum with unfixed underlying type. + // 2) Valid enum range: [0; 2^(ceil(log2(Color_ANY))) - 1]. + // Consequence: A value is out of this range will lead to UB (since C++17). + // For details see C++17 standard or explanation on the SO: + // stackoverflow.com/questions/18195312/what-happens-if-you-static-cast-invalid-value-to-enum-class + TEST_EQ_STR("", EnumNameColor(static_cast(0))); + TEST_EQ_STR("", EnumNameColor(static_cast(Color_ANY-1))); + TEST_EQ_STR("", EnumNameColor(static_cast(Color_ANY+1))); } void EnumOutOfRangeTest() {