diff --git a/.github/workflows/validate-cpp.yml b/.github/workflows/validate-cpp.yml index c8a0f0e8..9750bb9d 100644 --- a/.github/workflows/validate-cpp.yml +++ b/.github/workflows/validate-cpp.yml @@ -31,6 +31,26 @@ jobs: - name: Unit tests run: ./unit_tests ${{ matrix.mode }} + + build_fuzzers: + name: Build fuzzers [${{ matrix.toolchain }}][${{ matrix.mode }}] + runs-on: ubuntu-latest + strategy: + matrix: + mode: [Debug, Release] + toolchain: [Clang] + + steps: + - uses: actions/checkout@v3 + + - name: Setup + uses: ./.github/actions/setup-cpp + with: + toolchain: ${{ matrix.toolchain }} + + - name: Unit tests + run: ./build_fuzz_tests ${{ matrix.mode }} + benchmark: name: Benchmark [${{ matrix.toolchain }}] runs-on: ${{ (matrix.toolchain == 'MSVC') && 'windows-latest' || 'ubuntu-latest' }} diff --git a/CMakeLists.txt b/CMakeLists.txt index f4ce73cc..55f1a6df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,12 @@ include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/project-defaults.cmake) add_subdirectory(yoga) add_subdirectory(tests) +option(BUILD_FUZZ_TESTS "Build fuzz tests" OFF) + +if ('${CMAKE_CXX_COMPILER_ID}' MATCHES 'Clang' AND BUILD_FUZZ_TESTS) + add_subdirectory(fuzz) +endif() + # cmake install config include(GNUInstallDirs) include(CMakePackageConfigHelpers) diff --git a/build_fuzz_tests b/build_fuzz_tests new file mode 100755 index 00000000..eeb86d2b --- /dev/null +++ b/build_fuzz_tests @@ -0,0 +1,28 @@ +#!/usr/bin/env sh +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +if [ "$#" -eq 0 ]; then + build_type="Debug" +else + build_type="$1" +fi + +export CC=clang +export CXX=clang++ + +# Make sure libc++ isn't used, as libfuzzer is linked against stdlibc++ which causes conflicts +unset CXXFLAGS +export LDFLAGS=-lstdc++ + +if which ninja; then + set -e + cmake -B build -S . -D BUILD_FUZZ_TESTS=ON -D CMAKE_BUILD_TYPE="$build_type" -G Ninja +else + set -e + cmake -B build -S . -D BUILD_FUZZ_TESTS=ON -D CMAKE_BUILD_TYPE="$build_type" +fi + +cmake --build build --target fuzz_layout diff --git a/fuzz/CMakeLists.txt b/fuzz/CMakeLists.txt new file mode 100644 index 00000000..e8ececa3 --- /dev/null +++ b/fuzz/CMakeLists.txt @@ -0,0 +1,21 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +# If google/oss-fuzz has set the fuzzing engine +if(DEFINED ENV{LIB_FUZZING_ENGINE}) + set(FUZZING_ENGINE $ENV{LIB_FUZZING_ENGINE}) + set(FUZZING_COMPILE_FLAGS "") + set(FUZZING_LINK_FLAGS "${FUZZING_ENGINE}") +else() + set(FUZZING_COMPILE_FLAGS "-fsanitize=fuzzer") + set(FUZZING_LINK_FLAGS "-fsanitize=fuzzer") +endif() + +add_executable(fuzz_layout FuzzLayout.cpp) +set_target_properties(fuzz_layout PROPERTIES + COMPILE_FLAGS "${FUZZING_COMPILE_FLAGS}" + LINK_FLAGS "${FUZZING_LINK_FLAGS}" +) +target_link_libraries(fuzz_layout yogacore) diff --git a/fuzz/FuzzLayout.cpp b/fuzz/FuzzLayout.cpp new file mode 100644 index 00000000..989b2aee --- /dev/null +++ b/fuzz/FuzzLayout.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include +#include + +YGFlexDirection fuzzedFlexDirection(FuzzedDataProvider& fdp) { + return fdp.PickValueInArray({ + YGFlexDirectionColumn, + YGFlexDirectionColumnReverse, + YGFlexDirectionRow, + YGFlexDirectionRowReverse, + }); +} + +void fillFuzzedTree( + FuzzedDataProvider& fdp, + YGConfigConstRef config, + YGNodeRef root, + size_t depth = 0) { + constexpr size_t kMaxDepth = 20; + constexpr size_t kMaxChildren = 20; + + if (depth > kMaxDepth) { + return; + } + + size_t children = fdp.ConsumeIntegralInRange(0, kMaxChildren); + for (size_t i = 0; i < children; i++) { + YGNodeRef child = YGNodeNewWithConfig(config); + YGNodeStyleSetFlexDirection(root, fuzzedFlexDirection(fdp)); + YGNodeStyleSetWidth(child, fdp.ConsumeFloatingPoint()); + YGNodeStyleSetGap( + child, YGGutterAll, fdp.ConsumeProbability() * 100); + YGNodeStyleSetHeight(child, fdp.ConsumeFloatingPoint()); + YGNodeInsertChild(root, child, i); + fillFuzzedTree(fdp, config, child, depth + 1); + } +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + FuzzedDataProvider fdp(data, size); + YGConfigRef config = YGConfigNew(); + YGNodeRef root = YGNodeNewWithConfig(config); + fillFuzzedTree(fdp, config, root); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + YGNodeFreeRecursive(root); + YGConfigFree(config); + return 0; +}