Skip to content

Commit

Permalink
Integrate immer-archive
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-sparus committed Feb 16, 2024
1 parent 57a8c5e commit 80fd4b3
Show file tree
Hide file tree
Showing 50 changed files with 8,359 additions and 32 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ option(ENABLE_GUILE "enable building guile module" off)
option(ENABLE_BOOST_COROUTINE "run benchmarks with boost coroutine" off)

option(immer_BUILD_TESTS "Build tests" ON)
option(immer_BUILD_ARCHIVE_TESTS "Build experimental archive tests" off)
option(immer_BUILD_EXAMPLES "Build examples" ON)
option(immer_BUILD_DOCS "Build docs" ON)
option(immer_BUILD_EXTRAS "Build extras" ON)
Expand Down
31 changes: 20 additions & 11 deletions extra/fuzzer/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,32 +1,41 @@
add_custom_target(fuzzers COMMENT "Build all fuzzers.")

add_custom_target(fuzzers
COMMENT "Build all fuzzers.")

if (CHECK_FUZZERS)
if(CHECK_FUZZERS)
add_dependencies(tests fuzzers)
endif()

# LIB_FUZZING_ENGINE is set by the Google OSS-Fuzz
# infrastructure. Otherwise we use Clang's LibFuzzer
if (DEFINED ENV{LIB_FUZZING_ENGINE})
# LIB_FUZZING_ENGINE is set by the Google OSS-Fuzz infrastructure. Otherwise we
# use Clang's LibFuzzer
if(DEFINED ENV{LIB_FUZZING_ENGINE})
set(immer_fuzzing_engine $ENV{LIB_FUZZING_ENGINE})
else()
set(immer_fuzzing_engine "-fsanitize=fuzzer")
endif()

file(GLOB_RECURSE immer_fuzzers "*.cpp")
foreach(TMP_PATH ${immer_fuzzers})
string(FIND ${TMP_PATH} immer-archive EXCLUDE_DIR_FOUND)
if(NOT ${EXCLUDE_DIR_FOUND} EQUAL -1)
list(REMOVE_ITEM immer_fuzzers ${TMP_PATH})
endif()
endforeach(TMP_PATH)

foreach(_file IN LISTS immer_fuzzers)
immer_target_name_for(_target _output "${_file}")
add_executable(${_target} EXCLUDE_FROM_ALL "${_file}")
set_target_properties(${_target} PROPERTIES OUTPUT_NAME ${_output})
target_compile_options(${_target} PUBLIC ${immer_fuzzing_engine})
target_link_libraries(${_target} PUBLIC ${immer_fuzzing_engine}
immer-dev)
target_compile_definitions(${_target} PUBLIC IMMER_THROW_ON_INVALID_STATE=1)
target_link_libraries(${_target} PUBLIC ${immer_fuzzing_engine} immer-dev)
add_dependencies(fuzzers ${_target})
if (CHECK_FUZZERS)
if(CHECK_FUZZERS)
add_test("fuzzer/${_output}" ${_output} -max_total_time=1)
endif()
if (immer_INSTALL_FUZZERS)
if(immer_INSTALL_FUZZERS)
install(TARGETS ${_target} DESTINATION bin)
endif()
endforeach()

if(immer_BUILD_ARCHIVE_TESTS)
add_subdirectory(immer-archive)
endif()
21 changes: 19 additions & 2 deletions extra/fuzzer/flex-vector-st.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,25 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
auto is_valid_size = [](auto& v) {
return [&](auto idx) { return idx >= 0 && idx <= v.size(); };
};
auto can_concat = [](auto&& v1, auto&& v2) {
return v1.size() + v2.size() < vector_t::max_size();
auto can_concat = [](const auto& v1, const auto& v2) {
// First, check max_size
if (v1.size() + v2.size() > vector_t::max_size()) {
return false;
}

// But just checking max_size is not sufficient, because there are other
// conditions for the validity of the tree, like shift constraints, for
// example.
try {
// Try to concat and catch an exception if it fails
const auto v3 = v1 + v2;
if (v3.size()) {
return true;
}
} catch (const immer::detail::rbts::invalid_tree&) {
return false;
}
return true;
};
auto can_compare = [](auto&& v) {
// avoid comparing vectors that are too big, and hence, slow to compare
Expand Down
19 changes: 19 additions & 0 deletions extra/fuzzer/immer-archive/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
find_package(spdlog REQUIRED)

file(GLOB_RECURSE immer_fuzzers "*.cpp")
foreach(_file IN LISTS immer_fuzzers)
immer_target_name_for(_target _output "${_file}")
add_executable(${_target} EXCLUDE_FROM_ALL "${_file}")
set_target_properties(${_target} PROPERTIES OUTPUT_NAME ${_output})
target_compile_options(${_target} PUBLIC ${immer_fuzzing_engine})
target_include_directories(${_target} PRIVATE ../..)
target_link_libraries(${_target} PUBLIC ${immer_fuzzing_engine} immer-dev
spdlog::spdlog)
add_dependencies(fuzzers ${_target})
if(CHECK_FUZZERS)
add_test("fuzzer/${_output}" ${_output} -max_total_time=1)
endif()
if(immer_INSTALL_FUZZERS)
install(TARGETS ${_target} DESTINATION bin)
endif()
endforeach()
184 changes: 184 additions & 0 deletions extra/fuzzer/immer-archive/flex-vector.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
//
// immer: immutable data structures for C++
// Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente
//
// This software is distributed under the Boost Software License, Version 1.0.
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
//

#include <fuzzer/fuzzer_input.hpp>

#include <immer/box.hpp>
#include <immer/flex_vector.hpp>

#include <fmt/ranges.h>
#include <immer/experimental/immer-archive/rbts/load.hpp>
#include <immer/experimental/immer-archive/rbts/save.hpp>

#include <array>

namespace {
void require_eq(const auto& a, const auto& b)
{
if (a != b) {
throw std::runtime_error{
fmt::format("{} != {}", fmt::join(a, ", "), fmt::join(b, ", "))};
}
}
} // namespace

extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
std::size_t size)
{
constexpr auto var_count = 8;
constexpr auto bits = 2;

using vector_t =
immer::flex_vector<int, immer::default_memory_policy, bits, bits>;
using size_t = std::uint8_t;

const auto check_save_and_load = [&](const auto& vec) {
auto ar = immer_archive::rbts::make_save_archive_for(vec);
auto vector_id = immer_archive::container_id{};
std::tie(ar, vector_id) = immer_archive::rbts::save_to_archive(vec, ar);

auto loader =
immer_archive::rbts::make_loader_for(vec, fix_leaf_nodes(ar));
auto loaded = loader.load(vector_id);
require_eq(vec, loaded);
};

auto vars = std::array<vector_t, var_count>{};

auto is_valid_var = [&](auto idx) { return idx >= 0 && idx < var_count; };
auto is_valid_var_neq = [](auto other) {
return [=](auto idx) {
return idx >= 0 && idx < var_count && idx != other;
};
};
auto is_valid_index = [](auto& v) {
return [&](auto idx) { return idx >= 0 && idx < v.size(); };
};
auto is_valid_size = [](auto& v) {
return [&](auto idx) { return idx >= 0 && idx <= v.size(); };
};
auto can_concat = [](auto&& v1, auto&& v2) {
return v1.size() + v2.size() < vector_t::max_size() / 4;
};
auto can_compare = [](auto&& v) {
// avoid comparing vectors that are too big, and hence, slow to compare
return v.size() < (1 << 15);
};
return fuzzer_input{data, size}.run([&](auto& in) {
enum ops
{
op_push_back,
op_update,
op_take,
op_drop,
op_concat,
op_push_back_move,
op_update_move,
op_take_move,
op_drop_move,
op_concat_move_l,
op_concat_move_r,
op_concat_move_lr,
op_insert,
op_erase,
op_compare,
};
auto src = read<char>(in, is_valid_var);
auto dst = read<char>(in, is_valid_var);
const auto op = read<char>(in);
SPDLOG_DEBUG("op = {}", static_cast<int>(op));
switch (op) {
case op_push_back: {
vars[dst] = vars[src].push_back(42);
break;
}
case op_update: {
auto idx = read<size_t>(in, is_valid_index(vars[src]));
vars[dst] = vars[src].update(idx, [](auto x) { return x + 1; });
break;
}
case op_take: {
auto idx = read<size_t>(in, is_valid_size(vars[src]));
vars[dst] = vars[src].take(idx);
break;
}
case op_drop: {
auto idx = read<size_t>(in, is_valid_size(vars[src]));
vars[dst] = vars[src].drop(idx);
break;
}
case op_concat: {
auto src2 = read<char>(in, is_valid_var);
if (can_concat(vars[src], vars[src2]))
vars[dst] = vars[src] + vars[src2];
break;
}
case op_push_back_move: {
vars[dst] = std::move(vars[src]).push_back(21);
break;
}
case op_update_move: {
auto idx = read<size_t>(in, is_valid_index(vars[src]));
vars[dst] =
std::move(vars[src]).update(idx, [](auto x) { return x + 1; });
break;
}
case op_take_move: {
auto idx = read<size_t>(in, is_valid_size(vars[src]));
vars[dst] = std::move(vars[src]).take(idx);
break;
}
case op_drop_move: {
auto idx = read<size_t>(in, is_valid_size(vars[src]));
vars[dst] = std::move(vars[src]).drop(idx);
break;
}
case op_concat_move_l: {
auto src2 = read<char>(in, is_valid_var_neq(src));
if (can_concat(vars[src], vars[src2]))
vars[dst] = std::move(vars[src]) + vars[src2];
break;
}
case op_concat_move_r: {
auto src2 = read<char>(in, is_valid_var_neq(src));
if (can_concat(vars[src], vars[src2]))
vars[dst] = vars[src] + std::move(vars[src2]);
break;
}
case op_concat_move_lr: {
auto src2 = read<char>(in, is_valid_var_neq(src));
if (can_concat(vars[src], vars[src2]))
vars[dst] = std::move(vars[src]) + std::move(vars[src2]);
break;
}
case op_compare: {
using std::swap;
if (can_compare(vars[src]) && vars[src] == vars[dst])
swap(vars[src], vars[dst]);
break;
}
case op_erase: {
auto idx = read<size_t>(in, is_valid_index(vars[src]));
vars[dst] = vars[src].erase(idx);
break;
}
case op_insert: {
auto idx = read<size_t>(in, is_valid_size(vars[src]));
vars[dst] = vars[src].insert(idx, immer::box<int>{42});
break;
}
default:
break;
};

check_save_and_load(vars[src]);
check_save_and_load(vars[dst]);

return true;
});
}
Loading

0 comments on commit 80fd4b3

Please sign in to comment.