From 175edad90884bf7237f21ba9bca9f9ea5aa961c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Tue, 31 May 2022 17:28:05 +0200 Subject: [PATCH 001/104] Sketch mutable removal from HAMTs --- immer/detail/hamts/champ.hpp | 98 ++++++++++++++++++++++++++++++++++ immer/map.hpp | 3 +- immer/map_transient.hpp | 6 +-- immer/set.hpp | 3 +- immer/set_transient.hpp | 6 +-- test/set_transient/generic.ipp | 35 ++++++++++++ 6 files changed, 137 insertions(+), 14 deletions(-) diff --git a/immer/detail/hamts/champ.hpp b/immer/detail/hamts/champ.hpp index 336fe8e0..e9169cbd 100644 --- a/immer/detail/hamts/champ.hpp +++ b/immer/detail/hamts/champ.hpp @@ -849,6 +849,104 @@ struct champ } } + template + sub_result do_sub_mut( + edit_t e, node_t* node, const K& k, hash_t hash, shift_t shift) const + { + if (shift == max_shift) { + auto fst = node->collisions(); + auto lst = fst + node->collision_count(); + for (auto cur = fst; cur != lst; ++cur) + if (Equal{}(*cur, k)) + return node->collision_count() > 2 + ? node_t::copy_collision_remove(node, cur) + : sub_result{fst + (cur == fst)}; + return {}; + } else { + auto idx = (hash & (mask << shift)) >> shift; + auto bit = bitmap_t{1u} << idx; + if (node->nodemap() & bit) { + auto offset = node->children_count(bit); + auto mutate = node->can_mutate(e); + auto children = node->children(); + auto child = children[offset]; + auto result = mutate ? do_sub_mut(e, child, k, hash, shift + B) + : do_sub(child, k, hash, shift + B); + switch (result.kind) { + case sub_result::nothing: + return {}; + case sub_result::singleton: + return node->datamap() == 0 && + node->children_count() == 1 && shift > 0 + ? result + : node_t::copy_inner_replace_inline( + node, bit, offset, *result.data.singleton); + case sub_result::tree: + if (mutate) { + if (child != result.data.tree) { + children[offset] = result.data.tree; + if (child->dec()) + node_t::delete_deep_shift(child, shift + B); + } + return node; + } else { + IMMER_TRY { + return node_t::copy_inner_replace( + node, offset, result.data.tree); + } + IMMER_CATCH (...) { + node_t::delete_deep_shift(result.data.tree, + shift + B); + IMMER_RETHROW; + } + } + } + } else if (node->datamap() & bit) { + auto offset = node->data_count(bit); + auto val = node->values() + offset; + if (Equal{}(*val, k)) { + auto nv = node->data_count(); + if (node->nodemap() || nv > 2) + return node_t::copy_inner_remove_value( + node, bit, offset); + else if (nv == 2) { + return shift > 0 ? sub_result{node->values() + !offset} + : node_t::make_inner_n( + 0, + node->datamap() & ~bit, + node->values()[!offset]); + } else { + assert(shift == 0); + return empty(); + } + } + } + return {}; + } + } + + template + void sub_mut(edit_t e, const K& k) + { + auto hash = Hash{}(k); + auto res = do_sub_mut(e, root, k, hash, 0); + switch (res.kind) { + case sub_result::nothing: + break; + case sub_result::tree: + if (root != res.data.tree) { + auto p = root; + root = res.data.tree; + if (p->dec()) + node_t::delete_deep(p, 0); + } + --size; + break; + default: + IMMER_UNREACHABLE; + } + } + template bool equals(const champ& other) const { diff --git a/immer/map.hpp b/immer/map.hpp index f4699dea..33315ad9 100644 --- a/immer/map.hpp +++ b/immer/map.hpp @@ -489,8 +489,7 @@ class map map&& erase_move(std::true_type, const key_type& value) { - // xxx: implement mutable version - impl_ = impl_.sub(value); + impl_.sub_mut({}, value); return std::move(*this); } map erase_move(std::false_type, const key_type& value) diff --git a/immer/map_transient.hpp b/immer/map_transient.hpp index 6f9ed05c..d1ac4760 100644 --- a/immer/map_transient.hpp +++ b/immer/map_transient.hpp @@ -277,11 +277,7 @@ class map_transient : MemoryPolicy::transience_t::owner * associated in the map. It may allocate memory and its complexity is * *effectively* @f$ O(1) @f$. */ - void erase(const K& k) - { - // xxx: implement mutable version - impl_ = impl_.sub(k); - } + void erase(const K& k) { impl_.sub_mut(*this, k); } /*! * Returns an @a immutable form of this container, an diff --git a/immer/set.hpp b/immer/set.hpp index 55bdebf8..247ef83f 100644 --- a/immer/set.hpp +++ b/immer/set.hpp @@ -271,8 +271,7 @@ class set set&& erase_move(std::true_type, const value_type& value) { - // xxx: implement mutable version - impl_ = impl_.sub(value); + impl_.sub_mut({}, value); return std::move(*this); } set erase_move(std::false_type, const value_type& value) diff --git a/immer/set_transient.hpp b/immer/set_transient.hpp index 033ba6cf..11aa644f 100644 --- a/immer/set_transient.hpp +++ b/immer/set_transient.hpp @@ -158,11 +158,7 @@ class set_transient : MemoryPolicy::transience_t::owner * the set. It may allocate memory and its complexity is *effectively* @f$ * O(1) @f$. */ - void erase(const T& value) - { - // xxx: implement mutable version - impl_ = impl_.sub(value); - } + void erase(const T& value) { impl_.sub_mut(*this, value); } /*! * Returns an @a immutable form of this container, an diff --git a/test/set_transient/generic.ipp b/test/set_transient/generic.ipp index 0457e024..3653d48c 100644 --- a/test/set_transient/generic.ipp +++ b/test/set_transient/generic.ipp @@ -62,3 +62,38 @@ TEST_CASE("erase") CHECK(t.count(12) == 1); CHECK(t.size() == 1); } + +TEST_CASE("insert erase many") +{ + auto t = SET_T{}.transient(); + auto n = 1000; + for (auto i = 0; i < n; ++i) { + t.insert(i); + CHECK(t.find(i) != nullptr); + CHECK(t.size() == static_cast(i + 1)); + } + for (auto i = 0; i < n; ++i) { + t.erase(i); + CHECK(t.find(i) == nullptr); + CHECK(t.size() == static_cast(n - i - 1)); + } +} + +TEST_CASE("erase many from non transient") +{ + const auto n = 10000; + auto t = [] { + auto t = SET_T{}; + for (auto i = 0; i < n; ++i) { + t = t.insert(i); + CHECK(t.find(i) != nullptr); + CHECK(t.size() == static_cast(i + 1)); + } + return t.transient(); + }(); + for (auto i = 0; i < n; ++i) { + t.erase(i); + CHECK(t.find(i) == nullptr); + CHECK(t.size() == static_cast(n - i - 1)); + } +} From 9e62f8ccd538476f1e96e21fef87911f6f57c19d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Wed, 1 Jun 2022 17:51:25 +0200 Subject: [PATCH 002/104] Ensure that edit markers are set properly during transient updates This ensures that mutations can actually happen when using a tracing garbage collector. --- immer/detail/hamts/champ.hpp | 126 +++++++++++++++++++---------------- immer/detail/hamts/node.hpp | 84 +++++++++++++++++++---- 2 files changed, 139 insertions(+), 71 deletions(-) diff --git a/immer/detail/hamts/champ.hpp b/immer/detail/hamts/champ.hpp index e9169cbd..4c95b60d 100644 --- a/immer/detail/hamts/champ.hpp +++ b/immer/detail/hamts/champ.hpp @@ -421,12 +421,13 @@ struct champ *fst = std::move(v); return {node, false}; } else { - return {node_t::copy_collision_replace( - node, fst, std::move(v)), - false}; + auto r = node_t::copy_collision_replace( + node, fst, std::move(v)); + return {node_t::owned(r, e), false}; } } - return {node_t::copy_collision_insert(node, std::move(v)), true}; + auto r = node_t::copy_collision_insert(node, std::move(v)); + return {node_t::owned(r, e), true}; } else { auto idx = (hash & (mask << shift)) >> shift; auto bit = bitmap_t{1u} << idx; @@ -449,7 +450,7 @@ struct champ IMMER_TRY { result.first = node_t::copy_inner_replace( node, offset, result.first); - node_t::ownee(result.first) = e; + node_t::owned(result.first, e); return result; } IMMER_CATCH (...) { @@ -466,17 +467,17 @@ struct champ vals[offset] = std::move(v); return {node, false}; } else { - return {node_t::copy_inner_replace_value( - node, offset, std::move(v)), - false}; + auto r = node_t::copy_inner_replace_value( + node, offset, std::move(v)); + return {node_t::owned_values(r, e), false}; } } else { - auto child = node_t::make_merged( - shift + B, std::move(v), hash, *val, Hash{}(*val)); + auto child = node_t::make_merged_e( + e, shift + B, std::move(v), hash, *val, Hash{}(*val)); IMMER_TRY { - return {node_t::copy_inner_replace_merged( - node, bit, offset, child), - true}; + auto r = node_t::copy_inner_replace_merged( + node, bit, offset, child); + return {node_t::owned_values_safe(r, e), true}; } IMMER_CATCH (...) { node_t::delete_deep_shift(child, shift + B); @@ -484,9 +485,9 @@ struct champ } } } else { - return { - node_t::copy_inner_insert_value(node, bit, std::move(v)), - true}; + auto r = + node_t::copy_inner_insert_value(node, bit, std::move(v)); + return {node_t::owned_values(r, e), true}; } } } @@ -626,20 +627,19 @@ struct champ std::forward(fn)(Project{}(std::move(*fst)))); return {node, false}; } else { - return {node_t::copy_collision_replace( - node, - fst, - Combine{}( - std::forward(k), - std::forward(fn)(Project{}(*fst)))), - false}; + auto r = node_t::copy_collision_replace( + node, + fst, + Combine{}(std::forward(k), + std::forward(fn)(Project{}(*fst)))); + return {node_t::owned(r, e), false}; } } - return {node_t::copy_collision_insert( - node, - Combine{}(std::forward(k), - std::forward(fn)(Default{}()))), - true}; + auto r = node_t::copy_collision_insert( + node, + Combine{}(std::forward(k), + std::forward(fn)(Default{}()))); + return {node_t::owned(r, e), true}; } else { auto idx = (hash & (mask << shift)) >> shift; auto bit = bitmap_t{1u} << idx; @@ -662,6 +662,7 @@ struct champ IMMER_TRY { result.first = node_t::copy_inner_replace( node, offset, result.first); + node_t::owned(result.first, e); return result; } IMMER_CATCH (...) { @@ -680,16 +681,16 @@ struct champ std::move(vals[offset])))); return {node, false}; } else { - return {node_t::copy_inner_replace_value( - node, - offset, - Combine{}( - std::forward(k), - std::forward(fn)(Project{}(*val)))), - false}; + auto r = node_t::copy_inner_replace_value( + node, + offset, + Combine{}(std::forward(k), + std::forward(fn)(Project{}(*val)))); + return {node_t::owned_values(r, e), false}; } } else { - auto child = node_t::make_merged( + auto child = node_t::make_merged_e( + e, shift + B, Combine{}(std::forward(k), std::forward(fn)(Default{}())), @@ -697,9 +698,9 @@ struct champ *val, Hash{}(*val)); IMMER_TRY { - return {node_t::copy_inner_replace_merged( - node, bit, offset, child), - true}; + auto r = node_t::copy_inner_replace_merged( + node, bit, offset, child); + return {node_t::owned_values_safe(r, e), true}; } IMMER_CATCH (...) { node_t::delete_deep_shift(child, shift + B); @@ -707,12 +708,12 @@ struct champ } } } else { - return {node_t::copy_inner_insert_value( - node, - bit, - Combine{}(std::forward(k), - std::forward(fn)(Default{}()))), - true}; + auto r = node_t::copy_inner_insert_value( + node, + bit, + Combine{}(std::forward(k), + std::forward(fn)(Default{}()))); + return {node_t::owned_values(r, e), true}; } } } @@ -859,7 +860,9 @@ struct champ for (auto cur = fst; cur != lst; ++cur) if (Equal{}(*cur, k)) return node->collision_count() > 2 - ? node_t::copy_collision_remove(node, cur) + ? node_t::owned( + node_t::copy_collision_remove(node, cur), + e) : sub_result{fst + (cur == fst)}; return {}; } else { @@ -879,8 +882,13 @@ struct champ return node->datamap() == 0 && node->children_count() == 1 && shift > 0 ? result - : node_t::copy_inner_replace_inline( - node, bit, offset, *result.data.singleton); + : node_t::owned_values( + node_t::copy_inner_replace_inline( + node, + bit, + offset, + *result.data.singleton), + e); case sub_result::tree: if (mutate) { if (child != result.data.tree) { @@ -891,8 +899,9 @@ struct champ return node; } else { IMMER_TRY { - return node_t::copy_inner_replace( + auto r = node_t::copy_inner_replace( node, offset, result.data.tree); + return node_t::owned(r, e); } IMMER_CATCH (...) { node_t::delete_deep_shift(result.data.tree, @@ -906,15 +915,18 @@ struct champ auto val = node->values() + offset; if (Equal{}(*val, k)) { auto nv = node->data_count(); - if (node->nodemap() || nv > 2) - return node_t::copy_inner_remove_value( - node, bit, offset); - else if (nv == 2) { + if (node->nodemap() || nv > 2) { + auto r = + node_t::copy_inner_remove_value(node, bit, offset); + return node_t::owned_values_safe(r, e); + } else if (nv == 2) { return shift > 0 ? sub_result{node->values() + !offset} - : node_t::make_inner_n( - 0, - node->datamap() & ~bit, - node->values()[!offset]); + : node_t::owned_values( + node_t::make_inner_n( + 0, + node->datamap() & ~bit, + node->values()[!offset]), + e); } else { assert(shift == 0); return empty(); diff --git a/immer/detail/hamts/node.hpp b/immer/detail/hamts/node.hpp index efa9e243..2d088ed7 100644 --- a/immer/detail/hamts/node.hpp +++ b/immer/detail/hamts/node.hpp @@ -352,21 +352,24 @@ struct node auto old = impl.d.data.inner.values; if (node_t::can_mutate(old, e)) return values(); - auto nv = data_count(); - auto nxt = new (heap::allocate(sizeof_values_n(nv))) values_t{}; - auto dst = (T*) &nxt->d.buffer; - auto src = values(); - IMMER_TRY { - std::uninitialized_copy(src, src + nv, dst); - } - IMMER_CATCH (...) { - deallocate_values(nxt, nv); - IMMER_RETHROW; + else { + auto nv = data_count(); + auto nxt = new (heap::allocate(sizeof_values_n(nv))) values_t{}; + auto dst = (T*) &nxt->d.buffer; + auto src = values(); + ownee(nxt) = e; + IMMER_TRY { + std::uninitialized_copy(src, src + nv, dst); + } + IMMER_CATCH (...) { + deallocate_values(nxt, nv); + IMMER_RETHROW; + } + if (refs(old).dec()) + delete_values(old, nv); + impl.d.data.inner.values = nxt; + return dst; } - if (refs(old).dec()) - delete_values(old, nv); - impl.d.data.inner.values = nxt; - return dst; } static node_t* copy_collision_insert(node_t* src, T v) @@ -467,6 +470,27 @@ struct node return dst; } + static node_t* owned(node_t* n, edit_t e) + { + ownee(n) = e; + return n; + } + + static node_t* owned_values(node_t* n, edit_t e) + { + ownee(n) = e; + ownee(n->impl.d.data.inner.values) = e; + return n; + } + + static node_t* owned_values_safe(node_t* n, edit_t e) + { + ownee(n) = e; + if (n->impl.d.data.inner.values) + ownee(n->impl.d.data.inner.values) = e; + return n; + } + static node_t* copy_inner_replace_value(node_t* src, count_t offset, T v) { IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner); @@ -699,6 +723,38 @@ struct node } } + static node_t* make_merged_e( + edit_t e, shift_t shift, T v1, hash_t hash1, T v2, hash_t hash2) + { + if (shift < max_shift) { + auto idx1 = hash1 & (mask << shift); + auto idx2 = hash2 & (mask << shift); + if (idx1 == idx2) { + auto merged = make_merged_e( + e, shift + B, std::move(v1), hash1, std::move(v2), hash2); + IMMER_TRY { + return owned( + make_inner_n( + 1, static_cast(idx1 >> shift), merged), + e); + } + IMMER_CATCH (...) { + delete_deep_shift(merged, shift + B); + IMMER_RETHROW; + } + } else { + auto r = make_inner_n(0, + static_cast(idx1 >> shift), + std::move(v1), + static_cast(idx2 >> shift), + std::move(v2)); + return owned_values(r, e); + } + } else { + return owned(make_collision(std::move(v1), std::move(v2)), e); + } + } + node_t* inc() { refs(this).inc(); From f5ebe48b13c098ba3c6e3f862b914d80e1c87d49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Wed, 1 Jun 2022 19:14:27 +0200 Subject: [PATCH 003/104] Add benchmarks for HAMT erasure --- benchmark/set/erase.hpp | 85 ++++++++++++++++++++++++++++ benchmark/set/erase.ipp | 44 ++++++++++++++ benchmark/set/string-box/erase.cpp | 11 ++++ benchmark/set/string-long/erase.cpp | 11 ++++ benchmark/set/string-short/erase.cpp | 11 ++++ benchmark/set/unsigned/erase.cpp | 11 ++++ immer/detail/hamts/champ.hpp | 9 +++ 7 files changed, 182 insertions(+) create mode 100644 benchmark/set/erase.hpp create mode 100644 benchmark/set/erase.ipp create mode 100644 benchmark/set/string-box/erase.cpp create mode 100644 benchmark/set/string-long/erase.cpp create mode 100644 benchmark/set/string-short/erase.cpp create mode 100644 benchmark/set/unsigned/erase.cpp diff --git a/benchmark/set/erase.hpp b/benchmark/set/erase.hpp new file mode 100644 index 00000000..a702fd25 --- /dev/null +++ b/benchmark/set/erase.hpp @@ -0,0 +1,85 @@ +// +// 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 +// + +#pragma once + +#include "benchmark/config.hpp" + +#include +#include // Phil Nash +#include +#include +#include +#include + +namespace { + +template +auto benchmark_erase_mut_std() +{ + return [](nonius::chronometer meter) { + auto n = meter.param(); + auto g = Generator{}(n); + auto v_ = [&] { + auto v = Set{}; + for (auto i = 0u; i < n; ++i) + v.insert(g[i]); + return v; + }(); + measure(meter, [&] { + auto v = v_; + for (auto i = 0u; i < n; ++i) + v.erase(g[i]); + return v; + }); + }; +} + +template +auto benchmark_erase() +{ + return [](nonius::chronometer meter) { + auto n = meter.param(); + auto g = Generator{}(n); + auto v_ = [&] { + auto v = Set{}.transient(); + for (auto i = 0u; i < n; ++i) + v.insert(g[i]); + return v.persistent(); + }(); + measure(meter, [&] { + auto v = v_; + for (auto i = 0u; i < n; ++i) + v = v.erase(g[i]); + return v; + }); + }; +} + +template +auto benchmark_erase_move() +{ + return [](nonius::chronometer meter) { + auto n = meter.param(); + auto g = Generator{}(n); + auto v_ = [&] { + auto v = Set{}.transient(); + for (auto i = 0u; i < n; ++i) + v.insert(g[i]); + return v.persistent(); + }(); + measure(meter, [&] { + auto v = v_; + for (auto i = 0u; i < n; ++i) + v = std::move(v).erase(g[i]); + return v; + }); + }; +} + +} // namespace diff --git a/benchmark/set/erase.ipp b/benchmark/set/erase.ipp new file mode 100644 index 00000000..54197744 --- /dev/null +++ b/benchmark/set/erase.ipp @@ -0,0 +1,44 @@ +// +// 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 "erase.hpp" + +#ifndef GENERATOR_T +#error "you must define a GENERATOR_T" +#endif + +using generator__ = GENERATOR_T; +using t__ = typename decltype(generator__{}(0))::value_type; + +// clang-format off +NONIUS_BENCHMARK("std::set", benchmark_erase_mut_std>()) +NONIUS_BENCHMARK("std::unordered_set", benchmark_erase_mut_std>()) +NONIUS_BENCHMARK("boost::flat_set", benchmark_erase_mut_std>()) +// Phil Nash's hash_trie seems to not include an erase operation... at least at +// the version that we have included in the nix-shell here... +// NONIUS_BENCHMARK("hamt::hash_trie", benchmark_erase_mut_hash_trie>()) + +NONIUS_BENCHMARK("immer::set/5B", benchmark_erase,std::equal_to,def_memory,5>>()) +NONIUS_BENCHMARK("immer::set/4B", benchmark_erase,std::equal_to,def_memory,4>>()) +#ifndef DISABLE_GC_BENCHMARKS +NONIUS_BENCHMARK("immer::set/GC", benchmark_erase,std::equal_to,gc_memory,5>>()) +#endif +NONIUS_BENCHMARK("immer::set/UN", benchmark_erase,std::equal_to,unsafe_memory,5>>()) + +NONIUS_BENCHMARK("immer::set/move/5B", benchmark_erase_move,std::equal_to,def_memory,5>>()) +NONIUS_BENCHMARK("immer::set/move/4B", benchmark_erase_move,std::equal_to,def_memory,4>>()) +NONIUS_BENCHMARK("immer::set/move/UN", benchmark_erase_move,std::equal_to,unsafe_memory,5>>()) + +NONIUS_BENCHMARK("immer::set/tran/5B", benchmark_erase_mut_std,std::equal_to,def_memory,5>>()) +NONIUS_BENCHMARK("immer::set/tran/4B", benchmark_erase_mut_std,std::equal_to,def_memory,4>>()) +#ifndef DISABLE_GC_BENCHMARKS +NONIUS_BENCHMARK("immer::set/tran/GC", benchmark_erase_mut_std,std::equal_to,gc_memory,5>>()) +#endif +NONIUS_BENCHMARK("immer::set/tran/UN", benchmark_erase_mut_std,std::equal_to,unsafe_memory,5>>()) + +// clang-format on diff --git a/benchmark/set/string-box/erase.cpp b/benchmark/set/string-box/erase.cpp new file mode 100644 index 00000000..88191ada --- /dev/null +++ b/benchmark/set/string-box/erase.cpp @@ -0,0 +1,11 @@ +// +// 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 "generator.ipp" + +#include "../erase.ipp" diff --git a/benchmark/set/string-long/erase.cpp b/benchmark/set/string-long/erase.cpp new file mode 100644 index 00000000..88191ada --- /dev/null +++ b/benchmark/set/string-long/erase.cpp @@ -0,0 +1,11 @@ +// +// 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 "generator.ipp" + +#include "../erase.ipp" diff --git a/benchmark/set/string-short/erase.cpp b/benchmark/set/string-short/erase.cpp new file mode 100644 index 00000000..88191ada --- /dev/null +++ b/benchmark/set/string-short/erase.cpp @@ -0,0 +1,11 @@ +// +// 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 "generator.ipp" + +#include "../erase.ipp" diff --git a/benchmark/set/unsigned/erase.cpp b/benchmark/set/unsigned/erase.cpp new file mode 100644 index 00000000..88191ada --- /dev/null +++ b/benchmark/set/unsigned/erase.cpp @@ -0,0 +1,11 @@ +// +// 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 "generator.ipp" + +#include "../erase.ipp" diff --git a/immer/detail/hamts/champ.hpp b/immer/detail/hamts/champ.hpp index 4c95b60d..292527f4 100644 --- a/immer/detail/hamts/champ.hpp +++ b/immer/detail/hamts/champ.hpp @@ -784,7 +784,16 @@ struct champ return node->collision_count() > 2 ? node_t::copy_collision_remove(node, cur) : sub_result{fst + (cur == fst)}; +#if !defined(_MSC_VER) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + // Apparently GCC is generating this warning sometimes when + // compiling the benchmarks. It makes however no sense at all. return {}; +#if !defined(_MSC_VER) +#pragma GCC diagnostic pop +#endif } else { auto idx = (hash & (mask << shift)) >> shift; auto bit = bitmap_t{1u} << idx; From 00f9e49882ac953e2b074cd17f004712afc2e28c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Thu, 2 Jun 2022 11:29:23 +0200 Subject: [PATCH 004/104] Add missing link table_transient in the documentation --- doc/transients.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/transients.rst b/doc/transients.rst index 4cf53cac..461bb4ac 100644 --- a/doc/transients.rst +++ b/doc/transients.rst @@ -49,3 +49,10 @@ map_transient .. doxygenclass:: immer::map_transient :members: :undoc-members: + +table_transient +--------------- + +.. doxygenclass:: immer::table_transient + :members: + :undoc-members: From efc91fe7242b080eac79ea93653e0dfbd1e20122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Thu, 2 Jun 2022 11:31:43 +0200 Subject: [PATCH 005/104] Make immer::table and table_transient use mutation when possible --- immer/table.hpp | 19 +++++++------------ immer/table_transient.hpp | 18 ++++-------------- 2 files changed, 11 insertions(+), 26 deletions(-) diff --git a/immer/table.hpp b/immer/table.hpp index 4c2a251e..dd45012c 100644 --- a/immer/table.hpp +++ b/immer/table.hpp @@ -454,8 +454,7 @@ class table table&& insert_move(std::true_type, value_type value) { - // xxx: implement mutable version - impl_ = impl_.add(std::move(value)); + impl_.add_mut({}, std::move(value)); return std::move(*this); } @@ -467,10 +466,8 @@ class table template table&& update_move(std::true_type, key_type k, Fn&& fn) { - // xxx: implement mutable version - impl_ = - impl_.template update( - std::move(k), std::forward(fn)); + impl_.template update_mut( + std::move(k), std::forward(fn)); return std::move(*this); } @@ -478,14 +475,13 @@ class table table update_move(std::false_type, key_type k, Fn&& fn) { return impl_ - .template update( - std::move(k), std::forward(fn)); + .template update_mut( + {}, std::move(k), std::forward(fn)); } table&& erase_move(std::true_type, const key_type& value) { - // xxx: implement mutable version - impl_ = impl_.sub(value); + impl_.sub_mut({}, value); return std::move(*this); } @@ -496,8 +492,7 @@ class table table(impl_t impl) : impl_(std::move(impl)) - { - } + {} impl_t impl_ = impl_t::empty(); }; diff --git a/immer/table_transient.hpp b/immer/table_transient.hpp index 440903ee..5e8c42e5 100644 --- a/immer/table_transient.hpp +++ b/immer/table_transient.hpp @@ -215,11 +215,7 @@ class table_transient : MemoryPolicy::transience_t::owner * It may allocate memory and its complexity is *effectively* @f$ * O(1) @f$. */ - void insert(value_type value) - { - // xxx: implement mutable version - impl_ = impl_.add(std::move(value)); - } + void insert(value_type value) { impl_.add_mut(*this, std::move(value)); } /*! * Returns `this->insert(fn((*this)[k]))`. In particular, `fn` maps @@ -229,22 +225,17 @@ class table_transient : MemoryPolicy::transience_t::owner template void update(key_type k, Fn&& fn) { - // xxx: implement mutable version impl_ = impl_.template update( - std::move(k), std::forward(fn)); + *this, std::move(k), std::forward(fn)); } /*! * Removes table entry by given key `k` if there is any. It may allocate * memory and its complexity is *effectively* @f$ O(1) @f$. */ - void erase(const K& k) - { - // xxx: implement mutable version - impl_ = impl_.sub(k); - } + void erase(const K& k) { impl_.sub_mut(*this, k); } /*! * Returns an @a immutable form of this container, an @@ -268,8 +259,7 @@ class table_transient : MemoryPolicy::transience_t::owner table_transient(impl_t impl) : impl_(std::move(impl)) - { - } + {} impl_t impl_ = impl_t::empty(); From 4736697d3b2e4f7e0e35f65fbe64c3c9bfbce7cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Thu, 2 Jun 2022 11:33:35 +0200 Subject: [PATCH 006/104] Update the documentation of HAMT-based transients Their implementation is already on the way, thanks to sponsoring from Meta Inc (former Facebook). --- immer/map_transient.hpp | 13 ++++--------- immer/set_transient.hpp | 13 ++++--------- immer/table_transient.hpp | 13 ++++--------- 3 files changed, 12 insertions(+), 27 deletions(-) diff --git a/immer/map_transient.hpp b/immer/map_transient.hpp index d1ac4760..f8b99a75 100644 --- a/immer/map_transient.hpp +++ b/immer/map_transient.hpp @@ -24,17 +24,12 @@ template Date: Thu, 2 Jun 2022 11:45:52 +0200 Subject: [PATCH 007/104] Improve sequential initialization of HAMTs With this implementation, it uses transients also when using a tracing garbage collector -- before it would be optimized only when using reference counting. --- immer/detail/hamts/champ.hpp | 22 ++++++++++++++++++++++ immer/map.hpp | 12 ++++-------- immer/set.hpp | 12 ++++-------- immer/table.hpp | 18 +++++++----------- 4 files changed, 37 insertions(+), 27 deletions(-) diff --git a/immer/detail/hamts/champ.hpp b/immer/detail/hamts/champ.hpp index 292527f4..5f635097 100644 --- a/immer/detail/hamts/champ.hpp +++ b/immer/detail/hamts/champ.hpp @@ -89,6 +89,28 @@ struct champ node_t::delete_deep(root, 0); } + template + static auto from_initializer_list(std::initializer_list values) + { + auto e = owner_t{}; + auto result = champ{empty()}; + for (auto&& v : values) + result.add_mut(e, v); + return result; + } + + template , bool> = true> + static auto from_range(Iter first, Sent last) + { + auto e = owner_t{}; + auto result = champ{empty()}; + for (; first != last; ++first) + result.add_mut(e, *first); + return result; + } + template void for_each_chunk(Fn&& fn) const { diff --git a/immer/map.hpp b/immer/map.hpp index 33315ad9..acbbc0b1 100644 --- a/immer/map.hpp +++ b/immer/map.hpp @@ -170,10 +170,8 @@ class map * Constructs a map containing the elements in `values`. */ map(std::initializer_list values) - { - for (auto&& v : values) - *this = std::move(*this).insert(v); - } + : impl_{impl_t::from_initializer_list(values)} + {} /*! * Constructs a map containing the elements in the range @@ -184,10 +182,8 @@ class map std::enable_if_t, bool> = true> map(Iter first, Sent last) - { - for (; first != last; ++first) - *this = std::move(*this).insert(*first); - } + : impl_{impl_t::from_range(first, last)} + {} /*! * Default constructor. It creates a map of `size() == 0`. It diff --git a/immer/set.hpp b/immer/set.hpp index 247ef83f..a8199b22 100644 --- a/immer/set.hpp +++ b/immer/set.hpp @@ -95,10 +95,8 @@ class set * Constructs a set containing the elements in `values`. */ set(std::initializer_list values) - { - for (auto&& v : values) - *this = std::move(*this).insert(v); - } + : impl_{impl_t::from_initializer_list(values)} + {} /*! * Constructs a set containing the elements in the range @@ -109,10 +107,8 @@ class set std::enable_if_t, bool> = true> set(Iter first, Sent last) - { - for (; first != last; ++first) - *this = std::move(*this).insert(*first); - } + : impl_{impl_t::from_range(first, last)} + {} /*! * Returns an iterator pointing at the first element of the diff --git a/immer/table.hpp b/immer/table.hpp index dd45012c..c4118054 100644 --- a/immer/table.hpp +++ b/immer/table.hpp @@ -186,10 +186,8 @@ class table * Constructs a table containing the elements in `values`. */ table(std::initializer_list values) - { - for (auto&& v : values) - *this = std::move(*this).insert(v); - } + : impl_{impl_t::from_initializer_list(values)} + {} /*! * Constructs a table containing the elements in the range @@ -200,10 +198,8 @@ class table std::enable_if_t, bool> = true> table(Iter first, Sent last) - { - for (; first != last; ++first) - *this = std::move(*this).insert(*first); - } + : impl_{impl_t::from_range(first, last)} + {} /*! * Default constructor. It creates a table of `size() == 0`. It @@ -467,7 +463,7 @@ class table table&& update_move(std::true_type, key_type k, Fn&& fn) { impl_.template update_mut( - std::move(k), std::forward(fn)); + {}, std::move(k), std::forward(fn)); return std::move(*this); } @@ -475,8 +471,8 @@ class table table update_move(std::false_type, key_type k, Fn&& fn) { return impl_ - .template update_mut( - {}, std::move(k), std::forward(fn)); + .template update( + std::move(k), std::forward(fn)); } table&& erase_move(std::true_type, const key_type& value) From 5e9fe0b3476d1036824fc8934433ba3ddc85a90f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Tue, 7 Jun 2022 12:25:32 +0200 Subject: [PATCH 008/104] Fix table documentation header --- doc/containers.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/containers.rst b/doc/containers.rst index 70c026bf..5bec7889 100644 --- a/doc/containers.rst +++ b/doc/containers.rst @@ -50,7 +50,7 @@ map :undoc-members: table ---- +----- .. doxygenclass:: immer::table :members: From a89f7c98c64c9f45e962727eb9dca3e7e5dd3acd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Wed, 8 Jun 2022 17:51:42 +0200 Subject: [PATCH 009/104] Move values when node can be mutated but needs realloc (champ::add_mut) This severely improves performance during insertions when using reference counting! --- immer/detail/hamts/champ.hpp | 102 +++++++++++++++----------- immer/detail/hamts/node.hpp | 134 +++++++++++++++++++++++++++++++++++ 2 files changed, 195 insertions(+), 41 deletions(-) diff --git a/immer/detail/hamts/champ.hpp b/immer/detail/hamts/champ.hpp index 5f635097..231efd18 100644 --- a/immer/detail/hamts/champ.hpp +++ b/immer/detail/hamts/champ.hpp @@ -364,8 +364,13 @@ struct champ return Default{}(); } - std::pair - do_add(node_t* node, T v, hash_t hash, shift_t shift) const + struct add_result + { + node_t* node; + bool added; + }; + + add_result do_add(node_t* node, T v, hash_t hash, shift_t shift) const { assert(node); if (shift == max_shift) { @@ -386,12 +391,12 @@ struct champ auto result = do_add( node->children()[offset], std::move(v), hash, shift + B); IMMER_TRY { - result.first = - node_t::copy_inner_replace(node, offset, result.first); + result.node = + node_t::copy_inner_replace(node, offset, result.node); return result; } IMMER_CATCH (...) { - node_t::delete_deep_shift(result.first, shift + B); + node_t::delete_deep_shift(result.node, shift + B); IMMER_RETHROW; } } else if (node->datamap() & bit) { @@ -426,11 +431,18 @@ struct champ { auto hash = Hash{}(v); auto res = do_add(root, std::move(v), hash, 0); - auto new_size = size + (res.second ? 1 : 0); - return {res.first, new_size}; + auto new_size = size + (res.added ? 1 : 0); + return {res.node, new_size}; } - std::pair + struct add_mut_result + { + node_t* node; + bool added; + bool mutated; + }; + + add_mut_result do_add_mut(edit_t e, node_t* node, T v, hash_t hash, shift_t shift) const { assert(node); @@ -441,15 +453,17 @@ struct champ if (Equal{}(*fst, v)) { if (node->can_mutate(e)) { *fst = std::move(v); - return {node, false}; + return {node, false, true}; } else { auto r = node_t::copy_collision_replace( node, fst, std::move(v)); - return {node_t::owned(r, e), false}; + return {node_t::owned(r, e), false, false}; } } - auto r = node_t::copy_collision_insert(node, std::move(v)); - return {node_t::owned(r, e), true}; + auto mutate = node->can_mutate(e); + auto r = mutate ? node_t::move_collision_insert(node, std::move(v)) + : node_t::copy_collision_insert(node, std::move(v)); + return {node_t::owned(r, e), true, mutate}; } else { auto idx = (hash & (mask << shift)) >> shift; auto bit = bitmap_t{1u} << idx; @@ -459,24 +473,21 @@ struct champ if (node->can_mutate(e)) { auto result = do_add_mut(e, child, std::move(v), hash, shift + B); - auto p = node->children()[offset]; - if (p != result.first) { - node->children()[offset] = result.first; - if (p->dec()) - node_t::delete_deep_shift(p, shift + B); - } - return {node, result.second}; + node->children()[offset] = result.node; + if (!result.mutated) + child->dec_unsafe(); + return {node, result.added, true}; } else { assert(node->children()[offset]); auto result = do_add(child, std::move(v), hash, shift + B); IMMER_TRY { - result.first = node_t::copy_inner_replace( - node, offset, result.first); - node_t::owned(result.first, e); - return result; + result.node = node_t::copy_inner_replace( + node, offset, result.node); + node_t::owned(result.node, e); + return {result.node, result.added, false}; } IMMER_CATCH (...) { - node_t::delete_deep_shift(result.first, shift + B); + node_t::delete_deep_shift(result.node, shift + B); IMMER_RETHROW; } } @@ -487,19 +498,28 @@ struct champ if (node->can_mutate(e)) { auto vals = node->ensure_mutable_values(e); vals[offset] = std::move(v); - return {node, false}; + return {node, false, true}; } else { auto r = node_t::copy_inner_replace_value( node, offset, std::move(v)); - return {node_t::owned_values(r, e), false}; + return {node_t::owned_values(r, e), false, false}; } } else { - auto child = node_t::make_merged_e( - e, shift + B, std::move(v), hash, *val, Hash{}(*val)); + auto mutate = node->can_mutate(e); + auto hash2 = Hash{}(*val); + auto child = + node_t::make_merged_e(e, + shift + B, + std::move(v), + hash, + mutate ? std::move(*val) : *val, + hash2); IMMER_TRY { - auto r = node_t::copy_inner_replace_merged( - node, bit, offset, child); - return {node_t::owned_values_safe(r, e), true}; + auto r = mutate ? node_t::move_inner_replace_merged( + e, node, bit, offset, child) + : node_t::copy_inner_replace_merged( + node, bit, offset, child); + return {node_t::owned_values_safe(r, e), true, mutate}; } IMMER_CATCH (...) { node_t::delete_deep_shift(child, shift + B); @@ -507,9 +527,12 @@ struct champ } } } else { - auto r = - node_t::copy_inner_insert_value(node, bit, std::move(v)); - return {node_t::owned_values(r, e), true}; + auto mutate = node->can_mutate(e); + auto r = mutate ? node_t::move_inner_insert_value( + e, node, bit, std::move(v)) + : node_t::copy_inner_insert_value( + node, bit, std::move(v)); + return {node_t::owned_values(r, e), true, mutate}; } } } @@ -518,13 +541,10 @@ struct champ { auto hash = Hash{}(v); auto res = do_add_mut(e, root, std::move(v), hash, 0); - if (res.first != root) { - auto p = root; - root = res.first; - if (p->dec()) - node_t::delete_deep(p, 0); - } - size += res.second ? 1 : 0; + if (!res.mutated) + root->dec_unsafe(); + root = res.node; + size += res.added ? 1 : 0; } template kind() == kind_t::collision); + auto n = src->collision_count(); + auto dst = make_collision_n(n + 1); + auto srcp = src->collisions(); + auto dstp = dst->collisions(); + IMMER_TRY { + new (dstp) T{std::move(v)}; + IMMER_TRY { + detail::uninitialized_move(srcp, srcp + n, dstp + 1); + } + IMMER_CATCH (...) { + dstp->~T(); + IMMER_RETHROW; + } + } + IMMER_CATCH (...) { + deallocate_collision(dst, n + 1); + IMMER_RETHROW; + } + delete_collision(src); + return dst; + } + static node_t* copy_collision_remove(node_t* src, T* v) { IMMER_ASSERT_TAGGED(src->kind() == kind_t::collision); @@ -565,6 +590,59 @@ struct node return dst; } + static node_t* move_inner_replace_merged( + edit_t e, node_t* src, bitmap_t bit, count_t voffset, node_t* node) + { + IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner); + assert(!(src->nodemap() & bit)); + assert(src->datamap() & bit); + assert(voffset == src->data_count(bit)); + auto n = src->children_count(); + auto nv = src->data_count(); + auto dst = make_inner_n(n + 1, nv - 1); + auto noffset = src->children_count(bit); + dst->impl.d.data.inner.datamap = src->datamap() & ~bit; + dst->impl.d.data.inner.nodemap = src->nodemap() | bit; + if (nv > 1) { + auto mutate_values = can_mutate(dst->impl.d.data.inner.values, e); + IMMER_TRY { + if (mutate_values) + detail::uninitialized_move( + src->values(), src->values() + voffset, dst->values()); + else + std::uninitialized_copy( + src->values(), src->values() + voffset, dst->values()); + IMMER_TRY { + if (mutate_values) + detail::uninitialized_move(src->values() + voffset + 1, + src->values() + nv, + dst->values() + voffset); + else + std::uninitialized_copy(src->values() + voffset + 1, + src->values() + nv, + dst->values() + voffset); + } + IMMER_CATCH (...) { + destroy_n(dst->values(), voffset); + IMMER_RETHROW; + } + } + IMMER_CATCH (...) { + deallocate_inner(dst, n + 1, nv - 1); + IMMER_RETHROW; + } + } + // inc_nodes(src->children(), n); + std::uninitialized_copy( + src->children(), src->children() + noffset, dst->children()); + std::uninitialized_copy(src->children() + noffset, + src->children() + n, + dst->children() + noffset + 1); + dst->children()[noffset] = node; + delete_inner(src); + return dst; + } + static node_t* copy_inner_replace_inline(node_t* src, bitmap_t bit, count_t noffset, @@ -694,6 +772,62 @@ struct node return dst; } + static node_t* + move_inner_insert_value(edit_t e, node_t* src, bitmap_t bit, T v) + { + IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner); + auto n = src->children_count(); + auto nv = src->data_count(); + auto offset = src->data_count(bit); + auto dst = make_inner_n(n, nv + 1); + dst->impl.d.data.inner.datamap = src->datamap() | bit; + dst->impl.d.data.inner.nodemap = src->nodemap(); + IMMER_TRY { + auto mutate_values = + nv && can_mutate(dst->impl.d.data.inner.values, e); + if (nv) { + if (mutate_values) + detail::uninitialized_move( + src->values(), src->values() + offset, dst->values()); + else + std::uninitialized_copy( + src->values(), src->values() + offset, dst->values()); + } + IMMER_TRY { + new (dst->values() + offset) T{std::move(v)}; + IMMER_TRY { + if (nv) { + if (mutate_values) + detail::uninitialized_move(src->values() + offset, + src->values() + nv, + dst->values() + offset + + 1); + else + std::uninitialized_copy(src->values() + offset, + src->values() + nv, + dst->values() + offset + 1); + } + } + IMMER_CATCH (...) { + dst->values()[offset].~T(); + IMMER_RETHROW; + } + } + IMMER_CATCH (...) { + destroy_n(dst->values(), offset); + IMMER_RETHROW; + } + } + IMMER_CATCH (...) { + deallocate_inner(dst, n, nv + 1); + IMMER_RETHROW; + } + std::uninitialized_copy( + src->children(), src->children() + n, dst->children()); + delete_inner(src); + return dst; + } + static node_t* make_merged(shift_t shift, T v1, hash_t hash1, T v2, hash_t hash2) { From 98d4f983cb6a7cd8706608698cb0d854d55241da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Wed, 8 Jun 2022 19:05:51 +0200 Subject: [PATCH 010/104] Move values when node can be mutated but needs realloc (champ::update_mut) --- immer/detail/hamts/champ.hpp | 109 ++++++++++++++++++----------------- 1 file changed, 57 insertions(+), 52 deletions(-) diff --git a/immer/detail/hamts/champ.hpp b/immer/detail/hamts/champ.hpp index 231efd18..edd6e566 100644 --- a/immer/detail/hamts/champ.hpp +++ b/immer/detail/hamts/champ.hpp @@ -547,12 +547,14 @@ struct champ size += res.added ? 1 : 0; } + using update_result = add_result; + template - std::pair + update_result do_update(node_t* node, K&& k, Fn&& fn, hash_t hash, shift_t shift) const { if (shift == max_shift) { @@ -584,12 +586,12 @@ struct champ hash, shift + B); IMMER_TRY { - result.first = - node_t::copy_inner_replace(node, offset, result.first); + result.node = + node_t::copy_inner_replace(node, offset, result.node); return result; } IMMER_CATCH (...) { - node_t::delete_deep_shift(result.first, shift + B); + node_t::delete_deep_shift(result.node, shift + B); IMMER_RETHROW; } } else if (node->datamap() & bit) { @@ -642,21 +644,23 @@ struct champ auto hash = Hash{}(k); auto res = do_update( root, k, std::forward(fn), hash, 0); - auto new_size = size + (res.second ? 1 : 0); - return {res.first, new_size}; + auto new_size = size + (res.added ? 1 : 0); + return {res.node, new_size}; } + using update_mut_result = add_mut_result; + template - std::pair do_update_mut(edit_t e, - node_t* node, - K&& k, - Fn&& fn, - hash_t hash, - shift_t shift) const + update_mut_result do_update_mut(edit_t e, + node_t* node, + K&& k, + Fn&& fn, + hash_t hash, + shift_t shift) const { if (shift == max_shift) { auto fst = node->collisions(); @@ -667,21 +671,22 @@ struct champ *fst = Combine{}( std::forward(k), std::forward(fn)(Project{}(std::move(*fst)))); - return {node, false}; + return {node, false, true}; } else { auto r = node_t::copy_collision_replace( node, fst, Combine{}(std::forward(k), std::forward(fn)(Project{}(*fst)))); - return {node_t::owned(r, e), false}; + return {node_t::owned(r, e), false, false}; } } - auto r = node_t::copy_collision_insert( - node, - Combine{}(std::forward(k), - std::forward(fn)(Default{}()))); - return {node_t::owned(r, e), true}; + auto v = Combine{}(std::forward(k), + std::forward(fn)(Default{}())); + auto mutate = node->can_mutate(e); + auto r = mutate ? node_t::move_collision_insert(node, std::move(v)) + : node_t::copy_collision_insert(node, std::move(v)); + return {node_t::owned(r, e), true, mutate}; } else { auto idx = (hash & (mask << shift)) >> shift; auto bit = bitmap_t{1u} << idx; @@ -691,24 +696,21 @@ struct champ if (node->can_mutate(e)) { auto result = do_update_mut( e, child, k, std::forward(fn), hash, shift + B); - auto p = node->children()[offset]; - if (p != result.first) { - node->children()[offset] = result.first; - if (p->dec()) - node_t::delete_deep_shift(p, shift + B); - } - return {node, result.second}; + node->children()[offset] = result.node; + if (!result.mutated) + child->dec_unsafe(); + return {node, result.added, true}; } else { auto result = do_update( child, k, std::forward(fn), hash, shift + B); IMMER_TRY { - result.first = node_t::copy_inner_replace( - node, offset, result.first); - node_t::owned(result.first, e); - return result; + result.node = node_t::copy_inner_replace( + node, offset, result.node); + node_t::owned(result.node, e); + return {result.node, result.added, false}; } IMMER_CATCH (...) { - node_t::delete_deep_shift(result.first, shift + B); + node_t::delete_deep_shift(result.node, shift + B); IMMER_RETHROW; } } @@ -721,28 +723,32 @@ struct champ vals[offset] = Combine{}(std::forward(k), std::forward(fn)(Project{}( std::move(vals[offset])))); - return {node, false}; + return {node, false, true}; } else { auto r = node_t::copy_inner_replace_value( node, offset, Combine{}(std::forward(k), std::forward(fn)(Project{}(*val)))); - return {node_t::owned_values(r, e), false}; + return {node_t::owned_values(r, e), false, false}; } } else { - auto child = node_t::make_merged_e( + auto mutate = node->can_mutate(e); + auto hash2 = Hash{}(*val); + auto child = node_t::make_merged_e( e, shift + B, Combine{}(std::forward(k), std::forward(fn)(Default{}())), hash, - *val, - Hash{}(*val)); + mutate ? std::move(*val) : *val, + hash2); IMMER_TRY { - auto r = node_t::copy_inner_replace_merged( - node, bit, offset, child); - return {node_t::owned_values_safe(r, e), true}; + auto r = mutate ? node_t::move_inner_replace_merged( + e, node, bit, offset, child) + : node_t::copy_inner_replace_merged( + node, bit, offset, child); + return {node_t::owned_values_safe(r, e), true, mutate}; } IMMER_CATCH (...) { node_t::delete_deep_shift(child, shift + B); @@ -750,12 +756,14 @@ struct champ } } } else { - auto r = node_t::copy_inner_insert_value( - node, - bit, - Combine{}(std::forward(k), - std::forward(fn)(Default{}()))); - return {node_t::owned_values(r, e), true}; + auto mutate = node->can_mutate(e); + auto v = Combine{}(std::forward(k), + std::forward(fn)(Default{}())); + auto r = mutate ? node_t::move_inner_insert_value( + e, node, bit, std::move(v)) + : node_t::copy_inner_insert_value( + node, bit, std::move(v)); + return {node_t::owned_values(r, e), true, mutate}; } } } @@ -770,13 +778,10 @@ struct champ auto hash = Hash{}(k); auto res = do_update_mut( e, root, k, std::forward(fn), hash, 0); - if (res.first != root) { - auto p = root; - root = res.first; - if (p->dec()) - node_t::delete_deep(p, 0); - } - size += res.second ? 1 : 0; + if (!res.mutated) + root->dec_unsafe(); + root = res.node; + size += res.added ? 1 : 0; } // basically: From 3514768617d50b7747b8e346d934360e55d23e6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Fri, 10 Jun 2022 13:23:54 +0200 Subject: [PATCH 011/104] Use standard library implementations of shims whenever possible --- immer/config.hpp | 4 ++++ immer/detail/util.hpp | 47 ++++++++++++++++++++++++++++++++----------- 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/immer/config.hpp b/immer/config.hpp index c1024fb9..91e7978e 100644 --- a/immer/config.hpp +++ b/immer/config.hpp @@ -8,6 +8,10 @@ #pragma once +#if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +#define IMMER_HAS_CPP17 1 +#endif + #if defined(__has_cpp_attribute) #if __has_cpp_attribute(nodiscard) #define IMMER_NODISCARD [[nodiscard]] diff --git a/immer/detail/util.hpp b/immer/detail/util.hpp index b9e50414..cc2ee12b 100644 --- a/immer/detail/util.hpp +++ b/immer/detail/util.hpp @@ -39,27 +39,50 @@ T&& auto_const_cast(const T&& x) return const_cast(std::move(x)); } -template -auto uninitialized_move(Iter1 in1, Iter1 in2, Iter2 out) +#if IMMER_HAS_CPP17 +using std::destroy; +using std::destroy_n; +using std::uninitialized_move; +#else +template +inline void destroy_at(T* p) { - return std::uninitialized_copy( - std::make_move_iterator(in1), std::make_move_iterator(in2), out); + p->~T(); } -template -void destroy(T* first, T* last) +template +Iter destroy(Iter first, Iter last) { for (; first != last; ++first) - first->~T(); + destroy_at(std::addressof(*first)); + return first; +} + +template +Iter destroy_n(Iter first, Size n) +{ + for (; n > 0; (void) ++first, --n) + destroy_at(std::addressof(*first)); + return first; } -template -void destroy_n(T* p, Size n) +template +Iter2 uninitialized_move(Iter1 first, Iter1 last, Iter2 out) { - auto e = p + n; - for (; p != e; ++p) - p->~T(); + using value_t = typename std::iterator_traits::value_type; + auto current = out; + try { + for (; first != last; ++first, (void) ++current) { + ::new (const_cast(static_cast( + std::addressof(*current)))) value_t{std::move(*first)}; + } + return current; + } catch (...) { + destroy(out, current); + throw; + } } +#endif // IMMER_HAS_CPP17 template T* make(Args&&... args) From 54ff4a21a7951be663d49640a8b85f5887f9c08d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Fri, 10 Jun 2022 13:44:42 +0200 Subject: [PATCH 012/104] Improve implementation of std shims --- immer/detail/util.hpp | 49 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/immer/detail/util.hpp b/immer/detail/util.hpp index cc2ee12b..e82622f0 100644 --- a/immer/detail/util.hpp +++ b/immer/detail/util.hpp @@ -45,13 +45,32 @@ using std::destroy_n; using std::uninitialized_move; #else template -inline void destroy_at(T* p) +inline auto destroy_at(T* p) + -> std::enable_if_t::value> { p->~T(); } +template +inline auto destroy_at(T* p) + -> std::enable_if_t::value> +{ + p->~T(); +} + +template +constexpr bool can_trivially_detroy = std::is_trivially_destructible< + typename std::iterator_traits::value_type>::value; + +template +auto destroy(Iter, Iter last) + -> std::enable_if_t, Iter> +{ + return last; +} template -Iter destroy(Iter first, Iter last) +auto destroy(Iter first, Iter last) + -> std::enable_if_t, Iter> { for (; first != last; ++first) destroy_at(std::addressof(*first)); @@ -59,7 +78,14 @@ Iter destroy(Iter first, Iter last) } template -Iter destroy_n(Iter first, Size n) +auto destroy_n(Iter first, Size n) + -> std::enable_if_t, Iter> +{ + return first + n; +} +template +auto destroy_n(Iter first, Size n) + -> std::enable_if_t, Iter> { for (; n > 0; (void) ++first, --n) destroy_at(std::addressof(*first)); @@ -67,7 +93,22 @@ Iter destroy_n(Iter first, Size n) } template -Iter2 uninitialized_move(Iter1 first, Iter1 last, Iter2 out) +constexpr bool can_trivially_copy = + std::is_same::value_type, + typename std::iterator_traits::value_type>::value&& + std::is_trivially_copyable< + typename std::iterator_traits::value_type>::value; + +template +auto uninitialized_move(Iter1 first, Iter1 last, Iter2 out) + -> std::enable_if_t, Iter2> +{ + return std::copy(first, last, out); +} +template +auto uninitialized_move(Iter1 first, Iter1 last, Iter2 out) + -> std::enable_if_t, Iter2> + { using value_t = typename std::iterator_traits::value_type; auto current = out; From 265d79a5bafc731d76f609f07d039d57bc3f2f09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Fri, 10 Jun 2022 13:45:22 +0200 Subject: [PATCH 013/104] Our own implementations for uninitialied_... are better, use always I've checked both the implementations of std::uninitialied_copy and std::uninitialied_move in libc++ and stdlib++, ours are potentially better... --- immer/detail/util.hpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/immer/detail/util.hpp b/immer/detail/util.hpp index e82622f0..0a3a865e 100644 --- a/immer/detail/util.hpp +++ b/immer/detail/util.hpp @@ -39,11 +39,6 @@ T&& auto_const_cast(const T&& x) return const_cast(std::move(x)); } -#if IMMER_HAS_CPP17 -using std::destroy; -using std::destroy_n; -using std::uninitialized_move; -#else template inline auto destroy_at(T* p) -> std::enable_if_t::value> @@ -123,7 +118,6 @@ auto uninitialized_move(Iter1 first, Iter1 last, Iter2 out) throw; } } -#endif // IMMER_HAS_CPP17 template T* make(Args&&... args) From 778ad6718f1a6acb776ead205192253380da106c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Fri, 10 Jun 2022 14:15:14 +0200 Subject: [PATCH 014/104] Use std::copy when possible, our own uninitialized_copy elsewhere --- immer/detail/hamts/node.hpp | 122 +++++++++++++++---------------- immer/detail/rbts/node.hpp | 24 +++--- immer/detail/rbts/operations.hpp | 56 +++++++------- immer/detail/rbts/rrbtree.hpp | 36 ++++----- 4 files changed, 113 insertions(+), 125 deletions(-) diff --git a/immer/detail/hamts/node.hpp b/immer/detail/hamts/node.hpp index 7fa9a582..00da82fe 100644 --- a/immer/detail/hamts/node.hpp +++ b/immer/detail/hamts/node.hpp @@ -359,7 +359,7 @@ struct node auto src = values(); ownee(nxt) = e; IMMER_TRY { - std::uninitialized_copy(src, src + nv, dst); + uninitialized_copy(src, src + nv, dst); } IMMER_CATCH (...) { deallocate_values(nxt, nv); @@ -382,7 +382,7 @@ struct node IMMER_TRY { new (dstp) T{std::move(v)}; IMMER_TRY { - std::uninitialized_copy(srcp, srcp + n, dstp + 1); + uninitialized_copy(srcp, srcp + n, dstp + 1); } IMMER_CATCH (...) { dstp->~T(); @@ -406,7 +406,7 @@ struct node IMMER_TRY { new (dstp) T{std::move(v)}; IMMER_TRY { - detail::uninitialized_move(srcp, srcp + n, dstp + 1); + uninitialized_move(srcp, srcp + n, dstp + 1); } IMMER_CATCH (...) { dstp->~T(); @@ -430,9 +430,9 @@ struct node auto srcp = src->collisions(); auto dstp = dst->collisions(); IMMER_TRY { - dstp = std::uninitialized_copy(srcp, v, dstp); + dstp = uninitialized_copy(srcp, v, dstp); IMMER_TRY { - std::uninitialized_copy(v + 1, srcp + n, dstp); + uninitialized_copy(v + 1, srcp + n, dstp); } IMMER_CATCH (...) { destroy(dst->collisions(), dstp); @@ -457,9 +457,9 @@ struct node IMMER_TRY { new (dstp) T{std::move(v)}; IMMER_TRY { - dstp = std::uninitialized_copy(srcp, pos, dstp + 1); + dstp = uninitialized_copy(srcp, pos, dstp + 1); IMMER_TRY { - std::uninitialized_copy(pos + 1, srcp + n, dstp); + uninitialized_copy(pos + 1, srcp + n, dstp); } IMMER_CATCH (...) { destroy(dst->collisions(), dstp); @@ -488,7 +488,7 @@ struct node auto dstp = dst->children(); dst->impl.d.data.inner.datamap = src->datamap(); dst->impl.d.data.inner.nodemap = src->nodemap(); - std::uninitialized_copy(srcp, srcp + n, dstp); + std::copy(srcp, srcp + n, dstp); inc_nodes(srcp, n); srcp[offset]->dec_unsafe(); dstp[offset] = child; @@ -526,7 +526,7 @@ struct node dst->impl.d.data.inner.datamap = src->datamap(); dst->impl.d.data.inner.nodemap = src->nodemap(); IMMER_TRY { - std::uninitialized_copy( + uninitialized_copy( src->values(), src->values() + nv, dst->values()); IMMER_TRY { dst->values()[offset] = std::move(v); @@ -541,8 +541,7 @@ struct node IMMER_RETHROW; } inc_nodes(src->children(), n); - std::uninitialized_copy( - src->children(), src->children() + n, dst->children()); + std::copy(src->children(), src->children() + n, dst->children()); return dst; } @@ -563,12 +562,12 @@ struct node dst->impl.d.data.inner.nodemap = src->nodemap() | bit; if (nv > 1) { IMMER_TRY { - std::uninitialized_copy( + uninitialized_copy( src->values(), src->values() + voffset, dst->values()); IMMER_TRY { - std::uninitialized_copy(src->values() + voffset + 1, - src->values() + nv, - dst->values() + voffset); + uninitialized_copy(src->values() + voffset + 1, + src->values() + nv, + dst->values() + voffset); } IMMER_CATCH (...) { destroy_n(dst->values(), voffset); @@ -581,11 +580,10 @@ struct node } } inc_nodes(src->children(), n); - std::uninitialized_copy( - src->children(), src->children() + noffset, dst->children()); - std::uninitialized_copy(src->children() + noffset, - src->children() + n, - dst->children() + noffset + 1); + std::copy(src->children(), src->children() + noffset, dst->children()); + std::copy(src->children() + noffset, + src->children() + n, + dst->children() + noffset + 1); dst->children()[noffset] = node; return dst; } @@ -607,20 +605,20 @@ struct node auto mutate_values = can_mutate(dst->impl.d.data.inner.values, e); IMMER_TRY { if (mutate_values) - detail::uninitialized_move( + uninitialized_move( src->values(), src->values() + voffset, dst->values()); else - std::uninitialized_copy( + uninitialized_copy( src->values(), src->values() + voffset, dst->values()); IMMER_TRY { if (mutate_values) - detail::uninitialized_move(src->values() + voffset + 1, - src->values() + nv, - dst->values() + voffset); + uninitialized_move(src->values() + voffset + 1, + src->values() + nv, + dst->values() + voffset); else - std::uninitialized_copy(src->values() + voffset + 1, - src->values() + nv, - dst->values() + voffset); + uninitialized_copy(src->values() + voffset + 1, + src->values() + nv, + dst->values() + voffset); } IMMER_CATCH (...) { destroy_n(dst->values(), voffset); @@ -633,11 +631,10 @@ struct node } } // inc_nodes(src->children(), n); - std::uninitialized_copy( - src->children(), src->children() + noffset, dst->children()); - std::uninitialized_copy(src->children() + noffset, - src->children() + n, - dst->children() + noffset + 1); + std::copy(src->children(), src->children() + noffset, dst->children()); + std::copy(src->children() + noffset, + src->children() + n, + dst->children() + noffset + 1); dst->children()[noffset] = node; delete_inner(src); return dst; @@ -660,15 +657,15 @@ struct node dst->impl.d.data.inner.datamap = src->datamap() | bit; IMMER_TRY { if (nv) - std::uninitialized_copy( + uninitialized_copy( src->values(), src->values() + voffset, dst->values()); IMMER_TRY { new (dst->values() + voffset) T{std::move(value)}; IMMER_TRY { if (nv) - std::uninitialized_copy(src->values() + voffset, - src->values() + nv, - dst->values() + voffset + 1); + uninitialized_copy(src->values() + voffset, + src->values() + nv, + dst->values() + voffset + 1); } IMMER_CATCH (...) { dst->values()[voffset].~T(); @@ -686,11 +683,10 @@ struct node } inc_nodes(src->children(), n); src->children()[noffset]->dec_unsafe(); - std::uninitialized_copy( - src->children(), src->children() + noffset, dst->children()); - std::uninitialized_copy(src->children() + noffset + 1, - src->children() + n, - dst->children() + noffset); + std::copy(src->children(), src->children() + noffset, dst->children()); + std::copy(src->children() + noffset + 1, + src->children() + n, + dst->children() + noffset); return dst; } @@ -708,12 +704,12 @@ struct node dst->impl.d.data.inner.nodemap = src->nodemap(); if (nv > 1) { IMMER_TRY { - std::uninitialized_copy( + uninitialized_copy( src->values(), src->values() + voffset, dst->values()); IMMER_TRY { - std::uninitialized_copy(src->values() + voffset + 1, - src->values() + nv, - dst->values() + voffset); + uninitialized_copy(src->values() + voffset + 1, + src->values() + nv, + dst->values() + voffset); } IMMER_CATCH (...) { destroy_n(dst->values(), voffset); @@ -726,8 +722,7 @@ struct node } } inc_nodes(src->children(), n); - std::uninitialized_copy( - src->children(), src->children() + n, dst->children()); + std::copy(src->children(), src->children() + n, dst->children()); return dst; } @@ -742,15 +737,15 @@ struct node dst->impl.d.data.inner.nodemap = src->nodemap(); IMMER_TRY { if (nv) - std::uninitialized_copy( + uninitialized_copy( src->values(), src->values() + offset, dst->values()); IMMER_TRY { new (dst->values() + offset) T{std::move(v)}; IMMER_TRY { if (nv) - std::uninitialized_copy(src->values() + offset, - src->values() + nv, - dst->values() + offset + 1); + uninitialized_copy(src->values() + offset, + src->values() + nv, + dst->values() + offset + 1); } IMMER_CATCH (...) { dst->values()[offset].~T(); @@ -767,8 +762,7 @@ struct node IMMER_RETHROW; } inc_nodes(src->children(), n); - std::uninitialized_copy( - src->children(), src->children() + n, dst->children()); + std::copy(src->children(), src->children() + n, dst->children()); return dst; } @@ -787,10 +781,10 @@ struct node nv && can_mutate(dst->impl.d.data.inner.values, e); if (nv) { if (mutate_values) - detail::uninitialized_move( + uninitialized_move( src->values(), src->values() + offset, dst->values()); else - std::uninitialized_copy( + uninitialized_copy( src->values(), src->values() + offset, dst->values()); } IMMER_TRY { @@ -798,14 +792,13 @@ struct node IMMER_TRY { if (nv) { if (mutate_values) - detail::uninitialized_move(src->values() + offset, - src->values() + nv, - dst->values() + offset + - 1); + uninitialized_move(src->values() + offset, + src->values() + nv, + dst->values() + offset + 1); else - std::uninitialized_copy(src->values() + offset, - src->values() + nv, - dst->values() + offset + 1); + uninitialized_copy(src->values() + offset, + src->values() + nv, + dst->values() + offset + 1); } } IMMER_CATCH (...) { @@ -822,8 +815,7 @@ struct node deallocate_inner(dst, n, nv + 1); IMMER_RETHROW; } - std::uninitialized_copy( - src->children(), src->children() + n, dst->children()); + std::copy(src->children(), src->children() + n, dst->children()); delete_inner(src); return dst; } diff --git a/immer/detail/rbts/node.hpp b/immer/detail/rbts/node.hpp index 67c9a295..65cd97a8 100644 --- a/immer/detail/rbts/node.hpp +++ b/immer/detail/rbts/node.hpp @@ -511,7 +511,7 @@ struct node IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner); auto dst = make_inner_n(n); inc_nodes(src->inner(), n); - std::uninitialized_copy(src->inner(), src->inner() + n, dst->inner()); + std::copy(src->inner(), src->inner() + n, dst->inner()); return dst; } @@ -536,7 +536,7 @@ struct node IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner); auto p = src->inner(); inc_nodes(p, n); - std::uninitialized_copy(p, p + n, dst->inner()); + std::copy(p, p + n, dst->inner()); return dst; } @@ -598,7 +598,7 @@ struct node IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf); auto dst = make_leaf_n(n); IMMER_TRY { - std::uninitialized_copy(src->leaf(), src->leaf() + n, dst->leaf()); + uninitialized_copy(src->leaf(), src->leaf() + n, dst->leaf()); } IMMER_CATCH (...) { heap::deallocate(node_t::sizeof_leaf_n(n), dst); @@ -612,7 +612,7 @@ struct node IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf); auto dst = make_leaf_e(e); IMMER_TRY { - std::uninitialized_copy(src->leaf(), src->leaf() + n, dst->leaf()); + uninitialized_copy(src->leaf(), src->leaf() + n, dst->leaf()); } IMMER_CATCH (...) { heap::deallocate(node_t::max_sizeof_leaf, dst); @@ -627,7 +627,7 @@ struct node IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf); auto dst = make_leaf_n(allocn); IMMER_TRY { - std::uninitialized_copy(src->leaf(), src->leaf() + n, dst->leaf()); + uninitialized_copy(src->leaf(), src->leaf() + n, dst->leaf()); } IMMER_CATCH (...) { heap::deallocate(node_t::sizeof_leaf_n(allocn), dst); @@ -642,15 +642,14 @@ struct node IMMER_ASSERT_TAGGED(src2->kind() == kind_t::leaf); auto dst = make_leaf_n(n1 + n2); IMMER_TRY { - std::uninitialized_copy( - src1->leaf(), src1->leaf() + n1, dst->leaf()); + uninitialized_copy(src1->leaf(), src1->leaf() + n1, dst->leaf()); } IMMER_CATCH (...) { heap::deallocate(node_t::sizeof_leaf_n(n1 + n2), dst); IMMER_RETHROW; } IMMER_TRY { - std::uninitialized_copy( + uninitialized_copy( src2->leaf(), src2->leaf() + n2, dst->leaf() + n1); } IMMER_CATCH (...) { @@ -668,15 +667,14 @@ struct node IMMER_ASSERT_TAGGED(src2->kind() == kind_t::leaf); auto dst = make_leaf_e(e); IMMER_TRY { - std::uninitialized_copy( - src1->leaf(), src1->leaf() + n1, dst->leaf()); + uninitialized_copy(src1->leaf(), src1->leaf() + n1, dst->leaf()); } IMMER_CATCH (...) { heap::deallocate(max_sizeof_leaf, dst); IMMER_RETHROW; } IMMER_TRY { - std::uninitialized_copy( + uninitialized_copy( src2->leaf(), src2->leaf() + n2, dst->leaf() + n1); } IMMER_CATCH (...) { @@ -692,7 +690,7 @@ struct node IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf); auto dst = make_leaf_e(e); IMMER_TRY { - std::uninitialized_copy( + uninitialized_copy( src->leaf() + idx, src->leaf() + last, dst->leaf()); } IMMER_CATCH (...) { @@ -707,7 +705,7 @@ struct node IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf); auto dst = make_leaf_n(last - idx); IMMER_TRY { - std::uninitialized_copy( + uninitialized_copy( src->leaf() + idx, src->leaf() + last, dst->leaf()); } IMMER_CATCH (...) { diff --git a/immer/detail/rbts/operations.hpp b/immer/detail/rbts/operations.hpp index 8543915a..4cdf497e 100644 --- a/immer/detail/rbts/operations.hpp +++ b/immer/detail/rbts/operations.hpp @@ -1183,8 +1183,8 @@ struct slice_right_visitor : visitor_base> auto old_tail_size = pos.count(); auto new_tail_size = pos.index(last) + 1; auto new_tail = new_tail_size == old_tail_size - ? pos.node()->inc() - : node_t::copy_leaf(pos.node(), new_tail_size); + ? pos.node()->inc() + : node_t::copy_leaf(pos.node(), new_tail_size); return std::make_tuple(0, nullptr, new_tail_size, new_tail); } }; @@ -1263,10 +1263,10 @@ struct slice_left_mut_visitor return r; } else { using std::get; - auto newn = mutate ? (node->ensure_mutable_relaxed(e), node) - : node_t::make_inner_r_e(e); - auto newr = newn->relaxed(); - auto newcount = count - idx; + auto newn = mutate ? (node->ensure_mutable_relaxed(e), node) + : node_t::make_inner_r_e(e); + auto newr = newn->relaxed(); + auto newcount = count - idx; auto new_child_size = child_size - child_dropped_size; IMMER_TRY { auto subs = @@ -1277,9 +1277,9 @@ struct slice_left_mut_visitor pos.each_left(dec_visitor{}, idx); pos.copy_sizes( idx + 1, newcount - 1, new_child_size, newr->d.sizes + 1); - std::uninitialized_copy(node->inner() + idx + 1, - node->inner() + count, - newn->inner() + 1); + std::copy(node->inner() + idx + 1, + node->inner() + count, + newn->inner() + 1); newn->inner()[0] = get<1>(subs); newr->d.sizes[0] = new_child_size; newr->d.count = newcount; @@ -1348,9 +1348,9 @@ struct slice_left_mut_visitor idx + 1, newcount - 1, newr->d.sizes[0], newr->d.sizes + 1); newr->d.count = newcount; newn->inner()[0] = get<1>(subs); - std::uninitialized_copy(node->inner() + idx + 1, - node->inner() + count, - newn->inner() + 1); + std::copy(node->inner() + idx + 1, + node->inner() + count, + newn->inner() + 1); if (!mutate) { node_t::inc_nodes(newn->inner() + 1, newcount - 1); if (Mutating) @@ -1439,9 +1439,9 @@ struct slice_left_visitor : visitor_base> assert(newr->d.sizes[newr->d.count - 1] == pos.size() - dropped_size); newn->inner()[0] = get<1>(subs); - std::uninitialized_copy(n->inner() + idx + 1, - n->inner() + count, - newn->inner() + 1); + std::copy(n->inner() + idx + 1, + n->inner() + count, + newn->inner() + 1); node_t::inc_nodes(newn->inner() + 1, newr->d.count - 1); return std::make_tuple(pos.shift(), newn); } @@ -1627,9 +1627,9 @@ struct concat_merger auto data = to_->leaf(); auto to_copy = std::min(from_count - from_offset, *curr_ - to_offset_); - std::uninitialized_copy(from_data + from_offset, - from_data + from_offset + to_copy, - data + to_offset_); + std::copy(from_data + from_offset, + from_data + from_offset + to_copy, + data + to_offset_); to_offset_ += to_copy; from_offset += to_copy; if (*curr_ == to_offset_) { @@ -1662,9 +1662,9 @@ struct concat_merger auto data = to_->inner(); auto to_copy = std::min(from_count - from_offset, *curr_ - to_offset_); - std::uninitialized_copy(from_data + from_offset, - from_data + from_offset + to_copy, - data + to_offset_); + std::copy(from_data + from_offset, + from_data + from_offset + to_copy, + data + to_offset_); node_t::inc_nodes(from_data + from_offset, to_copy); auto sizes = to_->relaxed()->d.sizes; p.copy_sizes( @@ -2124,15 +2124,13 @@ struct concat_merger_mut data + to_offset_); } else { if (!from_mutate) - std::uninitialized_copy(from_data + from_offset, - from_data + from_offset + - to_copy, - data + to_offset_); + uninitialized_copy(from_data + from_offset, + from_data + from_offset + to_copy, + data + to_offset_); else - detail::uninitialized_move(from_data + from_offset, - from_data + from_offset + - to_copy, - data + to_offset_); + uninitialized_move(from_data + from_offset, + from_data + from_offset + to_copy, + data + to_offset_); } to_offset_ += to_copy; from_offset += to_copy; diff --git a/immer/detail/rbts/rrbtree.hpp b/immer/detail/rbts/rrbtree.hpp index 9288670b..558c19be 100644 --- a/immer/detail/rbts/rrbtree.hpp +++ b/immer/detail/rbts/rrbtree.hpp @@ -793,17 +793,17 @@ struct rrbtree return; } else if (tail_size + r.size <= branches) { l.ensure_mutable_tail(el, tail_size); - std::uninitialized_copy(r.tail->leaf(), - r.tail->leaf() + r.size, - l.tail->leaf() + tail_size); + uninitialized_copy(r.tail->leaf(), + r.tail->leaf() + r.size, + l.tail->leaf() + tail_size); l.size += r.size; return; } else { auto remaining = branches - tail_size; l.ensure_mutable_tail(el, tail_size); - std::uninitialized_copy(r.tail->leaf(), - r.tail->leaf() + remaining, - l.tail->leaf() + tail_size); + uninitialized_copy(r.tail->leaf(), + r.tail->leaf() + remaining, + l.tail->leaf() + tail_size); IMMER_TRY { auto new_tail = node_t::copy_leaf_e(el, r.tail, remaining, r.size); @@ -1062,26 +1062,26 @@ struct rrbtree } else if (tail_size + r.size <= branches) { l.ensure_mutable_tail(el, tail_size); if (r.tail->can_mutate(er)) - detail::uninitialized_move(r.tail->leaf(), - r.tail->leaf() + r.size, - l.tail->leaf() + tail_size); + uninitialized_move(r.tail->leaf(), + r.tail->leaf() + r.size, + l.tail->leaf() + tail_size); else - std::uninitialized_copy(r.tail->leaf(), - r.tail->leaf() + r.size, - l.tail->leaf() + tail_size); + uninitialized_copy(r.tail->leaf(), + r.tail->leaf() + r.size, + l.tail->leaf() + tail_size); l.size += r.size; return; } else { auto remaining = branches - tail_size; l.ensure_mutable_tail(el, tail_size); if (r.tail->can_mutate(er)) - detail::uninitialized_move(r.tail->leaf(), - r.tail->leaf() + remaining, - l.tail->leaf() + tail_size); + uninitialized_move(r.tail->leaf(), + r.tail->leaf() + remaining, + l.tail->leaf() + tail_size); else - std::uninitialized_copy(r.tail->leaf(), - r.tail->leaf() + remaining, - l.tail->leaf() + tail_size); + uninitialized_copy(r.tail->leaf(), + r.tail->leaf() + remaining, + l.tail->leaf() + tail_size); IMMER_TRY { auto new_tail = node_t::copy_leaf_e(el, r.tail, remaining, r.size); From e3ddbb7a9838fff46ab259be701036e959ddab6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Fri, 10 Jun 2022 14:25:11 +0200 Subject: [PATCH 015/104] Optimize our own implementation of uninitialized_copy It seems that libcxx does not do it this way. Maybe clang is smart enough to optimize this, but just in case... --- immer/detail/util.hpp | 72 +++++++++++++++---------------------------- 1 file changed, 24 insertions(+), 48 deletions(-) diff --git a/immer/detail/util.hpp b/immer/detail/util.hpp index 0a3a865e..f88dd91b 100644 --- a/immer/detail/util.hpp +++ b/immer/detail/util.hpp @@ -119,6 +119,30 @@ auto uninitialized_move(Iter1 first, Iter1 last, Iter2 out) } } +template +auto uninitialized_copy(SourceIter first, Sent last, SinkIter out) + -> std::enable_if_t, SinkIter> +{ + return std::copy(first, last, out); +} +template +auto uninitialized_copy(SourceIter first, Sent last, SinkIter out) + -> std::enable_if_t, SinkIter> +{ + auto current = out; + IMMER_TRY { + while (first != last) { + *current++ = *first; + ++first; + } + } + IMMER_CATCH (...) { + destroy(out, current); + IMMER_RETHROW; + } + return current; +} + template T* make(Args&&... args) { @@ -272,53 +296,5 @@ distance(Iterator first, Sentinel last) return last - first; } -/*! - * An alias to `std::uninitialized_copy` - */ -template < - typename Iterator, - typename Sentinel, - typename SinkIter, - std::enable_if_t< - detail::std_uninitialized_copy_supports_v, - bool> = true> -SinkIter uninitialized_copy(Iterator first, Sentinel last, SinkIter d_first) -{ - return std::uninitialized_copy(first, last, d_first); -} - -/*! - * Equivalent of the `std::uninitialized_copy` applied to the - * sentinel-delimited forward range @f$ [first, last) @f$ - */ -template ) &&detail:: - compatible_sentinel_v && - detail::is_forward_iterator_v, - bool> = true> -SinkIter uninitialized_copy(SourceIter first, Sent last, SinkIter d_first) -{ - auto current = d_first; - IMMER_TRY { - while (first != last) { - *current++ = *first; - ++first; - } - } - IMMER_CATCH (...) { - using Value = typename std::iterator_traits::value_type; - for (; d_first != current; ++d_first) { - d_first->~Value(); - } - IMMER_RETHROW; - } - return current; -} - } // namespace detail } // namespace immer From d004c6e4bf57b97b33156538e99ec700069cf2a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Fri, 10 Jun 2022 15:36:01 +0200 Subject: [PATCH 016/104] Fix implementation of unordered_copy --- immer/detail/util.hpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/immer/detail/util.hpp b/immer/detail/util.hpp index f88dd91b..61b634c9 100644 --- a/immer/detail/util.hpp +++ b/immer/detail/util.hpp @@ -129,18 +129,19 @@ template auto uninitialized_copy(SourceIter first, Sent last, SinkIter out) -> std::enable_if_t, SinkIter> { - auto current = out; + using value_t = typename std::iterator_traits::value_type; + auto current = out; IMMER_TRY { - while (first != last) { - *current++ = *first; - ++first; + for (; first != last; ++first, (void) ++current) { + ::new (const_cast(static_cast( + std::addressof(*current)))) value_t{*first}; } + return current; } IMMER_CATCH (...) { - destroy(out, current); + detail::destroy(out, current); IMMER_RETHROW; } - return current; } template From 27132d51839ee21caa40e71718a7a39b4aaa663f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Fri, 10 Jun 2022 15:36:26 +0200 Subject: [PATCH 017/104] Mark a bunch of functions as noexcept --- immer/detail/util.hpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/immer/detail/util.hpp b/immer/detail/util.hpp index 61b634c9..17ade0ae 100644 --- a/immer/detail/util.hpp +++ b/immer/detail/util.hpp @@ -40,14 +40,14 @@ T&& auto_const_cast(const T&& x) } template -inline auto destroy_at(T* p) +inline auto destroy_at(T* p) noexcept -> std::enable_if_t::value> { p->~T(); } template -inline auto destroy_at(T* p) +inline auto destroy_at(T* p) noexcept -> std::enable_if_t::value> { p->~T(); @@ -58,32 +58,32 @@ constexpr bool can_trivially_detroy = std::is_trivially_destructible< typename std::iterator_traits::value_type>::value; template -auto destroy(Iter, Iter last) +auto destroy(Iter, Iter last) noexcept -> std::enable_if_t, Iter> { return last; } template -auto destroy(Iter first, Iter last) +auto destroy(Iter first, Iter last) noexcept -> std::enable_if_t, Iter> { for (; first != last; ++first) - destroy_at(std::addressof(*first)); + detail::destroy_at(std::addressof(*first)); return first; } template -auto destroy_n(Iter first, Size n) +auto destroy_n(Iter first, Size n) noexcept -> std::enable_if_t, Iter> { return first + n; } template -auto destroy_n(Iter first, Size n) +auto destroy_n(Iter first, Size n) noexcept -> std::enable_if_t, Iter> { for (; n > 0; (void) ++first, --n) - destroy_at(std::addressof(*first)); + detail::destroy_at(std::addressof(*first)); return first; } @@ -95,7 +95,7 @@ constexpr bool can_trivially_copy = typename std::iterator_traits::value_type>::value; template -auto uninitialized_move(Iter1 first, Iter1 last, Iter2 out) +auto uninitialized_move(Iter1 first, Iter1 last, Iter2 out) noexcept -> std::enable_if_t, Iter2> { return std::copy(first, last, out); @@ -114,13 +114,13 @@ auto uninitialized_move(Iter1 first, Iter1 last, Iter2 out) } return current; } catch (...) { - destroy(out, current); + detail::destroy(out, current); throw; } } template -auto uninitialized_copy(SourceIter first, Sent last, SinkIter out) +auto uninitialized_copy(SourceIter first, Sent last, SinkIter out) noexcept -> std::enable_if_t, SinkIter> { return std::copy(first, last, out); From 9caf11101af8435e2db164d800599964af23741a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Fri, 10 Jun 2022 15:37:08 +0200 Subject: [PATCH 018/104] Fix ambiguities when compiling in C++17 mode --- immer/detail/arrays/node.hpp | 4 +- immer/detail/arrays/with_capacity.hpp | 2 +- immer/detail/hamts/node.hpp | 104 +++++++++++++------------- immer/detail/rbts/node.hpp | 27 ++++--- immer/detail/rbts/operations.hpp | 20 ++--- immer/detail/rbts/rrbtree.hpp | 42 +++++------ immer/detail/util.hpp | 4 +- 7 files changed, 106 insertions(+), 97 deletions(-) diff --git a/immer/detail/arrays/node.hpp b/immer/detail/arrays/node.hpp index 2914bec9..ac521e03 100644 --- a/immer/detail/arrays/node.hpp +++ b/immer/detail/arrays/node.hpp @@ -61,7 +61,7 @@ struct node static void delete_n(node_t* p, size_t sz, size_t cap) { - destroy_n(p->data(), sz); + detail::destroy_n(p->data(), sz); heap::deallocate(sizeof_n(cap), p); } @@ -98,7 +98,7 @@ struct node { auto p = make_n(n); IMMER_TRY { - uninitialized_copy(first, last, p->data()); + detail::uninitialized_copy(first, last, p->data()); return p; } IMMER_CATCH (...) { diff --git a/immer/detail/arrays/with_capacity.hpp b/immer/detail/arrays/with_capacity.hpp index 99ccb5d5..c80f7068 100644 --- a/immer/detail/arrays/with_capacity.hpp +++ b/immer/detail/arrays/with_capacity.hpp @@ -302,7 +302,7 @@ struct with_capacity { assert(sz <= size); if (ptr->can_mutate(e)) { - destroy_n(data() + size, size - sz); + detail::destroy_n(data() + size, size - sz); size = sz; } else { auto cap = recommend_down(sz, capacity); diff --git a/immer/detail/hamts/node.hpp b/immer/detail/hamts/node.hpp index 00da82fe..cc347a65 100644 --- a/immer/detail/hamts/node.hpp +++ b/immer/detail/hamts/node.hpp @@ -359,7 +359,7 @@ struct node auto src = values(); ownee(nxt) = e; IMMER_TRY { - uninitialized_copy(src, src + nv, dst); + detail::uninitialized_copy(src, src + nv, dst); } IMMER_CATCH (...) { deallocate_values(nxt, nv); @@ -382,7 +382,7 @@ struct node IMMER_TRY { new (dstp) T{std::move(v)}; IMMER_TRY { - uninitialized_copy(srcp, srcp + n, dstp + 1); + detail::uninitialized_copy(srcp, srcp + n, dstp + 1); } IMMER_CATCH (...) { dstp->~T(); @@ -406,7 +406,7 @@ struct node IMMER_TRY { new (dstp) T{std::move(v)}; IMMER_TRY { - uninitialized_move(srcp, srcp + n, dstp + 1); + detail::uninitialized_move(srcp, srcp + n, dstp + 1); } IMMER_CATCH (...) { dstp->~T(); @@ -430,12 +430,12 @@ struct node auto srcp = src->collisions(); auto dstp = dst->collisions(); IMMER_TRY { - dstp = uninitialized_copy(srcp, v, dstp); + dstp = detail::uninitialized_copy(srcp, v, dstp); IMMER_TRY { - uninitialized_copy(v + 1, srcp + n, dstp); + detail::uninitialized_copy(v + 1, srcp + n, dstp); } IMMER_CATCH (...) { - destroy(dst->collisions(), dstp); + detail::destroy(dst->collisions(), dstp); IMMER_RETHROW; } } @@ -457,12 +457,12 @@ struct node IMMER_TRY { new (dstp) T{std::move(v)}; IMMER_TRY { - dstp = uninitialized_copy(srcp, pos, dstp + 1); + dstp = detail::uninitialized_copy(srcp, pos, dstp + 1); IMMER_TRY { - uninitialized_copy(pos + 1, srcp + n, dstp); + detail::uninitialized_copy(pos + 1, srcp + n, dstp); } IMMER_CATCH (...) { - destroy(dst->collisions(), dstp); + detail::destroy(dst->collisions(), dstp); IMMER_RETHROW; } } @@ -526,13 +526,13 @@ struct node dst->impl.d.data.inner.datamap = src->datamap(); dst->impl.d.data.inner.nodemap = src->nodemap(); IMMER_TRY { - uninitialized_copy( + detail::uninitialized_copy( src->values(), src->values() + nv, dst->values()); IMMER_TRY { dst->values()[offset] = std::move(v); } IMMER_CATCH (...) { - destroy_n(dst->values(), nv); + detail::destroy_n(dst->values(), nv); IMMER_RETHROW; } } @@ -562,15 +562,15 @@ struct node dst->impl.d.data.inner.nodemap = src->nodemap() | bit; if (nv > 1) { IMMER_TRY { - uninitialized_copy( + detail::uninitialized_copy( src->values(), src->values() + voffset, dst->values()); IMMER_TRY { - uninitialized_copy(src->values() + voffset + 1, - src->values() + nv, - dst->values() + voffset); + detail::uninitialized_copy(src->values() + voffset + 1, + src->values() + nv, + dst->values() + voffset); } IMMER_CATCH (...) { - destroy_n(dst->values(), voffset); + detail::destroy_n(dst->values(), voffset); IMMER_RETHROW; } } @@ -605,23 +605,23 @@ struct node auto mutate_values = can_mutate(dst->impl.d.data.inner.values, e); IMMER_TRY { if (mutate_values) - uninitialized_move( + detail::uninitialized_move( src->values(), src->values() + voffset, dst->values()); else - uninitialized_copy( + detail::uninitialized_copy( src->values(), src->values() + voffset, dst->values()); IMMER_TRY { if (mutate_values) - uninitialized_move(src->values() + voffset + 1, - src->values() + nv, - dst->values() + voffset); + detail::uninitialized_move(src->values() + voffset + 1, + src->values() + nv, + dst->values() + voffset); else - uninitialized_copy(src->values() + voffset + 1, - src->values() + nv, - dst->values() + voffset); + detail::uninitialized_copy(src->values() + voffset + 1, + src->values() + nv, + dst->values() + voffset); } IMMER_CATCH (...) { - destroy_n(dst->values(), voffset); + detail::destroy_n(dst->values(), voffset); IMMER_RETHROW; } } @@ -657,15 +657,15 @@ struct node dst->impl.d.data.inner.datamap = src->datamap() | bit; IMMER_TRY { if (nv) - uninitialized_copy( + detail::uninitialized_copy( src->values(), src->values() + voffset, dst->values()); IMMER_TRY { new (dst->values() + voffset) T{std::move(value)}; IMMER_TRY { if (nv) - uninitialized_copy(src->values() + voffset, - src->values() + nv, - dst->values() + voffset + 1); + detail::uninitialized_copy(src->values() + voffset, + src->values() + nv, + dst->values() + voffset + 1); } IMMER_CATCH (...) { dst->values()[voffset].~T(); @@ -673,7 +673,7 @@ struct node } } IMMER_CATCH (...) { - destroy_n(dst->values(), voffset); + detail::destroy_n(dst->values(), voffset); IMMER_RETHROW; } } @@ -704,15 +704,15 @@ struct node dst->impl.d.data.inner.nodemap = src->nodemap(); if (nv > 1) { IMMER_TRY { - uninitialized_copy( + detail::uninitialized_copy( src->values(), src->values() + voffset, dst->values()); IMMER_TRY { - uninitialized_copy(src->values() + voffset + 1, - src->values() + nv, - dst->values() + voffset); + detail::uninitialized_copy(src->values() + voffset + 1, + src->values() + nv, + dst->values() + voffset); } IMMER_CATCH (...) { - destroy_n(dst->values(), voffset); + detail::destroy_n(dst->values(), voffset); IMMER_RETHROW; } } @@ -737,15 +737,15 @@ struct node dst->impl.d.data.inner.nodemap = src->nodemap(); IMMER_TRY { if (nv) - uninitialized_copy( + detail::uninitialized_copy( src->values(), src->values() + offset, dst->values()); IMMER_TRY { new (dst->values() + offset) T{std::move(v)}; IMMER_TRY { if (nv) - uninitialized_copy(src->values() + offset, - src->values() + nv, - dst->values() + offset + 1); + detail::uninitialized_copy(src->values() + offset, + src->values() + nv, + dst->values() + offset + 1); } IMMER_CATCH (...) { dst->values()[offset].~T(); @@ -753,7 +753,7 @@ struct node } } IMMER_CATCH (...) { - destroy_n(dst->values(), offset); + detail::destroy_n(dst->values(), offset); IMMER_RETHROW; } } @@ -781,10 +781,10 @@ struct node nv && can_mutate(dst->impl.d.data.inner.values, e); if (nv) { if (mutate_values) - uninitialized_move( + detail::uninitialized_move( src->values(), src->values() + offset, dst->values()); else - uninitialized_copy( + detail::uninitialized_copy( src->values(), src->values() + offset, dst->values()); } IMMER_TRY { @@ -792,13 +792,15 @@ struct node IMMER_TRY { if (nv) { if (mutate_values) - uninitialized_move(src->values() + offset, - src->values() + nv, - dst->values() + offset + 1); + detail::uninitialized_move(src->values() + offset, + src->values() + nv, + dst->values() + offset + + 1); else - uninitialized_copy(src->values() + offset, - src->values() + nv, - dst->values() + offset + 1); + detail::uninitialized_copy(src->values() + offset, + src->values() + nv, + dst->values() + offset + + 1); } } IMMER_CATCH (...) { @@ -807,7 +809,7 @@ struct node } } IMMER_CATCH (...) { - destroy_n(dst->values(), offset); + detail::destroy_n(dst->values(), offset); IMMER_RETHROW; } } @@ -905,7 +907,7 @@ struct node static void delete_values(values_t* p, count_t n) { assert(p); - destroy_n((T*) &p->d.buffer, n); + detail::destroy_n((T*) &p->d.buffer, n); deallocate_values(p, n); } @@ -924,7 +926,7 @@ struct node assert(p); IMMER_ASSERT_TAGGED(p->kind() == kind_t::collision); auto n = p->collision_count(); - destroy_n(p->collisions(), n); + detail::destroy_n(p->collisions(), n); deallocate_collision(p, n); } diff --git a/immer/detail/rbts/node.hpp b/immer/detail/rbts/node.hpp index 65cd97a8..4a9ae298 100644 --- a/immer/detail/rbts/node.hpp +++ b/immer/detail/rbts/node.hpp @@ -598,7 +598,8 @@ struct node IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf); auto dst = make_leaf_n(n); IMMER_TRY { - uninitialized_copy(src->leaf(), src->leaf() + n, dst->leaf()); + detail::uninitialized_copy( + src->leaf(), src->leaf() + n, dst->leaf()); } IMMER_CATCH (...) { heap::deallocate(node_t::sizeof_leaf_n(n), dst); @@ -612,7 +613,8 @@ struct node IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf); auto dst = make_leaf_e(e); IMMER_TRY { - uninitialized_copy(src->leaf(), src->leaf() + n, dst->leaf()); + detail::uninitialized_copy( + src->leaf(), src->leaf() + n, dst->leaf()); } IMMER_CATCH (...) { heap::deallocate(node_t::max_sizeof_leaf, dst); @@ -627,7 +629,8 @@ struct node IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf); auto dst = make_leaf_n(allocn); IMMER_TRY { - uninitialized_copy(src->leaf(), src->leaf() + n, dst->leaf()); + detail::uninitialized_copy( + src->leaf(), src->leaf() + n, dst->leaf()); } IMMER_CATCH (...) { heap::deallocate(node_t::sizeof_leaf_n(allocn), dst); @@ -642,14 +645,15 @@ struct node IMMER_ASSERT_TAGGED(src2->kind() == kind_t::leaf); auto dst = make_leaf_n(n1 + n2); IMMER_TRY { - uninitialized_copy(src1->leaf(), src1->leaf() + n1, dst->leaf()); + detail::uninitialized_copy( + src1->leaf(), src1->leaf() + n1, dst->leaf()); } IMMER_CATCH (...) { heap::deallocate(node_t::sizeof_leaf_n(n1 + n2), dst); IMMER_RETHROW; } IMMER_TRY { - uninitialized_copy( + detail::uninitialized_copy( src2->leaf(), src2->leaf() + n2, dst->leaf() + n1); } IMMER_CATCH (...) { @@ -667,14 +671,15 @@ struct node IMMER_ASSERT_TAGGED(src2->kind() == kind_t::leaf); auto dst = make_leaf_e(e); IMMER_TRY { - uninitialized_copy(src1->leaf(), src1->leaf() + n1, dst->leaf()); + detail::uninitialized_copy( + src1->leaf(), src1->leaf() + n1, dst->leaf()); } IMMER_CATCH (...) { heap::deallocate(max_sizeof_leaf, dst); IMMER_RETHROW; } IMMER_TRY { - uninitialized_copy( + detail::uninitialized_copy( src2->leaf(), src2->leaf() + n2, dst->leaf() + n1); } IMMER_CATCH (...) { @@ -690,7 +695,7 @@ struct node IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf); auto dst = make_leaf_e(e); IMMER_TRY { - uninitialized_copy( + detail::uninitialized_copy( src->leaf() + idx, src->leaf() + last, dst->leaf()); } IMMER_CATCH (...) { @@ -705,7 +710,7 @@ struct node IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf); auto dst = make_leaf_n(last - idx); IMMER_TRY { - uninitialized_copy( + detail::uninitialized_copy( src->leaf() + idx, src->leaf() + last, dst->leaf()); } IMMER_CATCH (...) { @@ -723,7 +728,7 @@ struct node new (dst->leaf() + n) T{std::forward(x)}; } IMMER_CATCH (...) { - destroy_n(dst->leaf(), n); + detail::destroy_n(dst->leaf(), n); heap::deallocate(node_t::sizeof_leaf_n(n + 1), dst); IMMER_RETHROW; } @@ -786,7 +791,7 @@ struct node static void delete_leaf(node_t* p, count_t n) { IMMER_ASSERT_TAGGED(p->kind() == kind_t::leaf); - destroy_n(p->leaf(), n); + detail::destroy_n(p->leaf(), n); heap::deallocate(ownee(p).owned() ? node_t::max_sizeof_leaf : node_t::sizeof_leaf_n(n), p); diff --git a/immer/detail/rbts/operations.hpp b/immer/detail/rbts/operations.hpp index 4cdf497e..bc65722c 100644 --- a/immer/detail/rbts/operations.hpp +++ b/immer/detail/rbts/operations.hpp @@ -1627,9 +1627,9 @@ struct concat_merger auto data = to_->leaf(); auto to_copy = std::min(from_count - from_offset, *curr_ - to_offset_); - std::copy(from_data + from_offset, - from_data + from_offset + to_copy, - data + to_offset_); + detail::uninitialized_copy(from_data + from_offset, + from_data + from_offset + to_copy, + data + to_offset_); to_offset_ += to_copy; from_offset += to_copy; if (*curr_ == to_offset_) { @@ -2124,13 +2124,15 @@ struct concat_merger_mut data + to_offset_); } else { if (!from_mutate) - uninitialized_copy(from_data + from_offset, - from_data + from_offset + to_copy, - data + to_offset_); + detail::uninitialized_copy(from_data + from_offset, + from_data + from_offset + + to_copy, + data + to_offset_); else - uninitialized_move(from_data + from_offset, - from_data + from_offset + to_copy, - data + to_offset_); + detail::uninitialized_move(from_data + from_offset, + from_data + from_offset + + to_copy, + data + to_offset_); } to_offset_ += to_copy; from_offset += to_copy; diff --git a/immer/detail/rbts/rrbtree.hpp b/immer/detail/rbts/rrbtree.hpp index 558c19be..cd76b4c9 100644 --- a/immer/detail/rbts/rrbtree.hpp +++ b/immer/detail/rbts/rrbtree.hpp @@ -574,7 +574,7 @@ struct rrbtree auto ts = size - tail_off; auto newts = new_size - tail_off; if (tail->can_mutate(e)) { - destroy_n(tail->leaf() + newts, ts - newts); + detail::destroy_n(tail->leaf() + newts, ts - newts); } else { auto new_tail = node_t::copy_leaf_e(e, tail, newts); dec_leaf(tail, ts); @@ -793,17 +793,17 @@ struct rrbtree return; } else if (tail_size + r.size <= branches) { l.ensure_mutable_tail(el, tail_size); - uninitialized_copy(r.tail->leaf(), - r.tail->leaf() + r.size, - l.tail->leaf() + tail_size); + detail::uninitialized_copy(r.tail->leaf(), + r.tail->leaf() + r.size, + l.tail->leaf() + tail_size); l.size += r.size; return; } else { auto remaining = branches - tail_size; l.ensure_mutable_tail(el, tail_size); - uninitialized_copy(r.tail->leaf(), - r.tail->leaf() + remaining, - l.tail->leaf() + tail_size); + detail::uninitialized_copy(r.tail->leaf(), + r.tail->leaf() + remaining, + l.tail->leaf() + tail_size); IMMER_TRY { auto new_tail = node_t::copy_leaf_e(el, r.tail, remaining, r.size); @@ -819,7 +819,7 @@ struct rrbtree } } IMMER_CATCH (...) { - destroy_n(r.tail->leaf() + tail_size, remaining); + detail::destroy_n(r.tail->leaf() + tail_size, remaining); IMMER_RETHROW; } } @@ -1062,26 +1062,26 @@ struct rrbtree } else if (tail_size + r.size <= branches) { l.ensure_mutable_tail(el, tail_size); if (r.tail->can_mutate(er)) - uninitialized_move(r.tail->leaf(), - r.tail->leaf() + r.size, - l.tail->leaf() + tail_size); + detail::uninitialized_move(r.tail->leaf(), + r.tail->leaf() + r.size, + l.tail->leaf() + tail_size); else - uninitialized_copy(r.tail->leaf(), - r.tail->leaf() + r.size, - l.tail->leaf() + tail_size); + detail::uninitialized_copy(r.tail->leaf(), + r.tail->leaf() + r.size, + l.tail->leaf() + tail_size); l.size += r.size; return; } else { auto remaining = branches - tail_size; l.ensure_mutable_tail(el, tail_size); if (r.tail->can_mutate(er)) - uninitialized_move(r.tail->leaf(), - r.tail->leaf() + remaining, - l.tail->leaf() + tail_size); + detail::uninitialized_move(r.tail->leaf(), + r.tail->leaf() + remaining, + l.tail->leaf() + tail_size); else - uninitialized_copy(r.tail->leaf(), - r.tail->leaf() + remaining, - l.tail->leaf() + tail_size); + detail::uninitialized_copy(r.tail->leaf(), + r.tail->leaf() + remaining, + l.tail->leaf() + tail_size); IMMER_TRY { auto new_tail = node_t::copy_leaf_e(el, r.tail, remaining, r.size); @@ -1097,7 +1097,7 @@ struct rrbtree } } IMMER_CATCH (...) { - destroy_n(r.tail->leaf() + tail_size, remaining); + detail::destroy_n(r.tail->leaf() + tail_size, remaining); IMMER_RETHROW; } } diff --git a/immer/detail/util.hpp b/immer/detail/util.hpp index 17ade0ae..fc0449dd 100644 --- a/immer/detail/util.hpp +++ b/immer/detail/util.hpp @@ -110,7 +110,7 @@ auto uninitialized_move(Iter1 first, Iter1 last, Iter2 out) try { for (; first != last; ++first, (void) ++current) { ::new (const_cast(static_cast( - std::addressof(*current)))) value_t{std::move(*first)}; + std::addressof(*current)))) value_t(std::move(*first)); } return current; } catch (...) { @@ -134,7 +134,7 @@ auto uninitialized_copy(SourceIter first, Sent last, SinkIter out) IMMER_TRY { for (; first != last; ++first, (void) ++current) { ::new (const_cast(static_cast( - std::addressof(*current)))) value_t{*first}; + std::addressof(*current)))) value_t(*first); } return current; } From 898f7d8e82c2679a93fe0cd1a81b57c2ae17ab24 Mon Sep 17 00:00:00 2001 From: Cyril Romain Date: Sat, 11 Jun 2022 12:57:58 +0200 Subject: [PATCH 019/104] Fix -fno-exceptions build (regression from 35147686) --- immer/detail/util.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/immer/detail/util.hpp b/immer/detail/util.hpp index fc0449dd..df21fc3f 100644 --- a/immer/detail/util.hpp +++ b/immer/detail/util.hpp @@ -107,15 +107,15 @@ auto uninitialized_move(Iter1 first, Iter1 last, Iter2 out) { using value_t = typename std::iterator_traits::value_type; auto current = out; - try { + IMMER_TRY { for (; first != last; ++first, (void) ++current) { ::new (const_cast(static_cast( std::addressof(*current)))) value_t(std::move(*first)); } return current; - } catch (...) { + } IMMER_CATCH (...) { detail::destroy(out, current); - throw; + IMMER_RETHROW; } } From c25a272ac749edf8445a2b28e68df6582cdb610e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Wed, 8 Jun 2022 19:52:33 +0200 Subject: [PATCH 020/104] Move objects whenever possible during champ erasure operations --- immer/detail/hamts/champ.hpp | 183 +++++++++++++++++++++++++---------- immer/detail/hamts/node.hpp | 163 +++++++++++++++++++++++++++++++ test/map/generic.ipp | 22 +++++ 3 files changed, 318 insertions(+), 50 deletions(-) diff --git a/immer/detail/hamts/champ.hpp b/immer/detail/hamts/champ.hpp index edd6e566..68be6153 100644 --- a/immer/detail/hamts/champ.hpp +++ b/immer/detail/hamts/champ.hpp @@ -906,58 +906,126 @@ struct champ } } + struct sub_result_mut + { + using kind_t = typename sub_result::kind_t; + using data_t = typename sub_result::data_t; + + kind_t kind; + data_t data; + bool mutated; + + sub_result_mut(sub_result a) + : kind{a.kind} + , data{a.data} + , mutated{false} + {} + sub_result_mut(sub_result a, bool m) + : kind{a.kind} + , data{a.data} + , mutated{m} + {} + sub_result_mut(bool m) + : kind{kind_t::nothing} + , mutated{m} {}; + sub_result_mut(T* x, bool m) + : kind{kind_t::singleton} + , mutated{m} + { + data.singleton = x; + }; + sub_result_mut(node_t* x, bool m) + : kind{kind_t::tree} + , mutated{m} + { + data.tree = x; + }; + }; + template - sub_result do_sub_mut( - edit_t e, node_t* node, const K& k, hash_t hash, shift_t shift) const + sub_result_mut do_sub_mut(edit_t e, + node_t* node, + const K& k, + hash_t hash, + shift_t shift, + void* store) const { + auto mutate = node->can_mutate(e); if (shift == max_shift) { auto fst = node->collisions(); auto lst = fst + node->collision_count(); - for (auto cur = fst; cur != lst; ++cur) - if (Equal{}(*cur, k)) - return node->collision_count() > 2 - ? node_t::owned( - node_t::copy_collision_remove(node, cur), - e) - : sub_result{fst + (cur == fst)}; - return {}; + for (auto cur = fst; cur != lst; ++cur) { + if (Equal{}(*cur, k)) { + if (node->collision_count() <= 2) { + if (mutate) { + auto r = new (store) + T{std::move(node->collisions()[cur == fst])}; + node_t::delete_collision(node); + return sub_result_mut{r, true}; + } else { + return sub_result_mut{fst + (cur == fst), false}; + } + } else { + auto r = mutate + ? node_t::move_collision_remove(node, cur) + : node_t::copy_collision_remove(node, cur); + return {node_t::owned(r, e), mutate}; + } + } + } + return {false}; } else { auto idx = (hash & (mask << shift)) >> shift; auto bit = bitmap_t{1u} << idx; if (node->nodemap() & bit) { auto offset = node->children_count(bit); - auto mutate = node->can_mutate(e); auto children = node->children(); auto child = children[offset]; - auto result = mutate ? do_sub_mut(e, child, k, hash, shift + B) - : do_sub(child, k, hash, shift + B); + auto result = + mutate ? do_sub_mut(e, child, k, hash, shift + B, store) + : do_sub(child, k, hash, shift + B); switch (result.kind) { case sub_result::nothing: - return {}; + return {mutate}; case sub_result::singleton: - return node->datamap() == 0 && - node->children_count() == 1 && shift > 0 - ? result - : node_t::owned_values( - node_t::copy_inner_replace_inline( + if (node->datamap() == 0 && node->children_count() == 1 && + shift > 0) { + if (mutate) + node_t::delete_inner(node); + return result; + } else { + auto r = + mutate ? node_t::move_inner_replace_inline( + e, node, bit, offset, - *result.data.singleton), - e); + result.mutated + ? std::move(*result.data.singleton) + : *result.data.singleton) + : node_t::copy_inner_replace_inline( + node, + bit, + offset, + *result.data.singleton); + if (result.mutated) + detail::destroy_at(result.data.singleton); + return {node_t::owned_values(r, e), mutate}; + } case sub_result::tree: if (mutate) { - if (child != result.data.tree) { - children[offset] = result.data.tree; - if (child->dec()) - node_t::delete_deep_shift(child, shift + B); - } - return node; + children[offset] = result.data.tree; + if (!result.mutated) + child->dec_unsafe(); + return {node, true}; } else { IMMER_TRY { - auto r = node_t::copy_inner_replace( - node, offset, result.data.tree); - return node_t::owned(r, e); + auto r = mutate + ? node_t::move_inner_replace( + node, offset, result.data.tree) + : node_t::copy_inner_replace( + node, offset, result.data.tree); + return {node_t::owned(r, e), mutate}; } IMMER_CATCH (...) { node_t::delete_deep_shift(result.data.tree, @@ -972,42 +1040,57 @@ struct champ if (Equal{}(*val, k)) { auto nv = node->data_count(); if (node->nodemap() || nv > 2) { - auto r = - node_t::copy_inner_remove_value(node, bit, offset); - return node_t::owned_values_safe(r, e); + auto r = mutate ? node_t::move_inner_remove_value( + e, node, bit, offset) + : node_t::copy_inner_remove_value( + node, bit, offset); + return {node_t::owned_values_safe(r, e), mutate}; } else if (nv == 2) { - return shift > 0 ? sub_result{node->values() + !offset} - : node_t::owned_values( - node_t::make_inner_n( - 0, - node->datamap() & ~bit, - node->values()[!offset]), - e); + if (shift > 0) { + if (mutate) { + auto r = new (store) + T{std::move(node->values()[!offset])}; + node_t::delete_inner(node); + return {r, mutate}; + } else { + return {node->values() + !offset, mutate}; + } + } else { + auto& v = node->values()[!offset]; + auto r = + node_t::make_inner_n(0, + node->datamap() & ~bit, + mutate ? std::move(v) : v); + assert(!node->nodemap()); + if (mutate) + node_t::delete_inner(node); + return {node_t::owned_values(r, e), mutate}; + } } else { assert(shift == 0); - return empty(); + if (mutate) + node_t::delete_inner(node); + return {empty(), true}; } } } - return {}; + return {mutate}; } } template void sub_mut(edit_t e, const K& k) { - auto hash = Hash{}(k); - auto res = do_sub_mut(e, root, k, hash, 0); + auto store = aligned_storage_for{}; + auto hash = Hash{}(k); + auto res = do_sub_mut(e, root, k, hash, 0, &store); switch (res.kind) { case sub_result::nothing: break; case sub_result::tree: - if (root != res.data.tree) { - auto p = root; - root = res.data.tree; - if (p->dec()) - node_t::delete_deep(p, 0); - } + if (!res.mutated) + root->dec_unsafe(); + root = res.data.tree; --size; break; default: diff --git a/immer/detail/hamts/node.hpp b/immer/detail/hamts/node.hpp index cc347a65..2f949793 100644 --- a/immer/detail/hamts/node.hpp +++ b/immer/detail/hamts/node.hpp @@ -446,6 +446,32 @@ struct node return dst; } + static node_t* move_collision_remove(node_t* src, T* v) + { + IMMER_ASSERT_TAGGED(src->kind() == kind_t::collision); + assert(src->collision_count() > 1); + auto n = src->collision_count(); + auto dst = make_collision_n(n - 1); + auto srcp = src->collisions(); + auto dstp = dst->collisions(); + IMMER_TRY { + dstp = detail::uninitialized_move(srcp, v, dstp); + IMMER_TRY { + detail::uninitialized_move(v + 1, srcp + n, dstp); + } + IMMER_CATCH (...) { + destroy(dst->collisions(), dstp); + IMMER_RETHROW; + } + } + IMMER_CATCH (...) { + deallocate_collision(dst, n - 1); + IMMER_RETHROW; + } + delete_collision(src); + return dst; + } + static node_t* copy_collision_replace(node_t* src, T* pos, T v) { IMMER_ASSERT_TAGGED(src->kind() == kind_t::collision); @@ -495,6 +521,22 @@ struct node return dst; } + static node_t* + move_inner_replace(node_t* src, count_t offset, node_t* child) + { + IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner); + auto n = src->children_count(); + auto dst = make_inner_n(n, src->impl.d.data.inner.values); + auto srcp = src->children(); + auto dstp = dst->children(); + dst->impl.d.data.inner.datamap = src->datamap(); + dst->impl.d.data.inner.nodemap = src->nodemap(); + std::copy(srcp, srcp + n, dstp); + dstp[offset] = child; + delete_inner(src); + return dst; + } + static node_t* owned(node_t* n, edit_t e) { ownee(n) = e; @@ -690,6 +732,68 @@ struct node return dst; } + static node_t* move_inner_replace_inline( + edit_t e, node_t* src, bitmap_t bit, count_t noffset, T value) + { + IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner); + assert(!(src->datamap() & bit)); + assert(src->nodemap() & bit); + assert(noffset == src->children_count(bit)); + auto n = src->children_count(); + auto nv = src->data_count(); + auto dst = make_inner_n(n - 1, nv + 1); + auto voffset = src->data_count(bit); + dst->impl.d.data.inner.nodemap = src->nodemap() & ~bit; + dst->impl.d.data.inner.datamap = src->datamap() | bit; + IMMER_TRY { + auto mutate_values = + nv && can_mutate(dst->impl.d.data.inner.values, e); + if (nv) { + if (mutate_values) + detail::uninitialized_move( + src->values(), src->values() + voffset, dst->values()); + else + detail::uninitialized_copy( + src->values(), src->values() + voffset, dst->values()); + } + IMMER_TRY { + new (dst->values() + voffset) T{std::move(value)}; + IMMER_TRY { + if (nv) { + if (mutate_values) + detail::uninitialized_move(src->values() + voffset, + src->values() + nv, + dst->values() + voffset + + 1); + else + detail::uninitialized_copy(src->values() + voffset, + src->values() + nv, + dst->values() + voffset + + 1); + } + } + IMMER_CATCH (...) { + dst->values()[voffset].~T(); + IMMER_RETHROW; + } + } + IMMER_CATCH (...) { + detail::destroy_n(dst->values(), voffset); + IMMER_RETHROW; + } + } + IMMER_CATCH (...) { + deallocate_inner(dst, n - 1, nv + 1); + IMMER_RETHROW; + } + std::copy(src->children(), src->children() + noffset, dst->children()); + std::copy(src->children() + noffset + 1, + src->children() + n, + dst->children() + noffset); + delete_inner(src); + return dst; + } + static node_t* copy_inner_remove_value(node_t* src, bitmap_t bit, count_t voffset) { @@ -726,6 +830,65 @@ struct node return dst; } + static node_t* move_inner_remove_value(edit_t e, + node_t* src, + bitmap_t bit, + count_t voffset) + { + IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner); + assert(!(src->nodemap() & bit)); + assert(src->datamap() & bit); + assert(voffset == src->data_count(bit)); + auto n = src->children_count(); + auto nv = src->data_count(); + auto dst = make_inner_n(n, nv - 1); + dst->impl.d.data.inner.datamap = src->datamap() & ~bit; + dst->impl.d.data.inner.nodemap = src->nodemap(); + if (nv > 1) { + auto mutate_values = can_mutate(dst->impl.d.data.inner.values, e); + if (mutate_values) { + IMMER_TRY { + detail::uninitialized_move( + src->values(), src->values() + voffset, dst->values()); + IMMER_TRY { + detail::uninitialized_move(src->values() + voffset + 1, + src->values() + nv, + dst->values() + voffset); + } + IMMER_CATCH (...) { + detail::destroy_n(dst->values(), voffset); + IMMER_RETHROW; + } + } + IMMER_CATCH (...) { + deallocate_inner(dst, n, nv - 1); + IMMER_RETHROW; + } + } else { + IMMER_TRY { + detail::uninitialized_copy( + src->values(), src->values() + voffset, dst->values()); + IMMER_TRY { + detail::uninitialized_copy(src->values() + voffset + 1, + src->values() + nv, + dst->values() + voffset); + } + IMMER_CATCH (...) { + detail::destroy_n(dst->values(), voffset); + IMMER_RETHROW; + } + } + IMMER_CATCH (...) { + deallocate_inner(dst, n, nv - 1); + IMMER_RETHROW; + } + } + } + std::copy(src->children(), src->children() + n, dst->children()); + delete_inner(src); + return dst; + } + static node_t* copy_inner_insert_value(node_t* src, bitmap_t bit, T v) { IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner); diff --git a/test/map/generic.ipp b/test/map/generic.ipp index 4420f7e8..a4153b4c 100644 --- a/test/map/generic.ipp +++ b/test/map/generic.ipp @@ -366,6 +366,28 @@ TEST_CASE("exception safety") CHECK(d.happenings > 0); IMMER_TRACE_E(d.happenings); } + + SECTION("erase collisisions move") + { + auto vals = make_values_with_collisions(n); + auto v = dadaist_conflictor_map_t{}; + auto d = dadaism{}; + for (auto i = 0u; i < n; ++i) + v = std::move(v).insert(vals[i]); + for (auto i = 0u; i < v.size();) { + try { + // auto s = d.next(); + v = std::move(v).erase(vals[i].first); + ++i; + } catch (dada_error) {} + for (auto i : test_irange(0u, i)) + CHECK(v.count(vals[i].first) == 0); + for (auto i : test_irange(i, n)) + CHECK(v.at(vals[i].first) == vals[i].second); + } + CHECK(d.happenings == 0); + IMMER_TRACE_E(d.happenings); + } } namespace { From 175c05fb3a1bf92640c5df5eca4c7e72e26ae1aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Tue, 21 Jun 2022 20:35:16 +0200 Subject: [PATCH 021/104] Fix #219, ambiguous calls to destroy_n --- immer/detail/hamts/node.hpp | 2 +- immer/detail/rbts/node.hpp | 4 ++-- immer/detail/rbts/operations.hpp | 6 +++--- immer/detail/rbts/rbtree.hpp | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/immer/detail/hamts/node.hpp b/immer/detail/hamts/node.hpp index 2f949793..9aa57f21 100644 --- a/immer/detail/hamts/node.hpp +++ b/immer/detail/hamts/node.hpp @@ -460,7 +460,7 @@ struct node detail::uninitialized_move(v + 1, srcp + n, dstp); } IMMER_CATCH (...) { - destroy(dst->collisions(), dstp); + detail::destroy(dst->collisions(), dstp); IMMER_RETHROW; } } diff --git a/immer/detail/rbts/node.hpp b/immer/detail/rbts/node.hpp index 4a9ae298..54156f9e 100644 --- a/immer/detail/rbts/node.hpp +++ b/immer/detail/rbts/node.hpp @@ -657,7 +657,7 @@ struct node src2->leaf(), src2->leaf() + n2, dst->leaf() + n1); } IMMER_CATCH (...) { - destroy_n(dst->leaf(), n1); + detail::destroy_n(dst->leaf(), n1); heap::deallocate(node_t::sizeof_leaf_n(n1 + n2), dst); IMMER_RETHROW; } @@ -683,7 +683,7 @@ struct node src2->leaf(), src2->leaf() + n2, dst->leaf() + n1); } IMMER_CATCH (...) { - destroy_n(dst->leaf(), n1); + detail::destroy_n(dst->leaf(), n1); heap::deallocate(max_sizeof_leaf, dst); IMMER_RETHROW; } diff --git a/immer/detail/rbts/operations.hpp b/immer/detail/rbts/operations.hpp index bc65722c..d92832b0 100644 --- a/immer/detail/rbts/operations.hpp +++ b/immer/detail/rbts/operations.hpp @@ -1067,8 +1067,8 @@ struct slice_right_mut_visitor node->inc(); return std::make_tuple(0, nullptr, new_tail_size, node); } else if (mutate) { - destroy_n(node->leaf() + new_tail_size, - old_tail_size - new_tail_size); + detail::destroy_n(node->leaf() + new_tail_size, + old_tail_size - new_tail_size); return std::make_tuple(0, nullptr, new_tail_size, node); } else { auto new_tail = node_t::copy_leaf_e(e, node, new_tail_size); @@ -1386,7 +1386,7 @@ struct slice_left_mut_visitor auto data = node->leaf(); auto newcount = count - idx; std::move(data + idx, data + count, data); - destroy_n(data + newcount, idx); + detail::destroy_n(data + newcount, idx); return std::make_tuple(0, node); } else { auto newn = node_t::copy_leaf_e(e, node, idx, count); diff --git a/immer/detail/rbts/rbtree.hpp b/immer/detail/rbts/rbtree.hpp index 0b016a0e..c8fa4251 100644 --- a/immer/detail/rbts/rbtree.hpp +++ b/immer/detail/rbts/rbtree.hpp @@ -15,9 +15,9 @@ #include #include -#include #include #include +#include namespace immer { namespace detail { @@ -465,7 +465,7 @@ struct rbtree auto ts = size - tail_off; auto newts = new_size - tail_off; if (tail->can_mutate(e)) { - destroy_n(tail->leaf() + newts, ts - newts); + detail::destroy_n(tail->leaf() + newts, ts - newts); } else { auto new_tail = node_t::copy_leaf_e(e, tail, newts); dec_leaf(tail, ts); From f0defde7ddd49b84bbe60399cda20cad89a42f5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Tue, 21 Jun 2022 20:38:22 +0200 Subject: [PATCH 022/104] Ensure that champ update operations move the payload in the value --- immer/map.hpp | 4 ++++ immer/table.hpp | 1 + 2 files changed, 5 insertions(+) diff --git a/immer/map.hpp b/immer/map.hpp index acbbc0b1..94ac2791 100644 --- a/immer/map.hpp +++ b/immer/map.hpp @@ -77,6 +77,10 @@ class map { return v.second; } + T&& operator()(value_t&& v) const noexcept + { + return std::move(v.second); + } }; struct project_value_ptr diff --git a/immer/table.hpp b/immer/table.hpp index c4118054..0a5a3efa 100644 --- a/immer/table.hpp +++ b/immer/table.hpp @@ -94,6 +94,7 @@ class table struct project_value { const T& operator()(const value_t& v) const noexcept { return v; } + T&& operator()(value_t&& v) const noexcept { return std::move(v); } }; struct project_value_ptr From 189725281318fb4550bbc7a80493d700db65ecd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Tue, 21 Jun 2022 20:42:02 +0200 Subject: [PATCH 023/104] Remove vestige --- immer/detail/type_traits.hpp | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/immer/detail/type_traits.hpp b/immer/detail/type_traits.hpp index afb2652c..091fe04d 100644 --- a/immer/detail/type_traits.hpp +++ b/immer/detail/type_traits.hpp @@ -201,23 +201,5 @@ struct std_distance_supports< template constexpr bool std_distance_supports_v = std_distance_supports::value; -template -struct std_uninitialized_copy_supports : std::false_type -{}; - -template -struct std_uninitialized_copy_supports< - T, - U, - V, - void_t(), std::declval(), std::declval()))>> - : std::true_type -{}; - -template -constexpr bool std_uninitialized_copy_supports_v = - std_uninitialized_copy_supports::value; - } // namespace detail } // namespace immer From 2a32d5db4d139f8631f3617b06dc51e9068d5995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Thu, 23 Jun 2022 13:21:33 +0200 Subject: [PATCH 024/104] Add update_move operation to map fuzzer --- extra/fuzzer/map-gc.cpp | 7 +++++++ extra/fuzzer/map-st.cpp | 7 +++++++ extra/fuzzer/map.cpp | 7 +++++++ 3 files changed, 21 insertions(+) diff --git a/extra/fuzzer/map-gc.cpp b/extra/fuzzer/map-gc.cpp index fc996cf7..c4028df8 100644 --- a/extra/fuzzer/map-gc.cpp +++ b/extra/fuzzer/map-gc.cpp @@ -52,6 +52,7 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, op_iterate, op_find, op_update, + op_update_move, op_diff }; auto src = read(in, is_valid_var); @@ -97,6 +98,12 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, vars[dst] = vars[src].update(key, [](int x) { return x + 1; }); break; } + case op_update_move: { + auto key = read(in); + vars[dst] = + std::move(vars[src]).update(key, [](int x) { return x + 1; }); + break; + } case op_diff: { auto&& a = vars[src]; auto&& b = vars[dst]; diff --git a/extra/fuzzer/map-st.cpp b/extra/fuzzer/map-st.cpp index c0610cdd..6a0a96ff 100644 --- a/extra/fuzzer/map-st.cpp +++ b/extra/fuzzer/map-st.cpp @@ -47,6 +47,7 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, op_iterate, op_find, op_update, + op_update_move, op_diff }; auto src = read(in, is_valid_var); @@ -92,6 +93,12 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, vars[dst] = vars[src].update(key, [](int x) { return x + 1; }); break; } + case op_update_move: { + auto key = read(in); + vars[dst] = + std::move(vars[src]).update(key, [](int x) { return x + 1; }); + break; + } case op_diff: { auto&& a = vars[src]; auto&& b = vars[dst]; diff --git a/extra/fuzzer/map.cpp b/extra/fuzzer/map.cpp index 529bed8b..5fe571d0 100644 --- a/extra/fuzzer/map.cpp +++ b/extra/fuzzer/map.cpp @@ -40,6 +40,7 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, op_iterate, op_find, op_update, + op_update_move, op_diff, }; auto src = read(in, is_valid_var); @@ -85,6 +86,12 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, vars[dst] = vars[src].update(key, [](int x) { return x + 1; }); break; } + case op_update_move: { + auto key = read(in); + vars[dst] = + std::move(vars[src]).update(key, [](int x) { return x + 1; }); + break; + } case op_diff: { auto&& a = vars[src]; auto&& b = vars[dst]; From 30ce53b4df64b20b84e94633011ba20f449645b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Thu, 23 Jun 2022 13:22:52 +0200 Subject: [PATCH 025/104] Add test for memory leak found by oss-fuzz --- ...testcase-minimized-map-st-5313188008165376 | Bin 0 -> 1025 bytes test/oss-fuzz/map-st-1.cpp | 134 ++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 test/oss-fuzz/data/clusterfuzz-testcase-minimized-map-st-5313188008165376 create mode 100644 test/oss-fuzz/map-st-1.cpp diff --git a/test/oss-fuzz/data/clusterfuzz-testcase-minimized-map-st-5313188008165376 b/test/oss-fuzz/data/clusterfuzz-testcase-minimized-map-st-5313188008165376 new file mode 100644 index 0000000000000000000000000000000000000000..290b5da078ea24dc8802aefe453d01d34e118cd5 GIT binary patch literal 1025 zcmcIhyH3ME5FGD0c4Co+f}Ri>DyY+hf)*hmLBS7%o{9>IFM$u>GvFr>?}WF)(~3mG zF+N}7Q|urbR-W7Q&hB`39aOOmV_GlhMGa3Rk{S@eM?)g|y74L)w?l*~SHRsqiGFNjOxintaJH}nBe-Gt}ynwlwf z-se&Ol~40oD^_3)t1+;S1BlFyg41G6iIA`s4lnd=B3d#cZ0gXeophB?>Csk_| zId+y)!iEyQ?9i%!cj;n2%u*)S&kS2qMq@WC$@jRD>oLoAW*YmJ$r+2~we$14+QsuZ d8ULd{u}u>0F1W+QJReCP=B}H@*<->F{{cT+TXz5e literal 0 HcmV?d00001 diff --git a/test/oss-fuzz/map-st-1.cpp b/test/oss-fuzz/map-st-1.cpp new file mode 100644 index 00000000..a692dac5 --- /dev/null +++ b/test/oss-fuzz/map-st-1.cpp @@ -0,0 +1,134 @@ +// +// 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 "input.hpp" + +#include "extra/fuzzer/fuzzer_gc_guard.hpp" + +#include +#include +#include + +#include + +using st_memory = immer::memory_policy, + immer::unsafe_refcount_policy, + immer::no_lock_policy, + immer::no_transience_policy, + false>; + +struct colliding_hash_t +{ + std::size_t operator()(std::size_t x) const { return x & ~15; } +}; + +namespace { + +int run_input(const std::uint8_t* data, std::size_t size) +{ + constexpr auto var_count = 4; + + using map_t = immer:: + map, st_memory>; + + auto vars = std::array{}; + + auto is_valid_var = [&](auto idx) { return idx >= 0 && idx < var_count; }; + + return fuzzer_input{data, size}.run([&](auto& in) { + enum ops + { + op_set, + op_erase, + op_set_move, + op_erase_move, + op_iterate, + op_find, + op_update, + op_diff + }; + auto src = read(in, is_valid_var); + auto dst = read(in, is_valid_var); + switch (read(in)) { + case op_set: { + auto value = read(in); + vars[dst] = vars[src].set(value, 42); + break; + } + case op_erase: { + auto value = read(in); + vars[dst] = vars[src].erase(value); + break; + } + case op_set_move: { + auto value = read(in); + vars[dst] = std::move(vars[src]).set(value, 42); + break; + } + case op_erase_move: { + auto value = read(in); + vars[dst] = std::move(vars[src]).erase(value); + break; + } + case op_iterate: { + auto srcv = vars[src]; + for (const auto& v : srcv) { + vars[dst] = vars[dst].set(v.first, v.second); + } + break; + } + case op_find: { + auto value = read(in); + auto res = vars[src].find(value); + if (res != nullptr) { + vars[dst] = vars[dst].set(*res, 42); + } + break; + } + case op_update: { + auto key = read(in); + vars[dst] = vars[src].update(key, [](int x) { return x + 1; }); + break; + } + case op_diff: { + auto&& a = vars[src]; + auto&& b = vars[dst]; + diff( + a, + b, + [&](auto&& x) { + assert(!a.count(x.first)); + assert(b.count(x.first)); + }, + [&](auto&& x) { + assert(a.count(x.first)); + assert(!b.count(x.first)); + }, + [&](auto&& x, auto&& y) { + assert(x.first == y.first); + assert(x.second != y.second); + }); + } + default: + break; + }; + return true; + }); +} + +} // namespace + +TEST_CASE("https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=48207") +{ + SECTION("fuzzer") + { + auto input = load_input( + "clusterfuzz-testcase-minimized-map-st-5313188008165376"); + CHECK(run_input(input.data(), input.size()) == 0); + } +} From 1c780ba19b1cf92c4bb4be8b43be6c6b234b034c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Thu, 23 Jun 2022 14:55:35 +0200 Subject: [PATCH 026/104] Fix memory leak --- immer/detail/hamts/champ.hpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/immer/detail/hamts/champ.hpp b/immer/detail/hamts/champ.hpp index 68be6153..a48250e0 100644 --- a/immer/detail/hamts/champ.hpp +++ b/immer/detail/hamts/champ.hpp @@ -990,8 +990,11 @@ struct champ case sub_result::singleton: if (node->datamap() == 0 && node->children_count() == 1 && shift > 0) { - if (mutate) + if (mutate) { node_t::delete_inner(node); + if (!result.mutated) + child->dec_unsafe(); + } return result; } else { auto r = @@ -1010,6 +1013,8 @@ struct champ *result.data.singleton); if (result.mutated) detail::destroy_at(result.data.singleton); + else if (mutate) + child->dec_unsafe(); return {node_t::owned_values(r, e), mutate}; } case sub_result::tree: From c37693ac4adf3a0ab6e7d52a1ca095b651eee8be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Thu, 23 Jun 2022 14:55:53 +0200 Subject: [PATCH 027/104] Remove vestige --- immer/detail/hamts/champ.hpp | 9 +++------ immer/detail/hamts/node.hpp | 16 ---------------- 2 files changed, 3 insertions(+), 22 deletions(-) diff --git a/immer/detail/hamts/champ.hpp b/immer/detail/hamts/champ.hpp index a48250e0..b2c371ee 100644 --- a/immer/detail/hamts/champ.hpp +++ b/immer/detail/hamts/champ.hpp @@ -1025,12 +1025,9 @@ struct champ return {node, true}; } else { IMMER_TRY { - auto r = mutate - ? node_t::move_inner_replace( - node, offset, result.data.tree) - : node_t::copy_inner_replace( - node, offset, result.data.tree); - return {node_t::owned(r, e), mutate}; + auto r = node_t::copy_inner_replace( + node, offset, result.data.tree); + return {node_t::owned(r, e), false}; } IMMER_CATCH (...) { node_t::delete_deep_shift(result.data.tree, diff --git a/immer/detail/hamts/node.hpp b/immer/detail/hamts/node.hpp index 9aa57f21..cb64005d 100644 --- a/immer/detail/hamts/node.hpp +++ b/immer/detail/hamts/node.hpp @@ -521,22 +521,6 @@ struct node return dst; } - static node_t* - move_inner_replace(node_t* src, count_t offset, node_t* child) - { - IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner); - auto n = src->children_count(); - auto dst = make_inner_n(n, src->impl.d.data.inner.values); - auto srcp = src->children(); - auto dstp = dst->children(); - dst->impl.d.data.inner.datamap = src->datamap(); - dst->impl.d.data.inner.nodemap = src->nodemap(); - std::copy(srcp, srcp + n, dstp); - dstp[offset] = child; - delete_inner(src); - return dst; - } - static node_t* owned(node_t* n, edit_t e) { ownee(n) = e; From c4be744a490a7a9ce1a1551ebdfa30628d5a98c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Fri, 24 Jun 2022 00:45:38 +0200 Subject: [PATCH 028/104] Try something --- immer/detail/hamts/champ.hpp | 2 +- test/map/generic.ipp | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/immer/detail/hamts/champ.hpp b/immer/detail/hamts/champ.hpp index b2c371ee..decaada7 100644 --- a/immer/detail/hamts/champ.hpp +++ b/immer/detail/hamts/champ.hpp @@ -995,7 +995,7 @@ struct champ if (!result.mutated) child->dec_unsafe(); } - return result; + return {result.data.singleton, mutate}; } else { auto r = mutate ? node_t::move_inner_replace_inline( diff --git a/test/map/generic.ipp b/test/map/generic.ipp index a4153b4c..713a981b 100644 --- a/test/map/generic.ipp +++ b/test/map/generic.ipp @@ -244,6 +244,14 @@ TEST_CASE("update a lot") CHECK(v[i] == i + 1); } } + + SECTION("erase") + { + for (decltype(v.size()) i = 0; i < v.size(); ++i) { + v = std::move(v).erase(i); + CHECK(v.count(i) == 0); + } + } } TEST_CASE("exception safety") From 037b34afe357b59fd58260b1986d6794c4dddd86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Tue, 28 Jun 2022 09:25:31 +0200 Subject: [PATCH 029/104] Fix memory leak --- immer/detail/hamts/champ.hpp | 4 +- ...testcase-minimized-map-st-6242663155761152 | Bin 0 -> 48 bytes test/oss-fuzz/map-st-2.cpp | 201 ++++++++++++++++++ 3 files changed, 203 insertions(+), 2 deletions(-) create mode 100644 test/oss-fuzz/data/clusterfuzz-testcase-minimized-map-st-6242663155761152 create mode 100644 test/oss-fuzz/map-st-2.cpp diff --git a/immer/detail/hamts/champ.hpp b/immer/detail/hamts/champ.hpp index decaada7..f33a3c59 100644 --- a/immer/detail/hamts/champ.hpp +++ b/immer/detail/hamts/champ.hpp @@ -973,7 +973,7 @@ struct champ } } } - return {false}; + return {mutate}; } else { auto idx = (hash & (mask << shift)) >> shift; auto bit = bitmap_t{1u} << idx; @@ -1072,7 +1072,7 @@ struct champ assert(shift == 0); if (mutate) node_t::delete_inner(node); - return {empty(), true}; + return {empty(), mutate}; } } } diff --git a/test/oss-fuzz/data/clusterfuzz-testcase-minimized-map-st-6242663155761152 b/test/oss-fuzz/data/clusterfuzz-testcase-minimized-map-st-6242663155761152 new file mode 100644 index 0000000000000000000000000000000000000000..3cdc39d005888bd3cff01893b3ecb13256d41f9d GIT binary patch literal 48 acmY#T00IUiz{m&^fdfVb24)ZkrUC#- +#include +#include + +#include + +#define IMMER_FUZZED_TRACE_ENABLE 0 + +#if IMMER_FUZZED_TRACE_ENABLE +#include +#define IMMER_FUZZED_TRACE(...) fmt::print(std::cerr, __VA_ARGS__) +#else +#define IMMER_FUZZED_TRACE(...) +#endif + +using st_memory = immer::memory_policy, + immer::unsafe_refcount_policy, + immer::no_lock_policy, + immer::no_transience_policy, + false>; + +struct colliding_hash_t +{ + std::size_t operator()(std::size_t x) const { return x & ~15; } +}; + +namespace { + +int run_input(const std::uint8_t* data, std::size_t size) +{ + constexpr auto var_count = 4; + + using map_t = immer:: + map, st_memory>; + + auto vars = std::array{}; + +#if IMMER_FUZZED_TRACE_ENABLE + IMMER_FUZZED_TRACE("/// new test run\n"); + IMMER_FUZZED_TRACE("using map_t = immer::map," + "immer::default_memory_policy, {}>;\n", + immer::default_bits); + for (auto i = 0u; i < var_count; ++i) + IMMER_FUZZED_TRACE("auto v{} = map_t{{}};\n", i); +#endif + + auto is_valid_var = [&](auto idx) { return idx >= 0 && idx < var_count; }; + + return fuzzer_input{data, size}.run([&](auto& in) { + enum ops + { + op_set, + op_erase, + op_set_move, + op_erase_move, + op_iterate, + op_find, + op_update, + op_update_move, + op_diff + }; + auto src = read(in, is_valid_var); + auto dst = read(in, is_valid_var); + switch (read(in)) { + case op_set: { + auto value = read(in); + IMMER_FUZZED_TRACE("v{} = v{}.set({}, 42);\n", +dst, +src, +value); + vars[dst] = vars[src].set(value, 42); + break; + } + case op_erase: { + auto value = read(in); + IMMER_FUZZED_TRACE("v{} = v{}.erase({});\n", +dst, +src, +value); + vars[dst] = vars[src].erase(value); + break; + } + case op_set_move: { + auto value = read(in); + IMMER_FUZZED_TRACE( + "v{} = std::move(v{}).set({}, 42);\n", +dst, +src, +value); + vars[dst] = std::move(vars[src]).set(value, 42); + break; + } + case op_erase_move: { + auto value = read(in); + IMMER_FUZZED_TRACE( + "v{} = std::move(v{}).erase({});\n", +dst, +src, +value); + vars[dst] = std::move(vars[src]).erase(value); + break; + } + case op_iterate: { + auto srcv = vars[src]; + IMMER_FUZZED_TRACE("{auto srcv = {}; for (const auto& v : srcv) " + "v{} = v{}.set(v.first, v.second); }\n", + +dst, + +src); + for (const auto& v : srcv) { + vars[dst] = vars[dst].set(v.first, v.second); + } + break; + } + case op_find: { + auto value = read(in); + auto res = vars[src].find(value); + IMMER_FUZZED_TRACE("if (auto res = v{}.find({}); res) v{} = " + "v{}.set(*res, 42);\n", + +src, + +value, + +dst, + +dst); + if (res != nullptr) { + vars[dst] = vars[dst].set(*res, 42); + } + break; + } + case op_update: { + auto key = read(in); + IMMER_FUZZED_TRACE("v{} = v{}.update(key, [](int x) { " + "return x + 1; });\n", + +dst, + +src, + +key); + vars[dst] = vars[src].update(key, [](int x) { return x + 1; }); + break; + } + case op_update_move: { + auto key = read(in); + IMMER_FUZZED_TRACE("v{} = std::move(v{}).update(key, [](int x) { " + "return x + 1; });\n", + +dst, + +src, + +key); + vars[dst] = + std::move(vars[src]).update(key, [](int x) { return x + 1; }); + break; + } + case op_diff: { + auto&& a = vars[src]; + auto&& b = vars[dst]; + diff( + a, + b, + [&](auto&& x) { + assert(!a.count(x.first)); + assert(b.count(x.first)); + }, + [&](auto&& x) { + assert(a.count(x.first)); + assert(!b.count(x.first)); + }, + [&](auto&& x, auto&& y) { + assert(x.first == y.first); + assert(x.second != y.second); + }); + } + default: + break; + }; + return true; + }); +} + +} // namespace + +TEST_CASE("https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=48398") +{ + SECTION("fuzzer") + { + auto input = load_input( + "clusterfuzz-testcase-minimized-map-st-6242663155761152"); + CHECK(run_input(input.data(), input.size()) == 0); + } + + SECTION("translated") + { + using map_t = immer::map, + immer::default_memory_policy, + 5>; + auto v0 = map_t{}; + auto v1 = map_t{}; + v0 = v0.set(0, 42); + v1 = v0; + v0 = std::move(v1).erase(0); + } +} From 893c08461a4d4b905f5569c083765e7c437f1a1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Tue, 28 Jun 2022 10:12:22 +0200 Subject: [PATCH 030/104] Add tools for printing statistics about trees --- immer/config.hpp | 8 ++++ immer/detail/hamts/champ.hpp | 86 ++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/immer/config.hpp b/immer/config.hpp index 91e7978e..2e8378f6 100644 --- a/immer/config.hpp +++ b/immer/config.hpp @@ -70,6 +70,10 @@ #define IMMER_DEBUG_PRINT 0 #endif +#ifndef IMMER_DEBUG_STATS +#define IMMER_DEBUG_STATS 0 +#endif + #ifndef IMMER_DEBUG_DEEP_CHECK #define IMMER_DEBUG_DEEP_CHECK 0 #endif @@ -79,6 +83,10 @@ #include #endif +#if IMMER_DEBUG_STATS +#include +#endif + #if IMMER_DEBUG_TRACES #define IMMER_TRACE(...) std::cout << __VA_ARGS__ << std::endl #else diff --git a/immer/detail/hamts/champ.hpp b/immer/detail/hamts/champ.hpp index f33a3c59..455f8a78 100644 --- a/immer/detail/hamts/champ.hpp +++ b/immer/detail/hamts/champ.hpp @@ -17,6 +17,60 @@ namespace immer { namespace detail { namespace hamts { +#if IMMER_DEBUG_STATS +struct champ_debug_stats +{ + std::size_t bits = {}; + std::size_t value_size = {}; + std::size_t child_size = sizeof(void*); + + std::size_t inner_node_count = {}; + std::size_t inner_node_w_value_count = {}; + std::size_t inner_node_w_child_count = {}; + std::size_t collision_node_count = {}; + + std::size_t child_count = {}; + std::size_t value_count = {}; + std::size_t collision_count = {}; + + void print() const + { + auto m = std::size_t{1} << bits; + std::cerr << "---\n"; + { + std::cerr << "collisions\n" + << " ratio = " << (100. * collision_count / value_count) + << " %\n"; + } + { + auto capacity = m * inner_node_count; + auto child_utilization = 100. * child_count / capacity; + auto value_utilization = 100. * value_count / capacity; + auto utilization = + 100. * (value_count * value_size + child_count * child_size) / + (capacity * value_size + capacity * child_size); + std::cerr << "utilization\n" + << " total = " << utilization << " %\n" + << " children = " << child_utilization << " %\n" + << " values = " << value_utilization << " %\n"; + } + { + auto value_capacity = m * inner_node_w_value_count; + auto child_capacity = m * inner_node_w_child_count; + auto child_utilization = 100. * child_count / child_capacity; + auto value_utilization = 100. * value_count / value_capacity; + auto utilization = + 100. * (value_count * value_size + child_count * child_size) / + (value_capacity * value_size + child_capacity * child_size); + std::cerr << "utilization (dense)\n" + << " total = " << utilization << " %\n" + << " children = " << child_utilization << " %\n" + << " values = " << value_utilization << " %\n"; + } + } +}; +#endif + template ) { + ++stats.inner_node_count; + stats.inner_node_w_value_count += node->data_count() > 0; + stats.inner_node_w_child_count += node->children_count() > 0; + stats.value_count += node->data_count(); + stats.child_count += node->children_count(); + auto nodemap = node->nodemap(); + if (nodemap) { + auto fst = node->children(); + auto lst = fst + node->children_count(); + for (; fst != lst; ++fst) + do_get_debug_stats(stats, *fst, depth + 1); + } + } else { + ++stats.collision_node_count; + stats.collision_count += node->collision_count(); + } + } + + champ_debug_stats get_debug_stats() const + { + auto stats = champ_debug_stats{B, sizeof(T)}; + do_get_debug_stats(stats, root, 0); + return stats; + } +#endif + template static auto from_initializer_list(std::initializer_list values) { From bd1f0c00c08adaa576a9acb450aeb0156b812f42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Tue, 5 Jul 2022 00:08:24 +0200 Subject: [PATCH 031/104] Improve debug stats API --- immer/detail/hamts/champ.hpp | 104 ++++++++++++++++++++++++----------- test/map/default.cpp | 2 + test/map/generic.ipp | 5 ++ 3 files changed, 80 insertions(+), 31 deletions(-) diff --git a/immer/detail/hamts/champ.hpp b/immer/detail/hamts/champ.hpp index 455f8a78..68d3c3d9 100644 --- a/immer/detail/hamts/champ.hpp +++ b/immer/detail/hamts/champ.hpp @@ -33,40 +33,82 @@ struct champ_debug_stats std::size_t value_count = {}; std::size_t collision_count = {}; - void print() const + friend champ_debug_stats operator+(champ_debug_stats a, champ_debug_stats b) { - auto m = std::size_t{1} << bits; - std::cerr << "---\n"; - { - std::cerr << "collisions\n" - << " ratio = " << (100. * collision_count / value_count) - << " %\n"; - } - { - auto capacity = m * inner_node_count; - auto child_utilization = 100. * child_count / capacity; - auto value_utilization = 100. * value_count / capacity; - auto utilization = - 100. * (value_count * value_size + child_count * child_size) / - (capacity * value_size + capacity * child_size); - std::cerr << "utilization\n" - << " total = " << utilization << " %\n" - << " children = " << child_utilization << " %\n" - << " values = " << value_utilization << " %\n"; - } + if (a.bits != b.bits || a.value_size != b.value_size || + a.child_size != b.child_size) + throw std::runtime_error{"accumulating incompatible stats"}; + return { + a.bits, + a.value_size, + a.child_size, + a.inner_node_count + b.inner_node_count, + a.inner_node_w_value_count + b.inner_node_w_value_count, + a.inner_node_w_child_count + b.inner_node_w_child_count, + a.collision_node_count + b.collision_node_count, + a.child_count + b.child_count, + a.value_count + b.value_count, + a.collision_count + b.collision_count, + }; + } + + struct summary + { + double collision_ratio; + + double utilization; + double child_utilization; + double value_utilization; + + double dense_utilization; + double dense_value_utilization; + double dense_child_utilization; + + friend std::ostream& operator<<(std::ostream& os, const summary& s) { - auto value_capacity = m * inner_node_w_value_count; - auto child_capacity = m * inner_node_w_child_count; - auto child_utilization = 100. * child_count / child_capacity; - auto value_utilization = 100. * value_count / value_capacity; - auto utilization = - 100. * (value_count * value_size + child_count * child_size) / - (value_capacity * value_size + child_capacity * child_size); - std::cerr << "utilization (dense)\n" - << " total = " << utilization << " %\n" - << " children = " << child_utilization << " %\n" - << " values = " << value_utilization << " %\n"; + os << "---\n"; + os << "collisions\n" + << " ratio = " << s.collision_ratio << " %\n"; + os << "utilization\n" + << " total = " << s.utilization << " %\n" + << " children = " << s.child_utilization << " %\n" + << " values = " << s.value_utilization << " %\n"; + os << "utilization (dense)\n" + << " total = " << s.dense_utilization << " %\n" + << " children = " << s.dense_child_utilization << " %\n" + << " values = " << s.dense_value_utilization << " %\n"; + return os; } + }; + + summary get_summary() const + { + auto m = std::size_t{1} << bits; + + auto collision_ratio = 100. * collision_count / value_count; + + auto capacity = m * inner_node_count; + auto child_utilization = 100. * child_count / capacity; + auto value_utilization = 100. * value_count / capacity; + auto utilization = + 100. * (value_count * value_size + child_count * child_size) / + (capacity * value_size + capacity * child_size); + + auto value_capacity = m * inner_node_w_value_count; + auto child_capacity = m * inner_node_w_child_count; + auto dense_child_utilization = 100. * child_count / child_capacity; + auto dense_value_utilization = 100. * value_count / value_capacity; + auto dense_utilization = + 100. * (value_count * value_size + child_count * child_size) / + (value_capacity * value_size + child_capacity * child_size); + + return {collision_ratio, + utilization, + child_utilization, + value_utilization, + dense_utilization, + dense_child_utilization, + dense_value_utilization}; } }; #endif diff --git a/test/map/default.cpp b/test/map/default.cpp index cb453c4f..71567c23 100644 --- a/test/map/default.cpp +++ b/test/map/default.cpp @@ -6,6 +6,8 @@ // See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt // +#define IMMER_DEBUG_STATS 1 + #include #define MAP_T ::immer::map diff --git a/test/map/generic.ipp b/test/map/generic.ipp index 713a981b..61db78f5 100644 --- a/test/map/generic.ipp +++ b/test/map/generic.ipp @@ -165,6 +165,11 @@ TEST_CASE("equals and setting") CHECK(v.set(1234, 42) == v.insert({1234, 42})); CHECK(v.update(1234, [](auto&& x) { return x + 1; }) == v.set(1234, 1)); CHECK(v.update(42, [](auto&& x) { return x + 1; }) == v.set(42, 43)); + +#if IMMER_DEBUG_STATS + std::cout << (v.impl().get_debug_stats() + v.impl().get_debug_stats()) + .get_summary(); +#endif } TEST_CASE("iterator") From f3cebc63a1537fa3cd28ce60ae4ac726eaddd96c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Tue, 5 Jul 2022 11:23:35 +0200 Subject: [PATCH 032/104] Fix table and table_transient update() --- immer/table.hpp | 48 ++++++++++++++++++++++++-------- immer/table_transient.hpp | 14 ++++++---- test/table/generic.ipp | 29 ++++++++++++------- test/table_transient/generic.ipp | 18 ++++++++++-- 4 files changed, 79 insertions(+), 30 deletions(-) diff --git a/immer/table.hpp b/immer/table.hpp index 0a5a3efa..e11ecf46 100644 --- a/immer/table.hpp +++ b/immer/table.hpp @@ -21,11 +21,22 @@ class table_transient; * It assumes the key is `id` class member. */ template -auto get_table_key(const T& x) -> decltype(x.id) +auto get_table_key(T const& x) -> decltype(x.id) { return x.id; } +/*! + * Function template to set the key in `immer::table_key_fn`. + * It assumes the key is `id` class member. + */ +template +auto set_table_key(T x, K&& k) -> T +{ + x.id = std::forward(k); + return x; +} + /*! * Default value for `KeyFn` in `immer::table`. * It assumes the key is `id` class member. @@ -33,9 +44,15 @@ auto get_table_key(const T& x) -> decltype(x.id) struct table_key_fn { template - decltype(auto) operator()(const T& x) const + decltype(auto) operator()(T&& x) const { - return get_table_key(x); + return get_table_key(std::forward(x)); + } + + template + auto operator()(T&& x, K&& k) const + { + return set_table_key(std::forward(x), std::forward(k)); } }; @@ -108,9 +125,9 @@ class table struct combine_value { template - value_t operator()(Kf&& k, Tf&& v) const + auto operator()(Kf&& k, Tf&& v) const { - return std::forward(v); + return KeyFn{}(std::forward(v), std::forward(k)); } }; @@ -133,10 +150,13 @@ class table struct hash_key { - auto operator()(const value_t& v) { return Hash{}(KeyFn{}(v)); } + std::size_t operator()(const value_t& v) const + { + return Hash{}(KeyFn{}(v)); + } template - auto operator()(const Key& v) + std::size_t operator()(const Key& v) const { return Hash{}(v); } @@ -144,14 +164,14 @@ class table struct equal_key { - auto operator()(const value_t& a, const value_t& b) + bool operator()(const value_t& a, const value_t& b) const { auto ke = KeyFn{}; return Equal{}(ke(a), ke(b)); } template - auto operator()(const value_t& a, const Key& b) + bool operator()(const value_t& a, const Key& b) const { return Equal{}(KeyFn{}(a), b); } @@ -159,7 +179,10 @@ class table struct equal_value { - auto operator()(const value_t& a, const value_t& b) { return a == b; } + bool operator()(const value_t& a, const value_t& b) const + { + return a == b; + } }; using impl_t = @@ -386,8 +409,9 @@ class table /*! * Returns `this->insert(fn((*this)[k]))`. In particular, `fn` maps - * `T` to `T`. The `fn` return value should have key `k`. - * It may allocate memory and its complexity is *effectively* @f$ O(1) @f$. + * `T` to `T`. The key `k` will be replaced inside the value returned by + * `fn`. It may allocate memory and its complexity is *effectively* @f$ O(1) + * @f$. */ template IMMER_NODISCARD table update(key_type k, Fn&& fn) const& diff --git a/immer/table_transient.hpp b/immer/table_transient.hpp index 9dc7ce46..01d35056 100644 --- a/immer/table_transient.hpp +++ b/immer/table_transient.hpp @@ -213,16 +213,18 @@ class table_transient : MemoryPolicy::transience_t::owner void insert(value_type value) { impl_.add_mut(*this, std::move(value)); } /*! - * Returns `this->insert(fn((*this)[k]))`. In particular, `fn` maps - * `T` to `T`. The `fn` return value should have key `k`. - * It may allocate memory and its complexity is *effectively* @f$ O(1) @f$. + * Returns `this->insert(fn((*this)[k]))`. In particular, `fn` maps `T` to + * `T`. The key `k` will be set into the value returned bu `fn`. It may + * allocate memory and its complexity is *effectively* @f$ O(1) @f$. */ template void update(key_type k, Fn&& fn) { - impl_ = impl_.template update( + impl_.template update_mut( + *this, std::move(k), std::forward(fn)); + } *this, std::move(k), std::forward(fn)); } diff --git a/test/table/generic.ipp b/test/table/generic.ipp index 40322408..4cd04b53 100644 --- a/test/table/generic.ipp +++ b/test/table/generic.ipp @@ -31,11 +31,25 @@ struct pair_key_fn return p.first; } + template + auto operator()(std::pair p, F k) const + { + p.first = std::move(k); + return p; + } + template F operator()(const dadaist>& p) const { return p.value.first; } + + template + auto operator()(dadaist> p, F k) const + { + p.value.first = std::move(k); + return p; + } }; template Date: Tue, 5 Jul 2022 11:23:59 +0200 Subject: [PATCH 033/104] Add update_if_exists method to map and table and related transients --- extra/fuzzer/map-gc.cpp | 14 +++ extra/fuzzer/map.cpp | 14 +++ immer/detail/hamts/champ.hpp | 175 +++++++++++++++++++++++++++++++ immer/map.hpp | 33 ++++++ immer/map_transient.hpp | 15 +++ immer/table.hpp | 40 +++++-- immer/table_transient.hpp | 13 +++ test/map/generic.ipp | 92 ++++++++++++++++ test/map_transient/generic.ipp | 8 ++ test/table/generic.ipp | 14 +++ test/table_transient/generic.ipp | 7 ++ 11 files changed, 417 insertions(+), 8 deletions(-) diff --git a/extra/fuzzer/map-gc.cpp b/extra/fuzzer/map-gc.cpp index c4028df8..556df956 100644 --- a/extra/fuzzer/map-gc.cpp +++ b/extra/fuzzer/map-gc.cpp @@ -53,6 +53,8 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, op_find, op_update, op_update_move, + op_update_if_exists, + op_update_if_exists_move, op_diff }; auto src = read(in, is_valid_var); @@ -104,6 +106,18 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, std::move(vars[src]).update(key, [](int x) { return x + 1; }); break; } + case op_update_if_exists: { + auto key = read(in); + vars[dst] = + vars[src].update_if_exists(key, [](int x) { return x + 1; }); + break; + } + case op_update_if_exists_move: { + auto key = read(in); + vars[dst] = std::move(vars[src]).update_if_exists( + key, [](int x) { return x + 1; }); + break; + } case op_diff: { auto&& a = vars[src]; auto&& b = vars[dst]; diff --git a/extra/fuzzer/map.cpp b/extra/fuzzer/map.cpp index 5fe571d0..e5d4ce33 100644 --- a/extra/fuzzer/map.cpp +++ b/extra/fuzzer/map.cpp @@ -41,6 +41,8 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, op_find, op_update, op_update_move, + op_update_if_exists, + op_update_if_exists_move, op_diff, }; auto src = read(in, is_valid_var); @@ -92,6 +94,18 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, std::move(vars[src]).update(key, [](int x) { return x + 1; }); break; } + case op_update_if_exists: { + auto key = read(in); + vars[dst] = + vars[src].update_if_exists(key, [](int x) { return x + 1; }); + break; + } + case op_update_if_exists_move: { + auto key = read(in); + vars[dst] = std::move(vars[src]).update_if_exists( + key, [](int x) { return x + 1; }); + break; + } case op_diff: { auto&& a = vars[src]; auto&& b = vars[dst]; diff --git a/immer/detail/hamts/champ.hpp b/immer/detail/hamts/champ.hpp index 68d3c3d9..c40c065a 100644 --- a/immer/detail/hamts/champ.hpp +++ b/immer/detail/hamts/champ.hpp @@ -776,6 +776,72 @@ struct champ return {res.node, new_size}; } + template + node_t* do_update_if_exists( + node_t* node, K&& k, Fn&& fn, hash_t hash, shift_t shift) const + { + if (shift == max_shift) { + auto fst = node->collisions(); + auto lst = fst + node->collision_count(); + for (; fst != lst; ++fst) + if (Equal{}(*fst, k)) + return node_t::copy_collision_replace( + node, + fst, + Combine{}(std::forward(k), + std::forward(fn)(Project{}(*fst)))); + return nullptr; + } else { + auto idx = (hash & (mask << shift)) >> shift; + auto bit = bitmap_t{1u} << idx; + if (node->nodemap() & bit) { + auto offset = node->children_count(bit); + auto result = do_update_if_exists( + node->children()[offset], + k, + std::forward(fn), + hash, + shift + B); + IMMER_TRY { + return result ? node_t::copy_inner_replace( + node, offset, result) + : nullptr; + } + IMMER_CATCH (...) { + node_t::delete_deep_shift(result, shift + B); + IMMER_RETHROW; + } + } else if (node->datamap() & bit) { + auto offset = node->data_count(bit); + auto val = node->values() + offset; + if (Equal{}(*val, k)) + return node_t::copy_inner_replace_value( + node, + offset, + Combine{}(std::forward(k), + std::forward(fn)(Project{}(*val)))); + else { + return nullptr; + } + } else { + return nullptr; + } + } + } + + template + champ update_if_exists(const K& k, Fn&& fn) const + { + auto hash = Hash{}(k); + auto res = do_update_if_exists( + root, k, std::forward(fn), hash, 0); + if (res) { + return {res, size}; + } else { + return {root->inc(), size}; + }; + } + using update_mut_result = add_mut_result; template + update_if_exists_mut_result do_update_if_exists_mut(edit_t e, + node_t* node, + K&& k, + Fn&& fn, + hash_t hash, + shift_t shift) const + { + if (shift == max_shift) { + auto fst = node->collisions(); + auto lst = fst + node->collision_count(); + for (; fst != lst; ++fst) + if (Equal{}(*fst, k)) { + if (node->can_mutate(e)) { + *fst = Combine{}( + std::forward(k), + std::forward(fn)(Project{}(std::move(*fst)))); + return {node, true}; + } else { + auto r = node_t::copy_collision_replace( + node, + fst, + Combine{}(std::forward(k), + std::forward(fn)(Project{}(*fst)))); + return {node_t::owned(r, e), false}; + } + } + return {nullptr, false}; + } else { + auto idx = (hash & (mask << shift)) >> shift; + auto bit = bitmap_t{1u} << idx; + if (node->nodemap() & bit) { + auto offset = node->children_count(bit); + auto child = node->children()[offset]; + if (node->can_mutate(e)) { + auto result = do_update_if_exists_mut( + e, child, k, std::forward(fn), hash, shift + B); + if (result.node) { + node->children()[offset] = result.node; + if (!result.mutated) + child->dec_unsafe(); + return {node, true}; + } else { + return {nullptr, false}; + } + } else { + auto result = do_update_if_exists( + child, k, std::forward(fn), hash, shift + B); + IMMER_TRY { + if (result) { + result = node_t::copy_inner_replace( + node, offset, result); + node_t::owned(result, e); + return {result, false}; + } else { + return {nullptr, false}; + } + } + IMMER_CATCH (...) { + node_t::delete_deep_shift(result, shift + B); + IMMER_RETHROW; + } + } + } else if (node->datamap() & bit) { + auto offset = node->data_count(bit); + auto val = node->values() + offset; + if (Equal{}(*val, k)) { + if (node->can_mutate(e)) { + auto vals = node->ensure_mutable_values(e); + vals[offset] = Combine{}(std::forward(k), + std::forward(fn)(Project{}( + std::move(vals[offset])))); + return {node, true}; + } else { + auto r = node_t::copy_inner_replace_value( + node, + offset, + Combine{}(std::forward(k), + std::forward(fn)(Project{}(*val)))); + return {node_t::owned_values(r, e), false}; + } + } else { + return {nullptr, false}; + } + } else { + return {nullptr, false}; + } + } + } + + template + void update_if_exists_mut(edit_t e, const K& k, Fn&& fn) + { + auto hash = Hash{}(k); + auto res = do_update_if_exists_mut( + e, root, k, std::forward(fn), hash, 0); + if (res.node) { + if (!res.mutated) + root->dec_unsafe(); + root = res.node; + } + } + // basically: // variant // boo bad we are not using... C++17 :'( diff --git a/immer/map.hpp b/immer/map.hpp index 94ac2791..790913a7 100644 --- a/immer/map.hpp +++ b/immer/map.hpp @@ -422,6 +422,25 @@ class map return update_move(move_t{}, std::move(k), std::forward(fn)); } + /*! + * Returns a map replacing the association `(k, v)` by the association new + * association `(k, fn(v))`, where `v` is the currently associated value for + * `k` in the map. It does nothing if `k` is not present in the map. It + * may allocate memory and its complexity is *effectively* @f$ O(1) @f$. + */ + template + IMMER_NODISCARD map update_if_exists(key_type k, Fn&& fn) const& + { + return impl_.template update_if_exists( + std::move(k), std::forward(fn)); + } + template + IMMER_NODISCARD decltype(auto) update_if_exists(key_type k, Fn&& fn) && + { + return update_if_exists_move( + move_t{}, std::move(k), std::forward(fn)); + } + /*! * Returns a map without the key `k`. If the key is not * associated in the map it returns the same map. It may allocate @@ -487,6 +506,20 @@ class map std::move(k), std::forward(fn)); } + template + map&& update_if_exists_move(std::true_type, key_type k, Fn&& fn) + { + impl_.template update_if_exists_mut( + {}, std::move(k), std::forward(fn)); + return std::move(*this); + } + template + map update_if_exists_move(std::false_type, key_type k, Fn&& fn) + { + return impl_.template update_if_exists( + std::move(k), std::forward(fn)); + } + map&& erase_move(std::true_type, const key_type& value) { impl_.sub_mut({}, value); diff --git a/immer/map_transient.hpp b/immer/map_transient.hpp index f8b99a75..935f48e2 100644 --- a/immer/map_transient.hpp +++ b/immer/map_transient.hpp @@ -267,6 +267,21 @@ class map_transient : MemoryPolicy::transience_t::owner *this, std::move(k), std::forward(fn)); } + /*! + * Replaces the association `(k, v)` by the association new association `(k, + * fn(v))`, where `v` is the currently associated value for `k` in the map + * or does nothing if `k` is not present in the map. It may allocate memory + * and its complexity is *effectively* @f$ O(1) @f$. + */ + template + void update_if_exists(key_type k, Fn&& fn) + { + impl_.template update_if_exists_mut< + typename persistent_type::project_value, + typename persistent_type::combine_value>( + *this, std::move(k), std::forward(fn)); + } + /*! * Removes the key `k` from the k. Does nothing if the key is not * associated in the map. It may allocate memory and its complexity is diff --git a/immer/table.hpp b/immer/table.hpp index e11ecf46..7dae1945 100644 --- a/immer/table.hpp +++ b/immer/table.hpp @@ -420,16 +420,29 @@ class table .template update( std::move(k), std::forward(fn)); } + template + IMMER_NODISCARD decltype(auto) update(key_type k, Fn&& fn) && + { + return update_move(move_t{}, std::move(k), std::forward(fn)); + } /*! - * Returns `this->insert(fn((*this)[k]))`. In particular, `fn` maps - * `T` to `T`. The `fn` return value should have key `k`. - * It may allocate memory and its complexity is *effectively* @f$ O(1) @f$. + * Returns `this.count(k) ? this->insert(fn((*this)[k])) : *this`. In + * particular, `fn` maps `T` to `T`. The key `k` will be replaced inside the + * value returned by `fn`. It may allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. */ template - IMMER_NODISCARD decltype(auto) update(key_type k, Fn&& fn) && + IMMER_NODISCARD table update_if_exists(key_type k, Fn&& fn) const& { - return update_move(move_t{}, std::move(k), std::forward(fn)); + return impl_.template update_if_exists( + std::move(k), std::forward(fn)); + } + template + IMMER_NODISCARD decltype(auto) update_if_exists(key_type k, Fn&& fn) && + { + return update_if_exists_move( + move_t{}, std::move(k), std::forward(fn)); } /*! @@ -478,7 +491,6 @@ class table impl_.add_mut({}, std::move(value)); return std::move(*this); } - table insert_move(std::false_type, value_type value) { return impl_.add(std::move(value)); @@ -491,7 +503,6 @@ class table {}, std::move(k), std::forward(fn)); return std::move(*this); } - template table update_move(std::false_type, key_type k, Fn&& fn) { @@ -500,12 +511,25 @@ class table std::move(k), std::forward(fn)); } + template + table&& update_if_exists_move(std::true_type, key_type k, Fn&& fn) + { + impl_.template update_if_exists_mut( + {}, std::move(k), std::forward(fn)); + return std::move(*this); + } + template + table update_if_exists_move(std::false_type, key_type k, Fn&& fn) + { + return impl_.template update_if_exists( + std::move(k), std::forward(fn)); + } + table&& erase_move(std::true_type, const key_type& value) { impl_.sub_mut({}, value); return std::move(*this); } - table erase_move(std::false_type, const key_type& value) { return impl_.sub(value); diff --git a/immer/table_transient.hpp b/immer/table_transient.hpp index 01d35056..30b0a8c6 100644 --- a/immer/table_transient.hpp +++ b/immer/table_transient.hpp @@ -225,6 +225,19 @@ class table_transient : MemoryPolicy::transience_t::owner typename persistent_type::combine_value>( *this, std::move(k), std::forward(fn)); } + + /*! + * Returns `this->insert(fn((*this)[k]))` when `this->count(k) > 0`. In + * particular, `fn` maps `T` to `T`. The key `k` will be replaced into the + * value returned by `fn`. It may allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. + */ + template + void update_if_exists(key_type k, Fn&& fn) + { + impl_.template update_if_exists_mut< + typename persistent_type::project_value, + typename persistent_type::combine_value>( *this, std::move(k), std::forward(fn)); } diff --git a/test/map/generic.ipp b/test/map/generic.ipp index 61db78f5..a594f44c 100644 --- a/test/map/generic.ipp +++ b/test/map/generic.ipp @@ -166,6 +166,10 @@ TEST_CASE("equals and setting") CHECK(v.update(1234, [](auto&& x) { return x + 1; }) == v.set(1234, 1)); CHECK(v.update(42, [](auto&& x) { return x + 1; }) == v.set(42, 43)); + CHECK(v.update_if_exists(1234, [](auto&& x) { return x + 1; }) == v); + CHECK(v.update_if_exists(42, [](auto&& x) { return x + 1; }) == + v.set(42, 43)); + #if IMMER_DEBUG_STATS std::cout << (v.impl().get_debug_stats() + v.impl().get_debug_stats()) .get_summary(); @@ -259,6 +263,27 @@ TEST_CASE("update a lot") } } +TEST_CASE("update_if_exists a lot") +{ + auto v = make_test_map(666u); + + SECTION("immutable") + { + for (decltype(v.size()) i = 0; i < v.size(); ++i) { + v = v.update_if_exists(i, [](auto&& x) { return x + 1; }); + CHECK(v[i] == i + 1); + } + } + SECTION("move") + { + for (decltype(v.size()) i = 0; i < v.size(); ++i) { + v = std::move(v).update_if_exists(i, + [](auto&& x) { return x + 1; }); + CHECK(v[i] == i + 1); + } + } +} + TEST_CASE("exception safety") { constexpr auto n = 2666u; @@ -289,6 +314,27 @@ TEST_CASE("exception safety") IMMER_TRACE_E(d.happenings); } + SECTION("update_if_exists") + { + auto v = dadaist_map_t{}; + auto d = dadaism{}; + for (auto i = 0u; i < n; ++i) + v = std::move(v).set(i, i); + for (auto i = 0u; i < v.size();) { + try { + auto s = d.next(); + v = v.update_if_exists(i, [](auto x) { return x + 1; }); + ++i; + } catch (dada_error) {} + for (auto i : test_irange(0u, i)) + CHECK(v.at(i) == i + 1); + for (auto i : test_irange(i, n)) + CHECK(v.at(i) == i); + } + CHECK(d.happenings > 0); + IMMER_TRACE_E(d.happenings); + } + SECTION("update collisisions") { auto vals = make_values_with_collisions(n); @@ -311,6 +357,29 @@ TEST_CASE("exception safety") IMMER_TRACE_E(d.happenings); } + SECTION("update_if_exists collisisions") + { + auto vals = make_values_with_collisions(n); + auto v = dadaist_conflictor_map_t{}; + auto d = dadaism{}; + for (auto i = 0u; i < n; ++i) + v = v.insert(vals[i]); + for (auto i = 0u; i < v.size();) { + try { + auto s = d.next(); + v = v.update_if_exists(vals[i].first, + [](auto x) { return x + 1; }); + ++i; + } catch (dada_error) {} + for (auto i : test_irange(0u, i)) + CHECK(v.at(vals[i].first) == vals[i].second + 1); + for (auto i : test_irange(i, n)) + CHECK(v.at(vals[i].first) == vals[i].second); + } + CHECK(d.happenings > 0); + IMMER_TRACE_E(d.happenings); + } + SECTION("set collisisions") { auto vals = make_values_with_collisions(n); @@ -380,6 +449,29 @@ TEST_CASE("exception safety") IMMER_TRACE_E(d.happenings); } + SECTION("update_if_exists collisisions move") + { + auto vals = make_values_with_collisions(n); + auto v = dadaist_conflictor_map_t{}; + auto d = dadaism{}; + for (auto i = 0u; i < n; ++i) + v = std::move(v).insert(vals[i]); + for (auto i = 0u; i < v.size();) { + try { + auto s = d.next(); + v = std::move(v).update_if_exists(vals[i].first, + [](auto x) { return x + 1; }); + ++i; + } catch (dada_error) {} + for (auto i : test_irange(0u, i)) + CHECK(v.at(vals[i].first) == vals[i].second + 1); + for (auto i : test_irange(i, n)) + CHECK(v.at(vals[i].first) == vals[i].second); + } + CHECK(d.happenings > 0); + IMMER_TRACE_E(d.happenings); + } + SECTION("erase collisisions move") { auto vals = make_values_with_collisions(n); diff --git a/test/map_transient/generic.ipp b/test/map_transient/generic.ipp index c4cb25dc..3e20abed 100644 --- a/test/map_transient/generic.ipp +++ b/test/map_transient/generic.ipp @@ -87,6 +87,14 @@ TEST_CASE("update") t.update("foo", [](auto x) { return x + 6; }); CHECK(t["foo"] == 48); CHECK(t.size() == 2); + + t.update_if_exists("foo", [](auto x) { return x + 42; }); + CHECK(t["foo"] == 90); + CHECK(t.size() == 2); + + t.update_if_exists("manolo", [](auto x) { return x + 42; }); + CHECK(t["manolo"] == 0); + CHECK(t.size() == 2); } TEST_CASE("erase") diff --git a/test/table/generic.ipp b/test/table/generic.ipp index 4cd04b53..b9a5aa67 100644 --- a/test/table/generic.ipp +++ b/test/table/generic.ipp @@ -282,6 +282,20 @@ TEST_CASE("update a lot") CHECK(v[i].second == i + 1); } } + SECTION("if_exists immutable") + { + for (decltype(v.size()) i = 0; i < v.size(); ++i) { + v = v.update_if_exists(i, incr_id); + CHECK(v[i].second == i + 1); + } + } + SECTION("if_exists move") + { + for (decltype(v.size()) i = 0; i < v.size(); ++i) { + v = std::move(v).update_if_exists(i, incr_id); + CHECK(v[i].second == i + 1); + } + } } TEST_CASE("exception safety") diff --git a/test/table_transient/generic.ipp b/test/table_transient/generic.ipp index fd9d4fc5..e0dd41a3 100644 --- a/test/table_transient/generic.ipp +++ b/test/table_transient/generic.ipp @@ -76,6 +76,13 @@ TEST_CASE("insert") }); CHECK(t["lol"].value == 1); CHECK(t.size() == 3); + + t.update_if_exists("foo", [](auto item) { + item.value += 1; + return item; + }); + CHECK(t["foo"].value == 8); + CHECK(t.size() == 3); } TEST_CASE("erase") From 34deab878183217064f813c41eb6c76be60cd243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Fri, 15 Jul 2022 12:16:50 +0200 Subject: [PATCH 034/104] Fix iteration over data-structures would sometimes allow mutation! --- immer/algorithm.hpp | 28 +++- immer/detail/hamts/champ.hpp | 64 +++++---- immer/detail/rbts/operations.hpp | 4 +- immer/detail/util.hpp | 15 ++- test/algorithm.cpp | 221 +++++++++++++++++++++++++++++++ 5 files changed, 296 insertions(+), 36 deletions(-) create mode 100644 test/algorithm.cpp diff --git a/immer/algorithm.hpp b/immer/algorithm.hpp index ae43d2e2..382df02d 100644 --- a/immer/algorithm.hpp +++ b/immer/algorithm.hpp @@ -93,6 +93,26 @@ bool for_each_chunk_p(const T* first, const T* last, Fn&& fn) return std::forward(fn)(first, last); } +namespace detail { + +template +T accumulate_move(Iter first, Iter last, T init) +{ + for (; first != last; ++first) + init = std::move(init) + *first; + return init; +} + +template +T accumulate_move(Iter first, Iter last, T init, Fn op) +{ + for (; first != last; ++first) + init = op(std::move(init), *first); + return init; +} + +} // namespace detail + /*! * Equivalent of `std::accumulate` applied to the range `r`. */ @@ -100,7 +120,7 @@ template T accumulate(Range&& r, T init) { for_each_chunk(r, [&](auto first, auto last) { - init = std::accumulate(first, last, init); + init = detail::accumulate_move(first, last, init); }); return init; } @@ -109,7 +129,7 @@ template T accumulate(Range&& r, T init, Fn fn) { for_each_chunk(r, [&](auto first, auto last) { - init = std::accumulate(first, last, init, fn); + init = detail::accumulate_move(first, last, init, fn); }); return init; } @@ -122,7 +142,7 @@ template T accumulate(Iterator first, Iterator last, T init) { for_each_chunk(first, last, [&](auto first, auto last) { - init = std::accumulate(first, last, init); + init = detail::accumulate_move(first, last, init); }); return init; } @@ -131,7 +151,7 @@ template T accumulate(Iterator first, Iterator last, T init, Fn fn) { for_each_chunk(first, last, [&](auto first, auto last) { - init = std::accumulate(first, last, init, fn); + init = detail::accumulate_move(first, last, init, fn); }); return init; } diff --git a/immer/detail/hamts/champ.hpp b/immer/detail/hamts/champ.hpp index c40c065a..a3e95ec3 100644 --- a/immer/detail/hamts/champ.hpp +++ b/immer/detail/hamts/champ.hpp @@ -246,7 +246,8 @@ struct champ } template - void for_each_chunk_traversal(node_t* node, count_t depth, Fn&& fn) const + void + for_each_chunk_traversal(const node_t* node, count_t depth, Fn&& fn) const { if (depth < max_depth) { auto datamap = node->datamap(); @@ -272,8 +273,8 @@ struct champ } template - void diff(node_t* old_node, - node_t* new_node, + void diff(const node_t* old_node, + const node_t* new_node, count_t depth, Differ&& differ) const { @@ -350,8 +351,8 @@ struct champ } template - void diff_data_node(node_t* old_node, - node_t* new_node, + void diff_data_node(const node_t* old_node, + const node_t* new_node, bitmap_t bit, count_t depth, Differ&& differ) const @@ -379,8 +380,8 @@ struct champ } template - void diff_node_data(node_t* old_node, - node_t* new_node, + void diff_node_data(const node_t* old_node, + const node_t* const new_node, bitmap_t bit, count_t depth, Differ&& differ) const @@ -408,8 +409,8 @@ struct champ } template - void diff_data_data(node_t* old_node, - node_t* new_node, + void diff_data_data(const node_t* old_node, + const node_t* new_node, bitmap_t bit, Differ&& differ) const { @@ -427,8 +428,9 @@ struct champ } template - void - diff_collisions(node_t* old_node, node_t* new_node, Differ&& differ) const + void diff_collisions(const node_t* old_node, + const node_t* new_node, + Differ&& differ) const { auto old_begin = old_node->collisions(); auto old_end = old_node->collisions() + old_node->collision_count(); @@ -690,13 +692,13 @@ struct champ auto lst = fst + node->collision_count(); for (; fst != lst; ++fst) if (Equal{}(*fst, k)) - return { - node_t::copy_collision_replace( - node, - fst, - Combine{}(std::forward(k), - std::forward(fn)(Project{}(*fst)))), - false}; + return {node_t::copy_collision_replace( + node, + fst, + Combine{}(std::forward(k), + std::forward(fn)(Project{}( + detail::as_const(*fst))))), + false}; return {node_t::copy_collision_insert( node, Combine{}(std::forward(k), @@ -726,13 +728,13 @@ struct champ auto offset = node->data_count(bit); auto val = node->values() + offset; if (Equal{}(*val, k)) - return { - node_t::copy_inner_replace_value( - node, - offset, - Combine{}(std::forward(k), - std::forward(fn)(Project{}(*val)))), - false}; + return {node_t::copy_inner_replace_value( + node, + offset, + Combine{}(std::forward(k), + std::forward(fn)(Project{}( + detail::as_const(*val))))), + false}; else { auto child = node_t::make_merged( shift + B, @@ -789,7 +791,8 @@ struct champ node, fst, Combine{}(std::forward(k), - std::forward(fn)(Project{}(*fst)))); + std::forward(fn)( + Project{}(detail::as_const(*fst))))); return nullptr; } else { auto idx = (hash & (mask << shift)) >> shift; @@ -819,7 +822,8 @@ struct champ node, offset, Combine{}(std::forward(k), - std::forward(fn)(Project{}(*val)))); + std::forward(fn)( + Project{}(detail::as_const(*val))))); else { return nullptr; } @@ -871,7 +875,8 @@ struct champ node, fst, Combine{}(std::forward(k), - std::forward(fn)(Project{}(*fst)))); + std::forward(fn)( + Project{}(detail::as_const(*fst))))); return {node_t::owned(r, e), false, false}; } } @@ -923,7 +928,8 @@ struct champ node, offset, Combine{}(std::forward(k), - std::forward(fn)(Project{}(*val)))); + std::forward(fn)( + Project{}(detail::as_const(*val))))); return {node_t::owned_values(r, e), false, false}; } } else { diff --git a/immer/detail/rbts/operations.hpp b/immer/detail/rbts/operations.hpp index d92832b0..adaf2c0b 100644 --- a/immer/detail/rbts/operations.hpp +++ b/immer/detail/rbts/operations.hpp @@ -92,7 +92,7 @@ struct for_each_chunk_visitor : visitor_base static void visit_leaf(Pos&& pos, Fn&& fn) { auto data = pos.node()->leaf(); - fn(data, data + pos.count()); + fn(as_const(data), as_const(data) + pos.count()); } }; @@ -109,7 +109,7 @@ struct for_each_chunk_p_visitor : visitor_base template static bool visit_leaf(Pos&& pos, Fn&& fn) { - auto data = pos.node()->leaf(); + auto data = as_const(pos.node()->leaf()); return fn(data, data + pos.count()); } }; diff --git a/immer/detail/util.hpp b/immer/detail/util.hpp index df21fc3f..d6ae246b 100644 --- a/immer/detail/util.hpp +++ b/immer/detail/util.hpp @@ -24,6 +24,18 @@ namespace immer { namespace detail { +template +const T* as_const(T* x) +{ + return x; +} + +template +const T& as_const(T& x) +{ + return x; +} + template using aligned_storage_for = typename std::aligned_storage::type; @@ -113,7 +125,8 @@ auto uninitialized_move(Iter1 first, Iter1 last, Iter2 out) std::addressof(*current)))) value_t(std::move(*first)); } return current; - } IMMER_CATCH (...) { + } + IMMER_CATCH (...) { detail::destroy(out, current); IMMER_RETHROW; } diff --git a/test/algorithm.cpp b/test/algorithm.cpp new file mode 100644 index 00000000..de925209 --- /dev/null +++ b/test/algorithm.cpp @@ -0,0 +1,221 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include + +struct thing +{ + int id = 0; +}; +bool operator==(const thing& a, const thing& b) { return a.id == b.id; } + +TEST_CASE("iteration exposes const") +{ + auto do_check = [](auto v) { + using value_t = typename decltype(v)::value_type; + immer::for_each(v, [](auto&& x) { + static_assert(std::is_same::value, ""); + }); + }; + + do_check(immer::vector{}); + do_check(immer::flex_vector{}); + do_check(immer::array{}); + do_check(immer::map{}); + do_check(immer::set{}); + do_check(immer::table{}); +} + +TEST_CASE("chunked iteration exposes const") +{ + auto do_check = [](auto v) { + using value_t = typename decltype(v)::value_type; + immer::for_each_chunk(v, [](auto a, auto b) { + static_assert(std::is_same::value, ""); + static_assert(std::is_same::value, ""); + }); + }; + + do_check(immer::vector{}); + do_check(immer::flex_vector{}); + do_check(immer::array{}); + do_check(immer::map{}); + do_check(immer::set{}); + do_check(immer::table{}); +} + +TEST_CASE("accumulate") +{ + auto do_check = [](auto v) { + using value_t = typename decltype(v)::value_type; + immer::accumulate(v, value_t{}, [](auto&& a, auto&& b) { + static_assert(std::is_same::value, ""); + static_assert(std::is_same::value, ""); + return std::move(a); + }); + }; + + do_check(immer::vector{}); + do_check(immer::flex_vector{}); + do_check(immer::array{}); + do_check(immer::map{}); + do_check(immer::set{}); + do_check(immer::table{}); +} + +TEST_CASE("diffing exposes const") +{ + auto do_check = [](auto v) { + using value_t = typename decltype(v)::value_type; + immer::diff( + v, + v, + [](auto&& x) { + static_assert(std::is_same::value, + ""); + }, + [](auto&& x) { + static_assert(std::is_same::value, + ""); + }, + [](auto&& x, auto&& y) { + static_assert(std::is_same::value, + ""); + static_assert(std::is_same::value, + ""); + }); + }; + + do_check(immer::map{}); + do_check(immer::set{}); + do_check(immer::table{}); +} + +TEST_CASE("all_of") +{ + auto do_check = [](auto v) { + using value_t = typename decltype(v)::value_type; + immer::all_of(v, [](auto&& x) { + static_assert(std::is_same::value, ""); + return true; + }); + }; + + do_check(immer::vector{}); + do_check(immer::flex_vector{}); + do_check(immer::array{}); + // not supported + // do_check(immer::map{}); + // do_check(immer::set{}); + // do_check(immer::table{}); +} + +TEST_CASE("update vectors") +{ + auto do_check = [](auto v) { + if (false) + (void) v.update(0, [](auto&& x) { + using type_t = std::decay_t; + // vectors do copy first the whole array, and then move the + // copied value into the function + static_assert(std::is_same::value, ""); + return x; + }); + }; + + do_check(immer::vector{}); + do_check(immer::flex_vector{}); + do_check(immer::array{}); +} + +TEST_CASE("update maps") +{ + auto do_check = [](auto v) { + (void) v.update(0, [](auto&& x) { + using type_t = std::decay_t; + // for maps, we actually do not make a copy at all but pase the + // original instance directly, as const.. + static_assert(std::is_same::value, ""); + return x; + }); + }; + + do_check(immer::map{}); + do_check(immer::table{}); +} + +TEST_CASE("update_if_exists maps") +{ + auto do_check = [](auto v) { + (void) v.update_if_exists(0, [](auto&& x) { + using type_t = std::decay_t; + // for maps, we actually do not make a copy at all but pase the + // original instance directly, as const.. + static_assert(std::is_same::value, ""); + return x; + }); + }; + + do_check(immer::map{}); + do_check(immer::table{}); +} + +TEST_CASE("update vectors move") +{ + auto do_check = [](auto v) { + if (false) + (void) std::move(v).update(0, [](auto&& x) { + using type_t = std::decay_t; + // vectors do copy first the whole array, and then move the + // copied value into the function + static_assert(std::is_same::value, ""); + return x; + }); + }; + + do_check(immer::vector{}); + do_check(immer::flex_vector{}); + do_check(immer::array{}); +} + +TEST_CASE("update maps move") +{ + auto do_check = [](auto v) { + (void) std::move(v).update(0, [](auto&& x) { + using type_t = std::decay_t; + // for maps, we actually do not make a copy at all but pase the + // original instance directly, as const.. + static_assert(std::is_same::value || + std::is_same::value, + ""); + return x; + }); + }; + + do_check(immer::map{}); + do_check(immer::table{}); +} + +TEST_CASE("update_if_exists maps move") +{ + auto do_check = [](auto v) { + (void) std::move(v).update_if_exists(0, [](auto&& x) { + using type_t = std::decay_t; + // for maps, we actually do not make a copy at all but pase the + // original instance directly, as const.. + static_assert(std::is_same::value || + std::is_same::value, + ""); + return x; + }); + }; + + do_check(immer::map{}); + do_check(immer::table{}); +} From db3ea447ad0854b633dd0738e3d28315cd5f3f96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Thu, 14 Jul 2022 18:40:07 +0200 Subject: [PATCH 035/104] Fix sometimes HAMTs would move out values that it should not! --- immer/detail/hamts/champ.hpp | 34 ++++++----- immer/detail/hamts/node.hpp | 15 +++-- immer/map.hpp | 2 + immer/set.hpp | 2 + immer/table.hpp | 2 + test/map/gc.cpp | 1 + test/map/generic.ipp | 110 +++++++++++++++++++++++++++++++++ test/set/gc.cpp | 2 + test/set/generic.ipp | 115 ++++++++++++++++++++++++++++++++++- 9 files changed, 260 insertions(+), 23 deletions(-) diff --git a/immer/detail/hamts/champ.hpp b/immer/detail/hamts/champ.hpp index a3e95ec3..5bb093ed 100644 --- a/immer/detail/hamts/champ.hpp +++ b/immer/detail/hamts/champ.hpp @@ -635,15 +635,16 @@ struct champ return {node_t::owned_values(r, e), false, false}; } } else { - auto mutate = node->can_mutate(e); - auto hash2 = Hash{}(*val); - auto child = - node_t::make_merged_e(e, - shift + B, - std::move(v), - hash, - mutate ? std::move(*val) : *val, - hash2); + auto mutate = node->can_mutate(e); + auto mutate_values = mutate && node->can_mutate_values(e); + auto hash2 = Hash{}(*val); + auto child = node_t::make_merged_e( + e, + shift + B, + std::move(v), + hash, + mutate_values ? std::move(*val) : *val, + hash2); IMMER_TRY { auto r = mutate ? node_t::move_inner_replace_merged( e, node, bit, offset, child) @@ -933,15 +934,16 @@ struct champ return {node_t::owned_values(r, e), false, false}; } } else { - auto mutate = node->can_mutate(e); - auto hash2 = Hash{}(*val); - auto child = node_t::make_merged_e( + auto mutate = node->can_mutate(e); + auto mutate_values = mutate && node->can_mutate_values(e); + auto hash2 = Hash{}(*val); + auto child = node_t::make_merged_e( e, shift + B, Combine{}(std::forward(k), std::forward(fn)(Default{}())), hash, - mutate ? std::move(*val) : *val, + mutate_values ? std::move(*val) : *val, hash2); IMMER_TRY { auto r = mutate ? node_t::move_inner_replace_merged( @@ -1013,7 +1015,8 @@ struct champ node, fst, Combine{}(std::forward(k), - std::forward(fn)(Project{}(*fst)))); + std::forward(fn)( + Project{}(detail::as_const(*fst))))); return {node_t::owned(r, e), false}; } } @@ -1068,7 +1071,8 @@ struct champ node, offset, Combine{}(std::forward(k), - std::forward(fn)(Project{}(*val)))); + std::forward(fn)( + Project{}(detail::as_const(*val))))); return {node_t::owned_values(r, e), false}; } } else { diff --git a/immer/detail/hamts/node.hpp b/immer/detail/hamts/node.hpp index cb64005d..3a8ff9a3 100644 --- a/immer/detail/hamts/node.hpp +++ b/immer/detail/hamts/node.hpp @@ -212,6 +212,10 @@ struct node { return refs(this).unique() || ownee(this).can_mutate(e); } + bool can_mutate_values(edit_t e) const + { + return can_mutate(impl.d.data.inner.values, e); + } static node_t* make_inner_n(count_t n) { @@ -365,8 +369,7 @@ struct node deallocate_values(nxt, nv); IMMER_RETHROW; } - if (refs(old).dec()) - delete_values(old, nv); + refs(old).dec_unsafe(); impl.d.data.inner.values = nxt; return dst; } @@ -628,7 +631,7 @@ struct node dst->impl.d.data.inner.datamap = src->datamap() & ~bit; dst->impl.d.data.inner.nodemap = src->nodemap() | bit; if (nv > 1) { - auto mutate_values = can_mutate(dst->impl.d.data.inner.values, e); + auto mutate_values = can_mutate(src->impl.d.data.inner.values, e); IMMER_TRY { if (mutate_values) detail::uninitialized_move( @@ -731,7 +734,7 @@ struct node dst->impl.d.data.inner.datamap = src->datamap() | bit; IMMER_TRY { auto mutate_values = - nv && can_mutate(dst->impl.d.data.inner.values, e); + nv && can_mutate(src->impl.d.data.inner.values, e); if (nv) { if (mutate_values) detail::uninitialized_move( @@ -829,7 +832,7 @@ struct node dst->impl.d.data.inner.datamap = src->datamap() & ~bit; dst->impl.d.data.inner.nodemap = src->nodemap(); if (nv > 1) { - auto mutate_values = can_mutate(dst->impl.d.data.inner.values, e); + auto mutate_values = can_mutate(src->impl.d.data.inner.values, e); if (mutate_values) { IMMER_TRY { detail::uninitialized_move( @@ -925,7 +928,7 @@ struct node dst->impl.d.data.inner.nodemap = src->nodemap(); IMMER_TRY { auto mutate_values = - nv && can_mutate(dst->impl.d.data.inner.values, e); + nv && can_mutate(src->impl.d.data.inner.values, e); if (nv) { if (mutate_values) detail::uninitialized_move( diff --git a/immer/map.hpp b/immer/map.hpp index 790913a7..593f7dc5 100644 --- a/immer/map.hpp +++ b/immer/map.hpp @@ -170,6 +170,8 @@ class map using transient_type = map_transient; + using memory_policy_type = MemoryPolicy; + /*! * Constructs a map containing the elements in `values`. */ diff --git a/immer/set.hpp b/immer/set.hpp index a8199b22..73ccdcf8 100644 --- a/immer/set.hpp +++ b/immer/set.hpp @@ -85,6 +85,8 @@ class set using transient_type = set_transient; + using memory_policy_type = MemoryPolicy; + /*! * Default constructor. It creates a set of `size() == 0`. It * does not allocate memory and its complexity is @f$ O(1) @f$. diff --git a/immer/table.hpp b/immer/table.hpp index 7dae1945..0e977c6a 100644 --- a/immer/table.hpp +++ b/immer/table.hpp @@ -206,6 +206,8 @@ class table using transient_type = table_transient; + using memory_policy_type = MemoryPolicy; + /*! * Constructs a table containing the elements in `values`. */ diff --git a/test/map/gc.cpp b/test/map/gc.cpp index 9a4eaebd..26646ee2 100644 --- a/test/map/gc.cpp +++ b/test/map/gc.cpp @@ -23,4 +23,5 @@ template ; #define MAP_T test_map_t +#define IMMER_IS_LIBGC_TEST 1 #include "generic.ipp" diff --git a/test/map/generic.ipp b/test/map/generic.ipp index a594f44c..4aa162dd 100644 --- a/test/map/generic.ipp +++ b/test/map/generic.ipp @@ -13,6 +13,7 @@ #endif #include +#include #include "test/dada.hpp" #include "test/util.hpp" @@ -23,6 +24,8 @@ #include #include +using memory_policy_t = MAP_T::memory_policy_type; + template auto make_generator() { @@ -284,6 +287,113 @@ TEST_CASE("update_if_exists a lot") } } +#if !IMMER_IS_LIBGC_TEST +TEST_CASE("update boxed move string") +{ + constexpr auto N = 666u; + constexpr auto S = 7; + auto s = MAP_T>{}; + SECTION("preserve immutability") + { + auto s0 = s; + auto i0 = 0u; + // insert + for (auto i = 0u; i < N; ++i) { + if (i % S == 0) { + s0 = s; + i0 = i; + } + s = std::move(s).update(std::to_string(i), + [&](auto&&) { return std::to_string(i); }); + { + CHECK(s.size() == i + 1); + for (auto j : test_irange(0u, i + 1)) { + CHECK(s.count(std::to_string(j)) == 1); + CHECK(*s.find(std::to_string(j)) == std::to_string(j)); + } + for (auto j : test_irange(i + 1u, N)) + CHECK(s.count(std::to_string(j)) == 0); + } + { + CHECK(s0.size() == i0); + for (auto j : test_irange(0u, i0)) { + CHECK(s0.count(std::to_string(j)) == 1); + CHECK(*s0.find(std::to_string(j)) == std::to_string(j)); + } + for (auto j : test_irange(i0, N)) + CHECK(s0.count(std::to_string(j)) == 0); + } + } + // update + for (auto i = 0u; i < N; ++i) { + if (i % S == 0) { + s0 = s; + i0 = i; + } + s = std::move(s).update(std::to_string(i), [&](auto&&) { + return std::to_string(i + 1); + }); + { + CHECK(s.size() == N); + for (auto j : test_irange(0u, i + 1)) + CHECK(*s.find(std::to_string(j)) == std::to_string(j + 1)); + for (auto j : test_irange(i + 1u, N)) + CHECK(*s.find(std::to_string(j)) == std::to_string(j)); + } + { + CHECK(s0.size() == N); + for (auto j : test_irange(0u, i0)) + CHECK(*s0.find(std::to_string(j)) == std::to_string(j + 1)); + for (auto j : test_irange(i0, N)) + CHECK(*s0.find(std::to_string(j)) == std::to_string(j)); + } + } + } +} +#endif + +#if !IMMER_IS_LIBGC_TEST +TEST_CASE("update_if_exists boxed move string") +{ + constexpr auto N = 666u; + constexpr auto S = 7; + auto s = MAP_T>{}; + SECTION("preserve immutability") + { + auto s0 = s; + auto i0 = 0u; + // insert + for (auto i = 0u; i < N; ++i) { + s = std::move(s).set(std::to_string(i), std::to_string(i)); + } + // update + for (auto i = 0u; i < N; ++i) { + if (i % S == 0) { + s0 = s; + i0 = i; + } + s = std::move(s).update_if_exists(std::to_string(i), [&](auto&&) { + return std::to_string(i + 1); + }); + { + CHECK(s.size() == N); + for (auto j : test_irange(0u, i + 1)) + CHECK(*s.find(std::to_string(j)) == std::to_string(j + 1)); + for (auto j : test_irange(i + 1u, N)) + CHECK(*s.find(std::to_string(j)) == std::to_string(j)); + } + { + CHECK(s0.size() == N); + for (auto j : test_irange(0u, i0)) + CHECK(*s0.find(std::to_string(j)) == std::to_string(j + 1)); + for (auto j : test_irange(i0, N)) + CHECK(*s0.find(std::to_string(j)) == std::to_string(j)); + } + } + } +} +#endif + TEST_CASE("exception safety") { constexpr auto n = 2666u; diff --git a/test/set/gc.cpp b/test/set/gc.cpp index 65963d2c..e4bc47e5 100644 --- a/test/set/gc.cpp +++ b/test/set/gc.cpp @@ -22,4 +22,6 @@ template ; #define SET_T test_set_t +#define IMMER_IS_LIBGC_TEST 1 + #include "generic.ipp" diff --git a/test/set/generic.ipp b/test/set/generic.ipp index a7bbdfd8..5c3cd199 100644 --- a/test/set/generic.ipp +++ b/test/set/generic.ipp @@ -16,12 +16,15 @@ #include "test/util.hpp" #include +#include #include #include #include +using memory_policy_t = SET_T::memory_policy_type; + template auto make_generator() { @@ -30,6 +33,15 @@ auto make_generator() return std::bind(dist, engine); } +template +auto make_generator_s() +{ + auto engine = std::default_random_engine{42}; + auto dist = std::uniform_int_distribution{}; + return + [g = std::bind(dist, engine)]() mutable { return std::to_string(g()); }; +} + struct conflictor { unsigned v1; @@ -232,6 +244,41 @@ TEST_CASE("insert conflicts") } } +#if !IMMER_IS_LIBGC_TEST +TEST_CASE("insert boxed move string") +{ + constexpr auto N = 666u; + constexpr auto S = 7; + auto s = SET_T>{}; + SECTION("preserve immutability") + { + auto s0 = s; + auto i0 = 0u; + for (auto i = 0u; i < N; ++i) { + if (i % S == 0) { + s0 = s; + i0 = i; + } + s = std::move(s).insert(std::to_string(i)); + { + CHECK(s.size() == i + 1); + for (auto j : test_irange(0u, i + 1)) + CHECK(s.count(std::to_string(j)) == 1); + for (auto j : test_irange(i + 1u, N)) + CHECK(s.count(std::to_string(j)) == 0); + } + { + CHECK(s0.size() == i0); + for (auto j : test_irange(0u, i0)) + CHECK(s0.count(std::to_string(j)) == 1); + for (auto j : test_irange(i0, N)) + CHECK(s0.count(std::to_string(j)) == 0); + } + } + } +} +#endif + TEST_CASE("erase a lot") { constexpr auto N = 666u; @@ -241,7 +288,43 @@ TEST_CASE("erase a lot") auto s = SET_T{}; for (auto i = 0u; i < N; ++i) - s = s.insert(vals[i]); + s = std::move(s).insert(vals[i]); + + SECTION("immutable") + { + for (auto i = 0u; i < N; ++i) { + s = s.erase(vals[i]); + CHECK(s.size() == N - i - 1); + for (auto j : test_irange(0u, i + 1)) + CHECK(s.count(vals[j]) == 0); + for (auto j : test_irange(i + 1u, N)) + CHECK(s.count(vals[j]) == 1); + } + } + SECTION("move") + { + for (auto i = 0u; i < N; ++i) { + s = std::move(s).erase(vals[i]); + CHECK(s.size() == N - i - 1); + for (auto j : test_irange(0u, i + 1)) + CHECK(s.count(vals[j]) == 0); + for (auto j : test_irange(i + 1u, N)) + CHECK(s.count(vals[j]) == 1); + } + } +} + +#if !IMMER_IS_LIBGC_TEST +TEST_CASE("erase a lot boxed string") +{ + constexpr auto N = 666u; + auto gen = make_generator_s(); + auto vals = std::vector>{}; + generate_n(back_inserter(vals), N, gen); + + auto s = SET_T>{}; + for (auto i = 0u; i < N; ++i) + s = std::move(s).insert(vals[i]); SECTION("immutable") { @@ -265,7 +348,35 @@ TEST_CASE("erase a lot") CHECK(s.count(vals[j]) == 1); } } + SECTION("move preserve immutability") + { + constexpr auto S = 7; + auto s0 = s; + auto i0 = 0u; + for (auto i = 0u; i < N; ++i) { + if (i % S == 0) { + s0 = s; + i0 = i; + } + s = std::move(s).erase(vals[i]); + { + CHECK(s.size() == N - i - 1); + for (auto j : test_irange(0u, i + 1)) + CHECK(s.count(vals[j]) == 0); + for (auto j : test_irange(i + 1u, N)) + CHECK(s.count(vals[j]) == 1); + } + { + CHECK(s0.size() == N - i0); + for (auto j : test_irange(0u, i0)) + CHECK(s0.count(vals[j]) == 0); + for (auto j : test_irange(i0, N)) + CHECK(s0.count(vals[j]) == 1); + } + } + } } +#endif TEST_CASE("erase conflicts") { @@ -273,7 +384,7 @@ TEST_CASE("erase conflicts") auto vals = make_values_with_collisions(N); auto s = SET_T{}; for (auto i = 0u; i < N; ++i) - s = s.insert(vals[i]); + s = std::move(s).insert(vals[i]); SECTION("immutable") { From a7b0e23771ff57594f04af9db5e2c78b76fde23b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Mon, 1 Aug 2022 14:53:05 +0200 Subject: [PATCH 036/104] Remove usage of dec_unsafe() in RBTS data-structures --- immer/detail/rbts/node.hpp | 58 +++++++++++++++++++++++++++++--- immer/detail/rbts/operations.hpp | 10 +++--- 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/immer/detail/rbts/node.hpp b/immer/detail/rbts/node.hpp index 54156f9e..e5042005 100644 --- a/immer/detail/rbts/node.hpp +++ b/immer/detail/rbts/node.hpp @@ -540,6 +540,19 @@ struct node return dst; } + static node_t* do_copy_inner_replace( + node_t* dst, node_t* src, count_t n, count_t offset, node_t* child) + { + IMMER_ASSERT_TAGGED(dst->kind() == kind_t::inner); + IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner); + auto p = src->inner(); + inc_nodes(p, offset); + inc_nodes(p + offset + 1, n - offset - 1); + std::copy(p, p + n, dst->inner()); + dst->inner()[offset] = child; + return dst; + } + static node_t* copy_inner_r(node_t* src, count_t n) { IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner); @@ -582,6 +595,23 @@ struct node return dst; } + static node_t* do_copy_inner_replace_r( + node_t* dst, node_t* src, count_t n, count_t offset, node_t* child) + { + IMMER_ASSERT_TAGGED(dst->kind() == kind_t::inner); + IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner); + auto src_r = src->relaxed(); + auto dst_r = dst->relaxed(); + auto p = src->inner(); + inc_nodes(p, offset); + inc_nodes(p + offset + 1, n - offset - 1); + std::copy(src->inner(), src->inner() + n, dst->inner()); + std::copy(src_r->d.sizes, src_r->d.sizes + n, dst_r->d.sizes); + dst_r->d.count = n; + dst->inner()[offset] = child; + return dst; + } + static node_t* do_copy_inner_sr(node_t* dst, node_t* src, count_t n) { if (embed_relaxed) @@ -593,6 +623,21 @@ struct node } } + static node_t* do_copy_inner_replace_sr( + node_t* dst, node_t* src, count_t n, count_t offset, node_t* child) + { + if (embed_relaxed) + return do_copy_inner_replace_r(dst, src, n, offset, child); + else { + auto p = src->inner(); + inc_nodes(p, offset); + inc_nodes(p + offset + 1, n - offset - 1); + std::copy(p, p + n, dst->inner()); + dst->inner()[offset] = child; + return dst; + } + } + static node_t* copy_leaf(node_t* src, count_t n) { IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf); @@ -817,10 +862,12 @@ struct node auto dst_r = impl.d.data.inner.relaxed = new (heap::allocate(max_sizeof_relaxed)) relaxed_t; if (src_r) { - node_t::refs(src_r).dec_unsafe(); auto n = dst_r->d.count = src_r->d.count; std::copy( src_r->d.sizes, src_r->d.sizes + n, dst_r->d.sizes); + if (node_t::refs(src_r).dec()) + heap::deallocate(node_t::sizeof_inner_r_n(n), + src_r); } node_t::ownee(dst_r) = e; return dst_r; @@ -842,10 +889,12 @@ struct node auto dst_r = impl.d.data.inner.relaxed = new (heap::allocate(max_sizeof_relaxed)) relaxed_t; if (src_r) { - node_t::refs(src_r).dec_unsafe(); auto n = dst_r->d.count = src_r->d.count; std::copy( src_r->d.sizes, src_r->d.sizes + n, dst_r->d.sizes); + if (node_t::refs(src_r).dec()) + heap::deallocate(node_t::sizeof_inner_r_n(n), + src_r); } node_t::ownee(dst_r) = ec; return dst_r; @@ -866,9 +915,11 @@ struct node auto dst_r = new (heap::allocate(max_sizeof_relaxed)) relaxed_t; if (src_r) { - node_t::refs(src_r).dec_unsafe(); std::copy( src_r->d.sizes, src_r->d.sizes + n, dst_r->d.sizes); + if (node_t::refs(src_r).dec()) + heap::deallocate(node_t::sizeof_inner_r_n(n), + src_r); } dst_r->d.count = n; node_t::ownee(dst_r) = e; @@ -890,7 +941,6 @@ struct node } bool dec() const { return refs(this).dec(); } - void dec_unsafe() const { refs(this).dec_unsafe(); } static void inc_nodes(node_t** p, count_t n) { diff --git a/immer/detail/rbts/operations.hpp b/immer/detail/rbts/operations.hpp index adaf2c0b..d5ff6793 100644 --- a/immer/detail/rbts/operations.hpp +++ b/immer/detail/rbts/operations.hpp @@ -468,9 +468,8 @@ struct update_visitor : visitor_base> auto node = node_t::make_inner_sr_n(count, pos.relaxed()); IMMER_TRY { auto child = pos.towards_oh(this_t{}, idx, offset, fn); - node_t::do_copy_inner_sr(node, pos.node(), count); - node->inner()[offset]->dec_unsafe(); - node->inner()[offset] = child; + node_t::do_copy_inner_replace_sr( + node, pos.node(), count, offset, child); return node; } IMMER_CATCH (...) { @@ -487,9 +486,8 @@ struct update_visitor : visitor_base> auto node = node_t::make_inner_n(count); IMMER_TRY { auto child = pos.towards_oh_ch(this_t{}, idx, offset, count, fn); - node_t::do_copy_inner(node, pos.node(), count); - node->inner()[offset]->dec_unsafe(); - node->inner()[offset] = child; + node_t::do_copy_inner_replace( + node, pos.node(), count, offset, child); return node; } IMMER_CATCH (...) { From 4a8fd59e35cd804466ad5b4738ef42328f140866 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Mon, 1 Aug 2022 16:51:38 +0200 Subject: [PATCH 037/104] Remove dec_unsafe() usage in HAMT data-structures --- immer/detail/hamts/champ.hpp | 40 ++++++++++++++++++------------------ immer/detail/hamts/node.hpp | 12 +++++------ 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/immer/detail/hamts/champ.hpp b/immer/detail/hamts/champ.hpp index 5bb093ed..099744df 100644 --- a/immer/detail/hamts/champ.hpp +++ b/immer/detail/hamts/champ.hpp @@ -604,8 +604,8 @@ struct champ auto result = do_add_mut(e, child, std::move(v), hash, shift + B); node->children()[offset] = result.node; - if (!result.mutated) - child->dec_unsafe(); + if (!result.mutated && child->dec()) + node_t::delete_deep_shift(child, shift + B); return {node, result.added, true}; } else { assert(node->children()[offset]); @@ -672,8 +672,8 @@ struct champ { auto hash = Hash{}(v); auto res = do_add_mut(e, root, std::move(v), hash, 0); - if (!res.mutated) - root->dec_unsafe(); + if (!res.mutated && root->dec()) + node_t::delete_deep(root, 0); root = res.node; size += res.added ? 1 : 0; } @@ -897,8 +897,8 @@ struct champ auto result = do_update_mut( e, child, k, std::forward(fn), hash, shift + B); node->children()[offset] = result.node; - if (!result.mutated) - child->dec_unsafe(); + if (!result.mutated && child->dec()) + node_t::delete_deep_shift(child, shift + B); return {node, result.added, true}; } else { auto result = do_update( @@ -980,8 +980,8 @@ struct champ auto hash = Hash{}(k); auto res = do_update_mut( e, root, k, std::forward(fn), hash, 0); - if (!res.mutated) - root->dec_unsafe(); + if (!res.mutated && root->dec()) + node_t::delete_deep(root, 0); root = res.node; size += res.added ? 1 : 0; } @@ -1032,8 +1032,8 @@ struct champ e, child, k, std::forward(fn), hash, shift + B); if (result.node) { node->children()[offset] = result.node; - if (!result.mutated) - child->dec_unsafe(); + if (!result.mutated && child->dec()) + node_t::delete_deep_shift(child, shift + B); return {node, true}; } else { return {nullptr, false}; @@ -1091,8 +1091,8 @@ struct champ auto res = do_update_if_exists_mut( e, root, k, std::forward(fn), hash, 0); if (res.node) { - if (!res.mutated) - root->dec_unsafe(); + if (!res.mutated && root->dec()) + node_t::delete_deep(root, 0); root = res.node; } } @@ -1305,8 +1305,8 @@ struct champ shift > 0) { if (mutate) { node_t::delete_inner(node); - if (!result.mutated) - child->dec_unsafe(); + if (!result.mutated && child->dec()) + node_t::delete_deep_shift(child, shift + B); } return {result.data.singleton, mutate}; } else { @@ -1326,15 +1326,15 @@ struct champ *result.data.singleton); if (result.mutated) detail::destroy_at(result.data.singleton); - else if (mutate) - child->dec_unsafe(); + else if (mutate && child->dec()) + node_t::delete_deep_shift(child, shift + B); return {node_t::owned_values(r, e), mutate}; } case sub_result::tree: if (mutate) { children[offset] = result.data.tree; - if (!result.mutated) - child->dec_unsafe(); + if (!result.mutated && child->dec()) + node_t::delete_deep_shift(child, shift + B); return {node, true}; } else { IMMER_TRY { @@ -1403,8 +1403,8 @@ struct champ case sub_result::nothing: break; case sub_result::tree: - if (!res.mutated) - root->dec_unsafe(); + if (!res.mutated && root->dec()) + node_t::delete_deep(root, 0); root = res.data.tree; --size; break; diff --git a/immer/detail/hamts/node.hpp b/immer/detail/hamts/node.hpp index 3a8ff9a3..6cee4e1b 100644 --- a/immer/detail/hamts/node.hpp +++ b/immer/detail/hamts/node.hpp @@ -369,8 +369,9 @@ struct node deallocate_values(nxt, nv); IMMER_RETHROW; } - refs(old).dec_unsafe(); impl.d.data.inner.values = nxt; + if (refs(old).dec()) + delete_values(old, nv); return dst; } } @@ -518,8 +519,8 @@ struct node dst->impl.d.data.inner.datamap = src->datamap(); dst->impl.d.data.inner.nodemap = src->nodemap(); std::copy(srcp, srcp + n, dstp); - inc_nodes(srcp, n); - srcp[offset]->dec_unsafe(); + inc_nodes(srcp, offset); + inc_nodes(srcp + offset + 1, n - offset - 1); dstp[offset] = child; return dst; } @@ -710,8 +711,8 @@ struct node deallocate_inner(dst, n - 1, nv + 1); IMMER_RETHROW; } - inc_nodes(src->children(), n); - src->children()[noffset]->dec_unsafe(); + inc_nodes(src->children(), noffset); + inc_nodes(src->children() + noffset + 1, n - noffset - 1); std::copy(src->children(), src->children() + noffset, dst->children()); std::copy(src->children() + noffset + 1, src->children() + n, @@ -1046,7 +1047,6 @@ struct node } bool dec() const { return refs(this).dec(); } - void dec_unsafe() const { refs(this).dec_unsafe(); } static void inc_nodes(node_t** p, count_t n) { From ad759c478314733cc3f44c0d3192d1251153e830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Mon, 1 Aug 2022 16:51:53 +0200 Subject: [PATCH 038/104] Remove dec_unsafe() from the policies --- immer/refcount/no_refcount_policy.hpp | 1 - immer/refcount/refcount_policy.hpp | 6 ------ immer/refcount/unsafe_refcount_policy.hpp | 1 - test/memory/refcounts.cpp | 10 ---------- 4 files changed, 18 deletions(-) diff --git a/immer/refcount/no_refcount_policy.hpp b/immer/refcount/no_refcount_policy.hpp index d726d2c3..5706858b 100644 --- a/immer/refcount/no_refcount_policy.hpp +++ b/immer/refcount/no_refcount_policy.hpp @@ -24,7 +24,6 @@ struct no_refcount_policy void inc() {} bool dec() { return false; } - void dec_unsafe() {} bool unique() { return false; } }; diff --git a/immer/refcount/refcount_policy.hpp b/immer/refcount/refcount_policy.hpp index 7a8e15a8..52b516e8 100644 --- a/immer/refcount/refcount_policy.hpp +++ b/immer/refcount/refcount_policy.hpp @@ -34,12 +34,6 @@ struct refcount_policy bool dec() { return 1 == refcount.fetch_sub(1, std::memory_order_acq_rel); } - void dec_unsafe() - { - assert(refcount.load() > 1); - refcount.fetch_sub(1, std::memory_order_relaxed); - } - bool unique() { return refcount == 1; } }; diff --git a/immer/refcount/unsafe_refcount_policy.hpp b/immer/refcount/unsafe_refcount_policy.hpp index 0d1417d1..df65019b 100644 --- a/immer/refcount/unsafe_refcount_policy.hpp +++ b/immer/refcount/unsafe_refcount_policy.hpp @@ -31,7 +31,6 @@ struct unsafe_refcount_policy void inc() { ++refcount; } bool dec() { return --refcount == 0; } - void dec_unsafe() { --refcount; } bool unique() { return refcount == 1; } }; diff --git a/test/memory/refcounts.cpp b/test/memory/refcounts.cpp index f9278ca7..3b05da02 100644 --- a/test/memory/refcounts.cpp +++ b/test/memory/refcounts.cpp @@ -42,16 +42,6 @@ void test_refcount() CHECK(!elem.dec()); CHECK(elem.dec()); } - - SECTION("inc dec unsafe") - { - refcount elem{}; - elem.inc(); - CHECK(!elem.dec()); - elem.inc(); - elem.dec_unsafe(); - CHECK(elem.dec()); - } } TEST_CASE("basic refcount") { test_refcount(); } From 0e07630e229f9e34a977595816b90e2d380ea7b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Tue, 16 Aug 2022 12:07:50 +0200 Subject: [PATCH 039/104] Fix debug stats --- immer/detail/hamts/champ.hpp | 21 +++++++++++++-------- test/map/generic.ipp | 17 +++++++++++++++++ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/immer/detail/hamts/champ.hpp b/immer/detail/hamts/champ.hpp index 099744df..6fd9e2f9 100644 --- a/immer/detail/hamts/champ.hpp +++ b/immer/detail/hamts/champ.hpp @@ -94,21 +94,26 @@ struct champ_debug_stats 100. * (value_count * value_size + child_count * child_size) / (capacity * value_size + capacity * child_size); - auto value_capacity = m * inner_node_w_value_count; - auto child_capacity = m * inner_node_w_child_count; - auto dense_child_utilization = 100. * child_count / child_capacity; - auto dense_value_utilization = 100. * value_count / value_capacity; + auto value_capacity = m * inner_node_w_value_count; + auto child_capacity = m * inner_node_w_child_count; + auto dense_child_utilization = + child_capacity == 0 ? 100. : 100. * child_count / child_capacity; + auto dense_value_utilization = + value_capacity == 0 ? 100. : 100. * value_count / value_capacity; auto dense_utilization = - 100. * (value_count * value_size + child_count * child_size) / - (value_capacity * value_size + child_capacity * child_size); + value_capacity + child_capacity == 0 + ? 100. + : 100. * (value_count * value_size + child_count * child_size) / + (value_capacity * value_size + + child_capacity * child_size); return {collision_ratio, utilization, child_utilization, value_utilization, dense_utilization, - dense_child_utilization, - dense_value_utilization}; + dense_value_utilization, + dense_child_utilization}; } }; #endif diff --git a/test/map/generic.ipp b/test/map/generic.ipp index 4aa162dd..ee6bbe86 100644 --- a/test/map/generic.ipp +++ b/test/map/generic.ipp @@ -179,6 +179,23 @@ TEST_CASE("equals and setting") #endif } +#if IMMER_DEBUG_STATS +TEST_CASE("debug stats") +{ + { + std::cout + << immer::map{}.impl().get_debug_stats().get_summary(); + } + { + immer::map map; + for (int i = 0; i <= 10; i++) { + map = std::move(map).set(i, i); + } + std::cout << map.impl().get_debug_stats().get_summary(); + } +} +#endif + TEST_CASE("iterator") { const auto N = 666u; From 107a1b332f2b821bf72ab2735b5ba50d9e56fc9d Mon Sep 17 00:00:00 2001 From: Mengna Li <95600143+Adela0814@users.noreply.github.com> Date: Wed, 14 Sep 2022 15:42:26 +0800 Subject: [PATCH 040/104] Add vcpkg installation instructions --- README.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.rst b/README.rst index abf4dcea..a6ad1208 100644 --- a/README.rst +++ b/README.rst @@ -181,6 +181,19 @@ system once you have manually cloned the repository:: .. _nix package manager: https://nixos.org/nix .. _cmake: https://cmake.org/ +Installing immer using vcpkg +----------------------------- + +You can download and install immer using the [vcpkg](https://github.com/Microsoft/vcpkg) dependency manager:: + + git clone https://github.com/Microsoft/vcpkg.git + cd vcpkg + ./bootstrap-vcpkg.sh + ./vcpkg integrate install + ./vcpkg install immer + +The immer port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository. + Development ----------- From 8acee494ba6775178d5b0de29a6ac4703f0d90ed Mon Sep 17 00:00:00 2001 From: Guannan Wei Date: Wed, 21 Sep 2022 12:27:27 -0700 Subject: [PATCH 041/104] Fix typo in comment --- immer/map.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/immer/map.hpp b/immer/map.hpp index 593f7dc5..d13f3b48 100644 --- a/immer/map.hpp +++ b/immer/map.hpp @@ -363,7 +363,7 @@ class map } /*! - * Returns whether the sets are equal. + * Returns whether the maps are equal. */ IMMER_NODISCARD bool operator==(const map& other) const { From 8f105e275c88facd57e31bda21c493fdf15b43e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Mon, 3 Oct 2022 18:18:41 +0200 Subject: [PATCH 042/104] Add map fuzzer that can find if the tree is corrupted by moves We suspect there are cases where immutability is broken by the mutating version of the delete operation. --- extra/fuzzer/map-st-str.cpp | 134 +++++++++++++++++++++++++++++++++++ immer/detail/hamts/champ.hpp | 57 +++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 extra/fuzzer/map-st-str.cpp diff --git a/extra/fuzzer/map-st-str.cpp b/extra/fuzzer/map-st-str.cpp new file mode 100644 index 00000000..a707ada0 --- /dev/null +++ b/extra/fuzzer/map-st-str.cpp @@ -0,0 +1,134 @@ +// +// 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_input.hpp" + +#include + +#include + +#include + +using st_memory = immer::memory_policy, + immer::unsafe_refcount_policy, + immer::no_lock_policy, + immer::no_transience_policy, + false>; + +struct colliding_hash_t +{ + std::size_t operator()(const std::string& x) const + { + return std::hash{}(x) & ~15; + } +}; + +extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, + std::size_t size) +{ + constexpr auto var_count = 4; + + using map_t = immer::map, + st_memory>; + + auto vars = std::array{}; + + auto is_valid_var = [&](auto idx) { return idx >= 0 && idx < var_count; }; + + return fuzzer_input{data, size}.run([&](auto& in) { + enum ops + { + op_set, + op_erase, + op_set_move, + op_erase_move, + op_iterate, + op_find, + op_update, + op_update_move, + op_diff + }; + auto src = read(in, is_valid_var); + auto dst = read(in, is_valid_var); + assert(vars[src].impl().check_champ()); + switch (read(in)) { + case op_set: { + auto value = std::to_string(read(in)); + vars[dst] = vars[src].set(value, "foo"); + break; + } + case op_erase: { + auto value = std::to_string(read(in)); + vars[dst] = vars[src].erase(value); + break; + } + case op_set_move: { + auto value = std::to_string(read(in)); + vars[dst] = std::move(vars[src]).set(value, "foo"); + break; + } + case op_erase_move: { + auto value = std::to_string(read(in)); + vars[dst] = std::move(vars[src]).erase(value); + break; + } + case op_iterate: { + auto srcv = vars[src]; + for (const auto& v : srcv) { + vars[dst] = vars[dst].set(v.first, v.second); + } + break; + } + case op_find: { + auto value = std::to_string(read(in)); + auto res = vars[src].find(value); + if (res != nullptr) { + vars[dst] = vars[dst].set(*res, "foo"); + } + break; + } + case op_update: { + auto key = std::to_string(read(in)); + vars[dst] = vars[src].update( + key, [](std::string x) { return std::move(x) + "bar"; }); + break; + } + case op_update_move: { + auto key = std::to_string(read(in)); + vars[dst] = std::move(vars[src]).update( + key, [](std::string x) { return std::move(x) + "baz"; }); + break; + } + case op_diff: { + auto&& a = vars[src]; + auto&& b = vars[dst]; + diff( + a, + b, + [&](auto&& x) { + assert(!a.count(x.first)); + assert(b.count(x.first)); + }, + [&](auto&& x) { + assert(a.count(x.first)); + assert(!b.count(x.first)); + }, + [&](auto&& x, auto&& y) { + assert(x.first == y.first); + assert(x.second != y.second); + }); + } + default: + break; + }; + return true; + }); +} diff --git a/immer/detail/hamts/champ.hpp b/immer/detail/hamts/champ.hpp index 6fd9e2f9..bd04ce7d 100644 --- a/immer/detail/hamts/champ.hpp +++ b/immer/detail/hamts/champ.hpp @@ -190,6 +190,63 @@ struct champ node_t::delete_deep(root, 0); } + std::size_t do_check_champ(node_t* node, + count_t depth, + size_t path_hash, + size_t hash_mask) const + { + auto result = std::size_t{}; + if (depth < max_depth) { + auto nodemap = node->nodemap(); + if (nodemap) { + auto fst = node->children(); + for (auto idx = std::size_t{}; idx < branches; ++idx) { + if (nodemap & (1 << idx)) { + auto child = *fst++; + result += + do_check_champ(child, + depth + 1, + path_hash | (idx << (B * depth)), + (hash_mask << B) | mask); + } + } + } + auto datamap = node->datamap(); + if (datamap) { + auto fst = node->values(); + for (auto idx = std::size_t{}; idx < branches; ++idx) { + if (datamap & (1 << idx)) { + auto hash = Hash{}(*fst++); + auto check = (hash & hash_mask) == + (path_hash | (idx << (B * depth))); + // assert(check); + result += !!check; + } + } + } + } else { + auto fst = node->collisions(); + auto lst = fst + node->collision_count(); + for (; fst != lst; ++fst) { + auto hash = Hash{}(*fst); + auto check = hash == path_hash; + // assert(check); + result += !!check; + } + } + return result; + } + + // Checks that the hashes of the values correspond with what has actually + // been inserted. If it doesn't it can mean that corruption has happened + // due some value being moved out of the champ when it should have not. + bool check_champ() const + { + auto r = do_check_champ(root, 0, 0, mask); + // assert(r == size); + return r == size; + } + #if IMMER_DEBUG_STATS void do_get_debug_stats(champ_debug_stats& stats, node_t* node, From 048891443cda248581b206cc4e95cc37302dfda1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Mon, 3 Oct 2022 18:26:48 +0200 Subject: [PATCH 043/104] Add set fuzzer that can find if the tree is corrupted by moves See previous commit. --- extra/fuzzer/set-st-str.cpp | 107 ++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 extra/fuzzer/set-st-str.cpp diff --git a/extra/fuzzer/set-st-str.cpp b/extra/fuzzer/set-st-str.cpp new file mode 100644 index 00000000..b3c2cc03 --- /dev/null +++ b/extra/fuzzer/set-st-str.cpp @@ -0,0 +1,107 @@ +// +// 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_input.hpp" + +#include +#include +#include + +#include + +#include + +using st_memory = immer::memory_policy, + immer::unsafe_refcount_policy, + immer::no_lock_policy, + immer::no_transience_policy, + false>; + +struct colliding_hash_t +{ + std::size_t operator()(const std::string& x) const + { + return std::hash{}(x) & ~15; + } +}; + +extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, + std::size_t size) +{ + constexpr auto var_count = 4; + + using set_t = + immer::set, st_memory>; + + auto vars = std::array{}; + + auto is_valid_var = [&](auto idx) { return idx >= 0 && idx < var_count; }; + + return fuzzer_input{data, size}.run([&](auto& in) { + enum ops + { + op_insert, + op_erase, + op_insert_move, + op_erase_move, + op_iterate, + op_diff + }; + auto src = read(in, is_valid_var); + auto dst = read(in, is_valid_var); + assert(vars[src].impl().check_champ()); + switch (read(in)) { + case op_insert: { + auto value = std::to_string(read(in)); + vars[dst] = vars[src].insert(value); + break; + } + case op_erase: { + auto value = std::to_string(read(in)); + vars[dst] = vars[src].erase(value); + break; + } + case op_insert_move: { + auto value = std::to_string(read(in)); + vars[dst] = std::move(vars[src]).insert(value); + break; + } + case op_erase_move: { + auto value = std::to_string(read(in)); + vars[dst] = std::move(vars[src]).erase(value); + break; + } + case op_iterate: { + auto srcv = vars[src]; + for (const auto& v : srcv) { + vars[dst] = vars[dst].insert(v); + } + break; + } + case op_diff: { + auto&& a = vars[src]; + auto&& b = vars[dst]; + diff( + a, + b, + [&](auto&& x) { + assert(!a.count(x)); + assert(b.count(x)); + }, + [&](auto&& x) { + assert(a.count(x)); + assert(!b.count(x)); + }, + [&](auto&& x, auto&& y) { assert(false); }); + } + default: + break; + }; + return true; + }); +} From b43a1ca2ac58efba740f31e0934070b8f98063e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Mon, 3 Oct 2022 18:27:09 +0200 Subject: [PATCH 044/104] Fix fuzzer was not doing what we thought it did --- extra/fuzzer/set-st.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extra/fuzzer/set-st.cpp b/extra/fuzzer/set-st.cpp index a492ff43..fe7c8201 100644 --- a/extra/fuzzer/set-st.cpp +++ b/extra/fuzzer/set-st.cpp @@ -69,7 +69,7 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, } case op_erase_move: { auto value = read(in); - vars[dst] = vars[src].erase(value); + vars[dst] = std::move(vars[src]).erase(value); break; } case op_iterate: { From 35c94037d4c9e28e3847b0cb49099ea65b56ecf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Mon, 3 Oct 2022 19:15:18 +0200 Subject: [PATCH 045/104] Fix issues in map/set erase() found by fuzzer --- immer/detail/hamts/champ.hpp | 49 ++-- ...h-2838943da19b47c02dcff313e523eead0e2e8635 | Bin 0 -> 114 bytes ...h-dc9dad6beae69a6bb8ffd6d203b95032f445ec9b | Bin 0 -> 667 bytes test/oss-fuzz/map-st-str-0.cpp | 218 ++++++++++++++++++ test/oss-fuzz/set-st-str-0.cpp | 122 ++++++++++ 5 files changed, 371 insertions(+), 18 deletions(-) create mode 100644 test/oss-fuzz/data/crash-2838943da19b47c02dcff313e523eead0e2e8635 create mode 100644 test/oss-fuzz/data/crash-dc9dad6beae69a6bb8ffd6d203b95032f445ec9b create mode 100644 test/oss-fuzz/map-st-str-0.cpp create mode 100644 test/oss-fuzz/set-st-str-0.cpp diff --git a/immer/detail/hamts/champ.hpp b/immer/detail/hamts/champ.hpp index bd04ce7d..f263a48b 100644 --- a/immer/detail/hamts/champ.hpp +++ b/immer/detail/hamts/champ.hpp @@ -1288,29 +1288,41 @@ struct champ kind_t kind; data_t data; + bool owned; bool mutated; sub_result_mut(sub_result a) : kind{a.kind} , data{a.data} + , owned{false} , mutated{false} {} sub_result_mut(sub_result a, bool m) : kind{a.kind} , data{a.data} + , owned{false} , mutated{m} {} - sub_result_mut(bool m) + sub_result_mut() : kind{kind_t::nothing} - , mutated{m} {}; + , mutated{false} {}; sub_result_mut(T* x, bool m) : kind{kind_t::singleton} + , owned{m} + , mutated{m} + { + data.singleton = x; + }; + sub_result_mut(T* x, bool o, bool m) + : kind{kind_t::singleton} + , owned{o} , mutated{m} { data.singleton = x; }; sub_result_mut(node_t* x, bool m) : kind{kind_t::tree} + , owned{false} , mutated{m} { data.tree = x; @@ -1348,7 +1360,7 @@ struct champ } } } - return {mutate}; + return {}; } else { auto idx = (hash & (mask << shift)) >> shift; auto bit = bitmap_t{1u} << idx; @@ -1361,7 +1373,7 @@ struct champ : do_sub(child, k, hash, shift + B); switch (result.kind) { case sub_result::nothing: - return {mutate}; + return {}; case sub_result::singleton: if (node->datamap() == 0 && node->children_count() == 1 && shift > 0) { @@ -1370,7 +1382,7 @@ struct champ if (!result.mutated && child->dec()) node_t::delete_deep_shift(child, shift + B); } - return {result.data.singleton, mutate}; + return {result.data.singleton, result.owned, mutate}; } else { auto r = mutate ? node_t::move_inner_replace_inline( @@ -1378,7 +1390,7 @@ struct champ node, bit, offset, - result.mutated + result.owned ? std::move(*result.data.singleton) : *result.data.singleton) : node_t::copy_inner_replace_inline( @@ -1386,9 +1398,9 @@ struct champ bit, offset, *result.data.singleton); - if (result.mutated) + if (result.owned) detail::destroy_at(result.data.singleton); - else if (mutate && child->dec()) + if (!result.mutated && mutate && child->dec()) node_t::delete_deep_shift(child, shift + B); return {node_t::owned_values(r, e), mutate}; } @@ -1412,8 +1424,9 @@ struct champ } } } else if (node->datamap() & bit) { - auto offset = node->data_count(bit); - auto val = node->values() + offset; + auto offset = node->data_count(bit); + auto val = node->values() + offset; + auto mutate_values = mutate && node->can_mutate_values(e); if (Equal{}(*val, k)) { auto nv = node->data_count(); if (node->nodemap() || nv > 2) { @@ -1424,20 +1437,20 @@ struct champ return {node_t::owned_values_safe(r, e), mutate}; } else if (nv == 2) { if (shift > 0) { - if (mutate) { + if (mutate_values) { auto r = new (store) T{std::move(node->values()[!offset])}; node_t::delete_inner(node); - return {r, mutate}; + return {r, true}; } else { - return {node->values() + !offset, mutate}; + return {node->values() + !offset, false}; } } else { auto& v = node->values()[!offset]; - auto r = - node_t::make_inner_n(0, - node->datamap() & ~bit, - mutate ? std::move(v) : v); + auto r = node_t::make_inner_n( + 0, + node->datamap() & ~bit, + mutate_values ? std::move(v) : v); assert(!node->nodemap()); if (mutate) node_t::delete_inner(node); @@ -1451,7 +1464,7 @@ struct champ } } } - return {mutate}; + return {}; } } diff --git a/test/oss-fuzz/data/crash-2838943da19b47c02dcff313e523eead0e2e8635 b/test/oss-fuzz/data/crash-2838943da19b47c02dcff313e523eead0e2e8635 new file mode 100644 index 0000000000000000000000000000000000000000..d9acbc51f8218dbc8512144ab7a06aa57c96ff95 GIT binary patch literal 114 zcmZQzWMcpWRR&fd{g2@vBZCw~gdvHMospG+(F)9EMaV{GUEUY%mNjcfB*jnGy$v*Y9g4D z0}C +#include +#include + +#include + +#include + +#include + +#define IMMER_FUZZED_TRACE_ENABLE 1 + +#if IMMER_FUZZED_TRACE_ENABLE +#include +#define IMMER_FUZZED_TRACE(...) fmt::print(std::cerr, __VA_ARGS__) +#else +#define IMMER_FUZZED_TRACE(...) +#endif + +namespace { + +using st_memory = immer::memory_policy, + immer::unsafe_refcount_policy, + immer::no_lock_policy, + immer::no_transience_policy, + false>; + +struct colliding_hash_t +{ + std::size_t operator()(const std::string& x) const + { + return std::hash{}(x) & ~15; + } +}; + +int run_input(const std::uint8_t* data, std::size_t size) +{ + constexpr auto var_count = 4; + + using map_t = immer::map, + st_memory>; + + auto vars = std::array{}; + +#if IMMER_FUZZED_TRACE_ENABLE + IMMER_FUZZED_TRACE("/// new test run\n"); + IMMER_FUZZED_TRACE("using map_t = immer::map, st_memory>;\n"); + for (auto i = 0u; i < var_count; ++i) + IMMER_FUZZED_TRACE("auto v{} = map_t{{}};\n", i); +#endif + + auto is_valid_var = [&](auto idx) { return idx >= 0 && idx < var_count; }; + + return fuzzer_input{data, size}.run([&](auto& in) { + enum ops + { + op_set, + op_erase, + op_set_move, + op_erase_move, + op_iterate, + op_find, + op_update, + op_update_move, + op_diff + }; + auto src = read(in, is_valid_var); + auto dst = read(in, is_valid_var); + IMMER_FUZZED_TRACE("CHECK(v{}.impl().check_champ());\n", +src); + CHECK(vars[src].impl().check_champ()); + switch (read(in)) { + case op_set: { + auto value = std::to_string(read(in)); + IMMER_FUZZED_TRACE( + "v{} = v{}.set(\"{}\", \"foo\");\n", +dst, +src, value); + vars[dst] = vars[src].set(value, "foo"); + break; + } + case op_erase: { + auto value = std::to_string(read(in)); + IMMER_FUZZED_TRACE("v{} = v{}.erase(\"{}\");\n", +dst, +src, value); + vars[dst] = vars[src].erase(value); + break; + } + case op_set_move: { + auto value = std::to_string(read(in)); + IMMER_FUZZED_TRACE("v{} = std::move(v{}).set(\"{}\", \"foo\");\n", + +dst, + +src, + value); + vars[dst] = std::move(vars[src]).set(value, "foo"); + break; + } + case op_erase_move: { + auto value = std::to_string(read(in)); + IMMER_FUZZED_TRACE( + "v{} = std::move(v{}).erase(\"{}\");\n", +dst, +src, value); + vars[dst] = std::move(vars[src]).erase(value); + break; + } + case op_iterate: { + IMMER_FUZZED_TRACE("!!!TODO;\n"); + auto srcv = vars[src]; + for (const auto& v : srcv) { + vars[dst] = vars[dst].set(v.first, v.second); + } + break; + } + case op_find: { + IMMER_FUZZED_TRACE("!!!TODO;\n"); + auto value = std::to_string(read(in)); + auto res = vars[src].find(value); + if (res != nullptr) { + vars[dst] = vars[dst].set(*res, "foo"); + } + break; + } + case op_update: { + auto key = std::to_string(read(in)); + IMMER_FUZZED_TRACE("v{} = v{}.update(\"{}\", [] (auto x) {{ " + "return x + \"bar\"; }});\n", + +dst, + +src, + key); + vars[dst] = vars[src].update( + key, [](std::string x) { return std::move(x) + "bar"; }); + break; + } + case op_update_move: { + auto key = std::to_string(read(in)); + IMMER_FUZZED_TRACE( + "v{} = std::move(v{}).update(\"{}\", [] (auto x) {{ " + "return x + \"bar\"; }});\n", + +dst, + +src, + key); + vars[dst] = std::move(vars[src]).update( + key, [](std::string x) { return std::move(x) + "baz"; }); + break; + } + case op_diff: { + IMMER_FUZZED_TRACE("!!!TODO;\n"); + auto&& a = vars[src]; + auto&& b = vars[dst]; + diff( + a, + b, + [&](auto&& x) { + assert(!a.count(x.first)); + assert(b.count(x.first)); + }, + [&](auto&& x) { + assert(a.count(x.first)); + assert(!b.count(x.first)); + }, + [&](auto&& x, auto&& y) { + assert(x.first == y.first); + assert(x.second != y.second); + }); + } + default: + break; + }; + return true; + }); +} + +} // namespace + +TEST_CASE("local test runs") +{ + SECTION("fuzzer") + { + auto input = + load_input("crash-dc9dad6beae69a6bb8ffd6d203b95032f445ec9b"); + CHECK(run_input(input.data(), input.size()) == 0); + (void) run_input; + } + + SECTION("simplify") + { + using map_t = immer::map, + st_memory>; + auto v0 = map_t{}; + auto v3 = map_t{}; + + v0 = std::move(v0) + .set("256", "foo") + .set("217020539959771201", "foo") + .set("91201394110889985", "foo") + .set("217020518514230019", "foo"); + v3 = v0; + v0 = v0.set("0", "foo"); + CHECK(v0.impl().check_champ()); + CHECK(v3.impl().check_champ()); + + v3 = std::move(v3).erase("217020518514230019"); // here + CHECK(v3.impl().check_champ()); + CHECK(v0.impl().check_champ()); + } +} diff --git a/test/oss-fuzz/set-st-str-0.cpp b/test/oss-fuzz/set-st-str-0.cpp new file mode 100644 index 00000000..b4060990 --- /dev/null +++ b/test/oss-fuzz/set-st-str-0.cpp @@ -0,0 +1,122 @@ +// +// 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 "input.hpp" + +#include +#include +#include + +#include + +#include + +#include + +namespace { + +using st_memory = immer::memory_policy, + immer::unsafe_refcount_policy, + immer::no_lock_policy, + immer::no_transience_policy, + false>; + +struct colliding_hash_t +{ + std::size_t operator()(const std::string& x) const + { + return std::hash{}(x) & ~15; + } +}; + +int run_input(const std::uint8_t* data, std::size_t size) +{ + constexpr auto var_count = 4; + + using set_t = + immer::set, st_memory>; + + auto vars = std::array{}; + + auto is_valid_var = [&](auto idx) { return idx >= 0 && idx < var_count; }; + + return fuzzer_input{data, size}.run([&](auto& in) { + enum ops + { + op_insert, + op_erase, + op_insert_move, + op_erase_move, + op_iterate, + op_diff + }; + auto src = read(in, is_valid_var); + auto dst = read(in, is_valid_var); + assert(vars[src].impl().check_champ()); + switch (read(in)) { + case op_insert: { + auto value = std::to_string(read(in)); + vars[dst] = vars[src].insert(value); + break; + } + case op_erase: { + auto value = std::to_string(read(in)); + vars[dst] = vars[src].erase(value); + break; + } + case op_insert_move: { + auto value = std::to_string(read(in)); + vars[dst] = std::move(vars[src]).insert(value); + break; + } + case op_erase_move: { + auto value = std::to_string(read(in)); + vars[dst] = std::move(vars[src]).erase(value); + break; + } + case op_iterate: { + auto srcv = vars[src]; + for (const auto& v : srcv) { + vars[dst] = vars[dst].insert(v); + } + break; + } + case op_diff: { + auto&& a = vars[src]; + auto&& b = vars[dst]; + diff( + a, + b, + [&](auto&& x) { + assert(!a.count(x)); + assert(b.count(x)); + }, + [&](auto&& x) { + assert(a.count(x)); + assert(!b.count(x)); + }, + [&](auto&& x, auto&& y) { assert(false); }); + } + default: + break; + }; + return true; + }); +} + +} // namespace + +TEST_CASE("local test runs") +{ + SECTION("fuzzer") + { + auto input = + load_input("crash-2838943da19b47c02dcff313e523eead0e2e8635"); + CHECK(run_input(input.data(), input.size()) == 0); + } +} From d5b6bf6b45f756329dd54933d77b4d7170bf7458 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Tue, 4 Oct 2022 12:14:28 +0200 Subject: [PATCH 046/104] Strengthen the fuzzers even further --- extra/fuzzer/map-st-str.cpp | 3 ++- extra/fuzzer/set-st-str.cpp | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/extra/fuzzer/map-st-str.cpp b/extra/fuzzer/map-st-str.cpp index a707ada0..40770961 100644 --- a/extra/fuzzer/map-st-str.cpp +++ b/extra/fuzzer/map-st-str.cpp @@ -8,6 +8,7 @@ #include "fuzzer_input.hpp" +#include #include #include @@ -34,7 +35,7 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, constexpr auto var_count = 4; using map_t = immer::map, colliding_hash_t, std::equal_to<>, st_memory>; diff --git a/extra/fuzzer/set-st-str.cpp b/extra/fuzzer/set-st-str.cpp index b3c2cc03..e5fc1137 100644 --- a/extra/fuzzer/set-st-str.cpp +++ b/extra/fuzzer/set-st-str.cpp @@ -8,6 +8,7 @@ #include "fuzzer_input.hpp" +#include #include #include #include From 75cc8984e2a2066b9fc76c11b3408a7d0a1d65d4 Mon Sep 17 00:00:00 2001 From: Asa Hammond Date: Thu, 13 Oct 2022 15:58:50 -0700 Subject: [PATCH 047/104] make clang ignore gcc pragma warning for champ --- immer/detail/hamts/champ.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/immer/detail/hamts/champ.hpp b/immer/detail/hamts/champ.hpp index f263a48b..64a8cb82 100644 --- a/immer/detail/hamts/champ.hpp +++ b/immer/detail/hamts/champ.hpp @@ -1207,14 +1207,18 @@ struct champ ? node_t::copy_collision_remove(node, cur) : sub_result{fst + (cur == fst)}; #if !defined(_MSC_VER) +#if defined(__GNUC__) && !defined(__clang__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif #endif // Apparently GCC is generating this warning sometimes when // compiling the benchmarks. It makes however no sense at all. return {}; #if !defined(_MSC_VER) +#if defined(__GNUC__) && !defined(__clang__) #pragma GCC diagnostic pop +#endif #endif } else { auto idx = (hash & (mask << shift)) >> shift; From 6e8a4f8f833b05b33366e6c3abf7b70d43d70d98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Mon, 24 Oct 2022 21:03:27 +0200 Subject: [PATCH 048/104] Add fuzzers that produce more conflicts --- extra/fuzzer/map-st-str-conflict.cpp | 136 +++++++++++++++++++++++++++ extra/fuzzer/set-st-str-conflict.cpp | 108 +++++++++++++++++++++ 2 files changed, 244 insertions(+) create mode 100644 extra/fuzzer/map-st-str-conflict.cpp create mode 100644 extra/fuzzer/set-st-str-conflict.cpp diff --git a/extra/fuzzer/map-st-str-conflict.cpp b/extra/fuzzer/map-st-str-conflict.cpp new file mode 100644 index 00000000..7da68547 --- /dev/null +++ b/extra/fuzzer/map-st-str-conflict.cpp @@ -0,0 +1,136 @@ +// +// 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_input.hpp" + +#include +#include + +#include + +#include + +using st_memory = immer::memory_policy, + immer::unsafe_refcount_policy, + immer::no_lock_policy, + immer::no_transience_policy, + false>; + +struct colliding_hash_t +{ + std::size_t operator()(const std::string& x) const + { + return std::hash{}(x) & ~((std::size_t{1} << 48) - 1); + } +}; + +extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, + std::size_t size) +{ + constexpr auto var_count = 4; + + using map_t = immer::map, + colliding_hash_t, + std::equal_to<>, + st_memory, + 3>; + + auto vars = std::array{}; + + auto is_valid_var = [&](auto idx) { return idx >= 0 && idx < var_count; }; + + return fuzzer_input{data, size}.run([&](auto& in) { + enum ops + { + op_set, + op_erase, + op_set_move, + op_erase_move, + op_iterate, + op_find, + op_update, + op_update_move, + op_diff + }; + auto src = read(in, is_valid_var); + auto dst = read(in, is_valid_var); + assert(vars[src].impl().check_champ()); + switch (read(in)) { + case op_set: { + auto value = std::to_string(read(in)); + vars[dst] = vars[src].set(value, "foo"); + break; + } + case op_erase: { + auto value = std::to_string(read(in)); + vars[dst] = vars[src].erase(value); + break; + } + case op_set_move: { + auto value = std::to_string(read(in)); + vars[dst] = std::move(vars[src]).set(value, "foo"); + break; + } + case op_erase_move: { + auto value = std::to_string(read(in)); + vars[dst] = std::move(vars[src]).erase(value); + break; + } + case op_iterate: { + auto srcv = vars[src]; + for (const auto& v : srcv) { + vars[dst] = vars[dst].set(v.first, v.second); + } + break; + } + case op_find: { + auto value = std::to_string(read(in)); + auto res = vars[src].find(value); + if (res != nullptr) { + vars[dst] = vars[dst].set(*res, "foo"); + } + break; + } + case op_update: { + auto key = std::to_string(read(in)); + vars[dst] = vars[src].update( + key, [](std::string x) { return std::move(x) + "bar"; }); + break; + } + case op_update_move: { + auto key = std::to_string(read(in)); + vars[dst] = std::move(vars[src]).update( + key, [](std::string x) { return std::move(x) + "baz"; }); + break; + } + case op_diff: { + auto&& a = vars[src]; + auto&& b = vars[dst]; + diff( + a, + b, + [&](auto&& x) { + assert(!a.count(x.first)); + assert(b.count(x.first)); + }, + [&](auto&& x) { + assert(a.count(x.first)); + assert(!b.count(x.first)); + }, + [&](auto&& x, auto&& y) { + assert(x.first == y.first); + assert(x.second != y.second); + }); + } + default: + break; + }; + return true; + }); +} diff --git a/extra/fuzzer/set-st-str-conflict.cpp b/extra/fuzzer/set-st-str-conflict.cpp new file mode 100644 index 00000000..bf00873a --- /dev/null +++ b/extra/fuzzer/set-st-str-conflict.cpp @@ -0,0 +1,108 @@ +// +// 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_input.hpp" + +#include +#include +#include +#include + +#include + +#include + +using st_memory = immer::memory_policy, + immer::unsafe_refcount_policy, + immer::no_lock_policy, + immer::no_transience_policy, + false>; + +struct colliding_hash_t +{ + std::size_t operator()(const std::string& x) const + { + return std::hash{}(x) & ~((std::size_t{1} << 48) - 1); + } +}; + +extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, + std::size_t size) +{ + constexpr auto var_count = 4; + + using set_t = immer:: + set, st_memory, 3>; + + auto vars = std::array{}; + + auto is_valid_var = [&](auto idx) { return idx >= 0 && idx < var_count; }; + + return fuzzer_input{data, size}.run([&](auto& in) { + enum ops + { + op_insert, + op_erase, + op_insert_move, + op_erase_move, + op_iterate, + op_diff + }; + auto src = read(in, is_valid_var); + auto dst = read(in, is_valid_var); + assert(vars[src].impl().check_champ()); + switch (read(in)) { + case op_insert: { + auto value = std::to_string(read(in)); + vars[dst] = vars[src].insert(value); + break; + } + case op_erase: { + auto value = std::to_string(read(in)); + vars[dst] = vars[src].erase(value); + break; + } + case op_insert_move: { + auto value = std::to_string(read(in)); + vars[dst] = std::move(vars[src]).insert(value); + break; + } + case op_erase_move: { + auto value = std::to_string(read(in)); + vars[dst] = std::move(vars[src]).erase(value); + break; + } + case op_iterate: { + auto srcv = vars[src]; + for (const auto& v : srcv) { + vars[dst] = vars[dst].insert(v); + } + break; + } + case op_diff: { + auto&& a = vars[src]; + auto&& b = vars[dst]; + diff( + a, + b, + [&](auto&& x) { + assert(!a.count(x)); + assert(b.count(x)); + }, + [&](auto&& x) { + assert(a.count(x)); + assert(!b.count(x)); + }, + [&](auto&& x, auto&& y) { assert(false); }); + } + default: + break; + }; + return true; + }); +} From e870de6c0bc34dbe72822dac6b5a76c3a68f7d18 Mon Sep 17 00:00:00 2001 From: David Korczynski Date: Sun, 20 Nov 2022 14:58:55 -0800 Subject: [PATCH 049/104] Add CIFuzz GitHub action --- .github/workflows/cifuzz.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/cifuzz.yml diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml new file mode 100644 index 00000000..9b1a2060 --- /dev/null +++ b/.github/workflows/cifuzz.yml @@ -0,0 +1,26 @@ +name: CIFuzz +on: [pull_request] +jobs: + Fuzzing: + runs-on: ubuntu-latest + steps: + - name: Build Fuzzers + id: build + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master + with: + oss-fuzz-project-name: 'immer' + dry-run: false + language: c++ + - name: Run Fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master + with: + oss-fuzz-project-name: 'immer' + fuzz-seconds: 300 + dry-run: false + language: c++ + - name: Upload Crash + uses: actions/upload-artifact@v3 + if: failure() && steps.build.outcome == 'success' + with: + name: artifacts + path: ./out/artifacts From 2c8ceb61600bb03ac26a979d3bfec707bf23826e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Tue, 22 Nov 2022 21:00:30 +0100 Subject: [PATCH 050/104] Add identity API --- immer/array.hpp | 11 ++++++++++- immer/flex_vector.hpp | 11 +++++++++++ immer/map.hpp | 8 ++++++++ immer/set.hpp | 8 ++++++++ immer/vector.hpp | 11 +++++++++++ test/map/generic.ipp | 6 ++++++ test/set/generic.ipp | 4 ++++ test/vector/generic.ipp | 6 ++++++ 8 files changed, 64 insertions(+), 1 deletion(-) diff --git a/immer/array.hpp b/immer/array.hpp index d32f13fc..f71477c2 100644 --- a/immer/array.hpp +++ b/immer/array.hpp @@ -107,7 +107,8 @@ class array /*! * Returns an iterator pointing just after the last element of the - * collection. It does not allocate memory and its complexity is @f$ O(1) @f$. + * collection. It does not allocate memory and its complexity is @f$ O(1) + * @f$. */ IMMER_NODISCARD iterator end() const { return impl_.data() + impl_.size; } @@ -308,6 +309,14 @@ class array return transient_type{std::move(impl_)}; } + /*! + * Returns a value that can be used as identity for the container. If two + * values have the same identity, they are guaranteed to be equal and to + * contain the same objects. However, two equal containers are not + * guaranteed to have the same identity. + */ + void* identity() const { return impl_.ptr; } + // Semi-private const impl_t& impl() const { return impl_; } diff --git a/immer/flex_vector.hpp b/immer/flex_vector.hpp index 6ee7a320..79f29db0 100644 --- a/immer/flex_vector.hpp +++ b/immer/flex_vector.hpp @@ -503,6 +503,17 @@ class flex_vector IMMER_NODISCARD transient_type transient() const& { return impl_; } IMMER_NODISCARD transient_type transient() && { return std::move(impl_); } + /*! + * Returns a value that can be used as identity for the container. If two + * values have the same identity, they are guaranteed to be equal and to + * contain the same objects. However, two equal containers are not + * guaranteed to have the same identity. + */ + std::pair identity() const + { + return {impl_.root, impl_.tail}; + } + // Semi-private const impl_t& impl() const { return impl_; } diff --git a/immer/map.hpp b/immer/map.hpp index d13f3b48..cab8e5d5 100644 --- a/immer/map.hpp +++ b/immer/map.hpp @@ -467,6 +467,14 @@ class map return transient_type{std::move(impl_)}; } + /*! + * Returns a value that can be used as identity for the container. If two + * values have the same identity, they are guaranteed to be equal and to + * contain the same objects. However, two equal containers are not + * guaranteed to have the same identity. + */ + void* identity() const { return impl_.root; } + // Semi-private const impl_t& impl() const { return impl_; } diff --git a/immer/set.hpp b/immer/set.hpp index 73ccdcf8..df4192f5 100644 --- a/immer/set.hpp +++ b/immer/set.hpp @@ -251,6 +251,14 @@ class set return transient_type{std::move(impl_)}; } + /*! + * Returns a value that can be used as identity for the container. If two + * values have the same identity, they are guaranteed to be equal and to + * contain the same objects. However, two equal containers are not + * guaranteed to have the same identity. + */ + void* identity() const { return impl_.root; } + // Semi-private const impl_t& impl() const { return impl_; } diff --git a/immer/vector.hpp b/immer/vector.hpp index 3cf69ddb..8c8ab05d 100644 --- a/immer/vector.hpp +++ b/immer/vector.hpp @@ -340,6 +340,17 @@ class vector IMMER_NODISCARD transient_type transient() const& { return impl_; } IMMER_NODISCARD transient_type transient() && { return std::move(impl_); } + /*! + * Returns a value that can be used as identity for the container. If two + * values have the same identity, they are guaranteed to be equal and to + * contain the same objects. However, two equal containers are not + * guaranteed to have the same identity. + */ + std::pair identity() const + { + return {impl_.root, impl_.tail}; + } + // Semi-private const impl_t& impl() const { return impl_; } diff --git a/test/map/generic.ipp b/test/map/generic.ipp index ee6bbe86..2107a4bc 100644 --- a/test/map/generic.ipp +++ b/test/map/generic.ipp @@ -88,6 +88,7 @@ TEST_CASE("instantiation") { auto v = MAP_T{}; CHECK(v.size() == 0u); + CHECK(v.identity() == MAP_T{}.identity()); } } @@ -173,6 +174,11 @@ TEST_CASE("equals and setting") CHECK(v.update_if_exists(42, [](auto&& x) { return x + 1; }) == v.set(42, 43)); + CHECK(v.update_if_exists(1234, [](auto&& x) { return x + 1; }).identity() == + v.identity()); + CHECK(v.update_if_exists(42, [](auto&& x) { return x + 1; }).identity() != + v.set(42, 43).identity()); + #if IMMER_DEBUG_STATS std::cout << (v.impl().get_debug_stats() + v.impl().get_debug_stats()) .get_summary(); diff --git a/test/set/generic.ipp b/test/set/generic.ipp index 5c3cd199..9a9d35af 100644 --- a/test/set/generic.ipp +++ b/test/set/generic.ipp @@ -171,12 +171,16 @@ TEST_CASE("basic insertion") { auto v1 = SET_T{}; CHECK(v1.count(42) == 0); + CHECK(v1.identity() == SET_T{}.identity()); auto v2 = v1.insert(42); CHECK(v1.count(42) == 0); CHECK(v2.count(42) == 1); + CHECK(v1.identity() != v2.identity()); auto v3 = v2.insert(42); + // it would maybe be nice if this was not the case, but it is... + CHECK(v2.identity() != v3.identity()); CHECK(v1.count(42) == 0); CHECK(v2.count(42) == 1); CHECK(v3.count(42) == 1); diff --git a/test/vector/generic.ipp b/test/vector/generic.ipp index 5697c272..0e2dc5cb 100644 --- a/test/vector/generic.ipp +++ b/test/vector/generic.ipp @@ -127,6 +127,12 @@ TEST_CASE("push back one element") CHECK(v1.size() == 0u); CHECK(v2.size() == 1u); CHECK(v2[0] == 42); + + // basic identity rules + auto v3 = v2; + CHECK(v1.identity() != v2.identity()); + CHECK(v3.identity() == v2.identity()); + CHECK(v1.identity() == VECTOR_T{}.identity()); } SECTION("many elements") From de91bdb28e254f24c2dd66938a0405fea7d6b0c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Wed, 23 Nov 2022 18:18:21 +0100 Subject: [PATCH 051/104] Prepare release 0.8.0 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2362ecb7..a8cfbd3b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_policy(SET CMP0048 NEW) # enable project VERSION cmake_policy(SET CMP0056 NEW) # honor link flags in try_compile() list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") -project(immer VERSION 0.7.0) +project(immer VERSION 0.8.0) if (NOT MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -Wno-unused-parameter -Wno-extended-offsetof -Wno-c++17-extensions -Wno-c++1z-extensions -Wno-unknown-warning-option -Wno-type-limits") From ad5acab104acaf45b764d5f868b022b4ee4bdce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Wed, 23 Nov 2022 18:21:45 +0100 Subject: [PATCH 052/104] Upgrade cachix action --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e0ef3016..2ca6f7cf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,7 +11,7 @@ jobs: with: nix_path: nixpkgs=channel:nixos-unstable install_url: "https://releases.nixos.org/nix/nix-2.3.16/install" - - uses: cachix/cachix-action@v10 + - uses: cachix/cachix-action@v12 with: name: arximboldi signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' @@ -35,7 +35,7 @@ jobs: with: nix_path: nixpkgs=channel:nixos-unstable install_url: "https://releases.nixos.org/nix/nix-2.3.16/install" - - uses: cachix/cachix-action@v10 + - uses: cachix/cachix-action@v12 with: name: arximboldi signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' @@ -99,7 +99,7 @@ jobs: with: nix_path: nixpkgs=channel:nixos-unstable install_url: "https://releases.nixos.org/nix/nix-2.3.16/install" - - uses: cachix/cachix-action@v10 + - uses: cachix/cachix-action@v12 with: name: arximboldi signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' From 6b05223054c1e4af45d4f827905a89d4f8984318 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Thu, 24 Nov 2022 22:19:25 +0100 Subject: [PATCH 053/104] Upgrade install-nix-action --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2ca6f7cf..d86d14dd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,7 +7,7 @@ jobs: - uses: actions/checkout@v2 with: fetch-depth: 0 # needed for fetchGit in default.nix - - uses: cachix/install-nix-action@v14.1 + - uses: cachix/install-nix-action@v18 with: nix_path: nixpkgs=channel:nixos-unstable install_url: "https://releases.nixos.org/nix/nix-2.3.16/install" @@ -31,7 +31,7 @@ jobs: - uses: actions/checkout@v2 with: submodules: true - - uses: cachix/install-nix-action@v14.1 + - uses: cachix/install-nix-action@v18 with: nix_path: nixpkgs=channel:nixos-unstable install_url: "https://releases.nixos.org/nix/nix-2.3.16/install" @@ -95,7 +95,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: cachix/install-nix-action@v14.1 + - uses: cachix/install-nix-action@v18 with: nix_path: nixpkgs=channel:nixos-unstable install_url: "https://releases.nixos.org/nix/nix-2.3.16/install" From a814bcd5929dc6730f5d713a870fcb1e23af0bf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Thu, 24 Nov 2022 23:39:07 +0100 Subject: [PATCH 054/104] Do not install specific old version of Nix --- .github/workflows/test.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d86d14dd..701e76b2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,7 +10,6 @@ jobs: - uses: cachix/install-nix-action@v18 with: nix_path: nixpkgs=channel:nixos-unstable - install_url: "https://releases.nixos.org/nix/nix-2.3.16/install" - uses: cachix/cachix-action@v12 with: name: arximboldi @@ -34,7 +33,6 @@ jobs: - uses: cachix/install-nix-action@v18 with: nix_path: nixpkgs=channel:nixos-unstable - install_url: "https://releases.nixos.org/nix/nix-2.3.16/install" - uses: cachix/cachix-action@v12 with: name: arximboldi @@ -98,7 +96,6 @@ jobs: - uses: cachix/install-nix-action@v18 with: nix_path: nixpkgs=channel:nixos-unstable - install_url: "https://releases.nixos.org/nix/nix-2.3.16/install" - uses: cachix/cachix-action@v12 with: name: arximboldi From 20eb876b80a7827bd3fa207bf9358deddc5fc27e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Thu, 22 Dec 2022 12:45:05 +0100 Subject: [PATCH 055/104] Add benchmarks for checking CHAMP memory usage The exection of these is not automated yet. We suggest running them with: valgrind --tool=massif --detailed-freq=1 --max-snapshots=1000 The output file can then be studied with a tool like: massif-visualizer --- benchmark/set/memory/basic-string-long.cpp | 5 + benchmark/set/memory/basic-string-short.cpp | 5 + benchmark/set/memory/basic-unsigned.cpp | 5 + benchmark/set/memory/exp-string-long.cpp | 5 + benchmark/set/memory/exp-string-short.cpp | 5 + benchmark/set/memory/exp-unsigned.cpp | 5 + benchmark/set/memory/lin-string-long.cpp | 5 + benchmark/set/memory/lin-string-short.cpp | 5 + benchmark/set/memory/lin-unsigned.cpp | 5 + benchmark/set/memory/memory.hpp | 451 ++++++++++++++++++++ shell.nix | 1 + 11 files changed, 497 insertions(+) create mode 100644 benchmark/set/memory/basic-string-long.cpp create mode 100644 benchmark/set/memory/basic-string-short.cpp create mode 100644 benchmark/set/memory/basic-unsigned.cpp create mode 100644 benchmark/set/memory/exp-string-long.cpp create mode 100644 benchmark/set/memory/exp-string-short.cpp create mode 100644 benchmark/set/memory/exp-unsigned.cpp create mode 100644 benchmark/set/memory/lin-string-long.cpp create mode 100644 benchmark/set/memory/lin-string-short.cpp create mode 100644 benchmark/set/memory/lin-unsigned.cpp create mode 100644 benchmark/set/memory/memory.hpp diff --git a/benchmark/set/memory/basic-string-long.cpp b/benchmark/set/memory/basic-string-long.cpp new file mode 100644 index 00000000..cd39f7f6 --- /dev/null +++ b/benchmark/set/memory/basic-string-long.cpp @@ -0,0 +1,5 @@ +#define IMMER_BENCHMARK_MEMORY_STRING_LONG 1 + +#include "memory.hpp" + +int main() { return main_basic(); } diff --git a/benchmark/set/memory/basic-string-short.cpp b/benchmark/set/memory/basic-string-short.cpp new file mode 100644 index 00000000..2c4d47cc --- /dev/null +++ b/benchmark/set/memory/basic-string-short.cpp @@ -0,0 +1,5 @@ +#define IMMER_BENCHMARK_MEMORY_STRING_SHORT 1 + +#include "memory.hpp" + +int main() { return main_basic(); } diff --git a/benchmark/set/memory/basic-unsigned.cpp b/benchmark/set/memory/basic-unsigned.cpp new file mode 100644 index 00000000..0b02c404 --- /dev/null +++ b/benchmark/set/memory/basic-unsigned.cpp @@ -0,0 +1,5 @@ +#define IMMER_BENCHMARK_MEMORY_UNSIGNED 1 + +#include "memory.hpp" + +int main() { return main_basic(); } diff --git a/benchmark/set/memory/exp-string-long.cpp b/benchmark/set/memory/exp-string-long.cpp new file mode 100644 index 00000000..866869d9 --- /dev/null +++ b/benchmark/set/memory/exp-string-long.cpp @@ -0,0 +1,5 @@ +#define IMMER_BENCHMARK_MEMORY_STRING_LONG 1 + +#include "memory.hpp" + +int main() { return main_exp(); } diff --git a/benchmark/set/memory/exp-string-short.cpp b/benchmark/set/memory/exp-string-short.cpp new file mode 100644 index 00000000..e198c070 --- /dev/null +++ b/benchmark/set/memory/exp-string-short.cpp @@ -0,0 +1,5 @@ +#define IMMER_BENCHMARK_MEMORY_STRING_SHORT 1 + +#include "memory.hpp" + +int main() { return main_exp(); } diff --git a/benchmark/set/memory/exp-unsigned.cpp b/benchmark/set/memory/exp-unsigned.cpp new file mode 100644 index 00000000..2bed0de5 --- /dev/null +++ b/benchmark/set/memory/exp-unsigned.cpp @@ -0,0 +1,5 @@ +#define IMMER_BENCHMARK_MEMORY_UNSIGNED 1 + +#include "memory.hpp" + +int main() { return main_exp(); } diff --git a/benchmark/set/memory/lin-string-long.cpp b/benchmark/set/memory/lin-string-long.cpp new file mode 100644 index 00000000..ce3d5bd1 --- /dev/null +++ b/benchmark/set/memory/lin-string-long.cpp @@ -0,0 +1,5 @@ +#define IMMER_BENCHMARK_MEMORY_STRING_LONG 1 + +#include "memory.hpp" + +int main() { return main_lin(); } diff --git a/benchmark/set/memory/lin-string-short.cpp b/benchmark/set/memory/lin-string-short.cpp new file mode 100644 index 00000000..f1a610b8 --- /dev/null +++ b/benchmark/set/memory/lin-string-short.cpp @@ -0,0 +1,5 @@ +#define IMMER_BENCHMARK_MEMORY_STRING_SHORT 1 + +#include "memory.hpp" + +int main() { return main_lin(); } diff --git a/benchmark/set/memory/lin-unsigned.cpp b/benchmark/set/memory/lin-unsigned.cpp new file mode 100644 index 00000000..c3e9bad2 --- /dev/null +++ b/benchmark/set/memory/lin-unsigned.cpp @@ -0,0 +1,5 @@ +#define IMMER_BENCHMARK_MEMORY_UNSIGNED 1 + +#include "memory.hpp" + +int main() { return main_lin(); } diff --git a/benchmark/set/memory/memory.hpp b/benchmark/set/memory/memory.hpp new file mode 100644 index 00000000..3bdf580a --- /dev/null +++ b/benchmark/set/memory/memory.hpp @@ -0,0 +1,451 @@ +// +// 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 +// + +#pragma once + +// +// These are some experiments to get insights about memory usage with various +// data-structures and configurations. +// +// The idea is to run this inside valgrind's massif tool and see what comes +// out. The following is the libraries that we do check. +// + +// these are for "exp" tests +#include +#include // Phil Nash +#include +#include +#include +#include + +// these are for "lin" tests, which are map based actually +#include +#include // Phil Nash +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +struct generate_string_short +{ + static constexpr auto char_set = + "_-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + static constexpr auto max_length = 15; + static constexpr auto min_length = 4; + + auto operator()() const + { + auto engine = std::default_random_engine{42}; + auto dist = std::uniform_int_distribution{}; + auto gen = std::bind(dist, engine); + + return [=]() mutable { + auto len = gen() % (max_length - min_length) + min_length; + auto str = std::string(len, ' '); + std::generate_n(str.begin(), len, [&] { + return char_set[gen() % sizeof(char_set)]; + }); + return str; + }; + } +}; + +struct generate_string_long +{ + static constexpr auto char_set = + "_-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + static constexpr auto max_length = 256; + static constexpr auto min_length = 32; + + auto operator()() const + { + auto engine = std::default_random_engine{42}; + auto dist = std::uniform_int_distribution{}; + auto gen = std::bind(dist, engine); + + return [=]() mutable { + auto len = gen() % (max_length - min_length) + min_length; + auto str = std::string(len, ' '); + std::generate_n(str.begin(), len, [&] { + return char_set[gen() % sizeof(char_set)]; + }); + return str; + }; + } +}; + +struct generate_unsigned +{ + auto operator()() const + { + auto engine = std::default_random_engine{42}; + auto dist = std::uniform_int_distribution{}; + auto gen = std::bind(dist, engine); + return gen; + } +}; + +namespace basic_params { +constexpr auto N = 1 << 24; + +void take_snapshot(std::size_t i) +{ + std::cerr << " snapshot " << i << " / " << N << std::endl; + // This is not doing what we thing it does.. it is better to control the + // snapshot generation with something like this: + // + // --max-snapshots=1000 --detailed-freq=1 + // + // VALGRIND_MONITOR_COMMAND("detailed_snapshot"); + // VALGRIND_MONITOR_COMMAND("all_snapshots"); +} +} // namespace basic_params + +template +auto benchmark_memory_basic_std() +{ + using namespace basic_params; + + std::cerr << "running... " << boost::core::demangle(typeid(Set).name()) + << std::endl; + + auto rs = std::vector{}; + auto g = Generator{}(); + auto v = Set{}; + + take_snapshot(0); + for (auto i = 0u; i < N; ++i) { + v.insert(g()); + } + take_snapshot(N); + + volatile auto dont_optimize_ = v.size(); + return dont_optimize_; +} + +template +auto benchmark_memory_basic() +{ + using namespace basic_params; + + std::cerr << "running... " << boost::core::demangle(typeid(Set).name()) + << std::endl; + + auto g = Generator{}(); + auto v = Set{}; + + take_snapshot(0); + for (auto i = 0u; i < N; ++i) { + v = std::move(v).insert(g()); + } + take_snapshot(N); + + volatile auto dont_optimize_ = v.size(); + return dont_optimize_; +} + +namespace exp_params { +constexpr auto N = 1 << 22; +constexpr auto E = 2; + +void take_snapshot(std::size_t i) +{ + std::cerr << " snapshot " << i << " / " << N << std::endl; + // This is not doing what we thing it does.. it is better to control the + // snapshot generation with something like this: + // + // --max-snapshots=1000 --detailed-freq=1 + // + // VALGRIND_MONITOR_COMMAND("detailed_snapshot"); + // VALGRIND_MONITOR_COMMAND("all_snapshots"); +} +} // namespace exp_params + +template +auto benchmark_memory_exp_std() +{ + using namespace exp_params; + + std::cerr << "running... " << boost::core::demangle(typeid(Set).name()) + << std::endl; + + auto rs = std::vector{}; + auto g = Generator{}(); + auto v = Set{}; + + take_snapshot(0); + for (auto i = 0u, n = 1u; i < N; ++i) { + if (i == n) { + rs.push_back(v); + n *= E; + take_snapshot(i); + } + v.insert(g()); + } + take_snapshot(N); + + volatile auto dont_optimize_ = rs.data(); + return dont_optimize_; +} + +template +auto benchmark_memory_exp() +{ + using namespace exp_params; + + std::cerr << "running... " << boost::core::demangle(typeid(Set).name()) + << std::endl; + + auto rs = std::vector{}; + auto g = Generator{}(); + auto v = Set{}; + + take_snapshot(0); + for (auto i = 0u, n = 1u; i < N; ++i) { + if (i == n) { + rs.push_back(v); + n *= E; + take_snapshot(i); + } + v = std::move(v).insert(g()); + } + take_snapshot(N); + + volatile auto dont_optimize_ = rs.data(); + return dont_optimize_; +} + +namespace lin_params { +constexpr auto N = 1 << 18; +constexpr auto M = 1 << 7; +constexpr auto S = 1; + +void take_snapshot(std::size_t i) +{ + std::cerr << " snapshot " << i << " / " << M << std::endl; + // This is not doing what we thing it does.. it is better to control the + // snapshot generation with something like this: + // + // --max-snapshots=1000 --detailed-freq=1 + // + // VALGRIND_MONITOR_COMMAND("detailed_snapshot"); + // VALGRIND_MONITOR_COMMAND("all_snapshots"); +} +} // namespace lin_params + +template +auto benchmark_memory_lin_std() +{ + using namespace lin_params; + + std::cerr << "running... " << boost::core::demangle(typeid(Map).name()) + << std::endl; + + auto rs = std::vector{}; + auto ks = std::vector{}; + auto g = Generator{}(); + auto v = Map{}; + auto ug = generate_unsigned{}(); + + take_snapshot(0); + for (auto i = 0u; i < N; ++i) { + auto k = g(); + v.insert({k, 0u}); + ks.push_back(std::move(k)); + } + take_snapshot(N); + + take_snapshot(0); + for (auto i = 0u; i < M; ++i) { + for (auto j = 0u; j < S; ++j) { + auto&& k = ks[ug() % ks.size()]; + ++v.at(k); + } + rs.push_back(v); + take_snapshot(i); + } + take_snapshot(M); + + volatile auto dont_optimize_ = rs.data(); + return dont_optimize_; +} + +template +auto benchmark_memory_lin() +{ + using namespace lin_params; + + std::cerr << "running... " << boost::core::demangle(typeid(Map).name()) + << std::endl; + + auto rs = std::vector{}; + auto ks = std::vector{}; + auto g = Generator{}(); + auto v = Map{}; + auto ug = generate_unsigned{}(); + + take_snapshot(0); + for (auto i = 0u; i < N; ++i) { + auto k = g(); + v = std::move(v).insert({k, 0u}); + ks.push_back(std::move(k)); + } + take_snapshot(N); + + take_snapshot(0); + for (auto i = 0u; i < M; ++i) { + for (auto j = 0u; j < S; ++j) { + auto&& k = ks[ug() % ks.size()]; + v = std::move(v).update(k, [](auto x) { return ++x; }); + } + rs.push_back(v); + take_snapshot(i); + } + take_snapshot(M); + + volatile auto dont_optimize_ = rs.data(); + return dont_optimize_; +} + +#if IMMER_BENCHMARK_MEMORY_STRING_SHORT +using generator__ = generate_string_short; +using t__ = std::string; +#elif IMMER_BENCHMARK_MEMORY_STRING_LONG +using generator__ = generate_string_long; +using t__ = std::string; +#elif IMMER_BENCHMARK_MEMORY_UNSIGNED +using generator__ = generate_unsigned; +using t__ = unsigned; +#else +#error "choose some type!" +#endif + +int main_basic() +{ + benchmark_memory_basic_std>(); + benchmark_memory_basic_std>(); + + // too slow, why? + // benchmark_memory_basic_std>(); + + // very bad... just ignore... + // benchmark_memory_basic_std>(); + + using def_memory = immer::default_memory_policy; + + benchmark_memory_basic< + generator__, + immer::set, std::equal_to, def_memory, 2>>(); + benchmark_memory_basic< + generator__, + immer::set, std::equal_to, def_memory, 3>>(); + benchmark_memory_basic< + generator__, + immer::set, std::equal_to, def_memory, 4>>(); + benchmark_memory_basic< + generator__, + immer::set, std::equal_to, def_memory, 5>>(); + benchmark_memory_basic< + generator__, + immer::set, std::equal_to, def_memory, 6>>(); + + return 0; +} + +int main_exp() +{ + benchmark_memory_exp_std>(); + benchmark_memory_exp_std>(); + + // too slow, why? + // benchmark_memory_exp_std>(); + + // very bad... just ignore... + // benchmark_memory_exp_std>(); + + using def_memory = immer::default_memory_policy; + + benchmark_memory_exp< + generator__, + immer::set, std::equal_to, def_memory, 2>>(); + benchmark_memory_exp< + generator__, + immer::set, std::equal_to, def_memory, 3>>(); + benchmark_memory_exp< + generator__, + immer::set, std::equal_to, def_memory, 4>>(); + benchmark_memory_exp< + generator__, + immer::set, std::equal_to, def_memory, 5>>(); + benchmark_memory_exp< + generator__, + immer::set, std::equal_to, def_memory, 6>>(); + + return 0; +} + +int main_lin() +{ + benchmark_memory_lin_std>(); + benchmark_memory_lin_std>(); + + // too slow, why? + // benchmark_memory_lin_std>(); + + // very bad... just ignore... + // benchmark_memory_lin_std>(); + + using def_memory = immer::default_memory_policy; + + benchmark_memory_lin, + std::equal_to, + def_memory, + 2>>(); + benchmark_memory_lin, + std::equal_to, + def_memory, + 3>>(); + benchmark_memory_lin, + std::equal_to, + def_memory, + 4>>(); + benchmark_memory_lin, + std::equal_to, + def_memory, + 5>>(); + benchmark_memory_lin, + std::equal_to, + def_memory, + 6>>(); + + return 0; +} diff --git a/shell.nix b/shell.nix index 04fd7e92..2879431b 100644 --- a/shell.nix +++ b/shell.nix @@ -58,6 +58,7 @@ tc.stdenv.mkDerivation rec { boost boehmgc fmt + valgrind benchmarks.c_rrb benchmarks.steady benchmarks.chunkedseq From ffef2712f37c6b1afaff471efb2d3ff66b2f0bc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Thu, 22 Dec 2022 16:08:11 +0100 Subject: [PATCH 056/104] Lower constants for memory usage so CI does not timeout --- benchmark/set/memory/memory.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/benchmark/set/memory/memory.hpp b/benchmark/set/memory/memory.hpp index 3bdf580a..7b218ba1 100644 --- a/benchmark/set/memory/memory.hpp +++ b/benchmark/set/memory/memory.hpp @@ -99,7 +99,7 @@ struct generate_unsigned }; namespace basic_params { -constexpr auto N = 1 << 24; +constexpr auto N = 1 << 20; void take_snapshot(std::size_t i) { @@ -158,7 +158,7 @@ auto benchmark_memory_basic() } namespace exp_params { -constexpr auto N = 1 << 22; +constexpr auto N = 1 << 20; constexpr auto E = 2; void take_snapshot(std::size_t i) @@ -229,7 +229,7 @@ auto benchmark_memory_exp() } namespace lin_params { -constexpr auto N = 1 << 18; +constexpr auto N = 1 << 14; constexpr auto M = 1 << 7; constexpr auto S = 1; From 38edbd8be44ab10ec6bea36bcdd4c23d0038fef9 Mon Sep 17 00:00:00 2001 From: Lars Maier Date: Fri, 30 Dec 2022 12:37:08 +0100 Subject: [PATCH 057/104] Attempt to make flex_vector nothrow move constructible/assignable. --- immer/detail/rbts/node.hpp | 24 +++++++++++++++++++----- immer/detail/rbts/rrbtree.hpp | 21 +++++++++++++-------- immer/flex_vector.hpp | 3 +++ 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/immer/detail/rbts/node.hpp b/immer/detail/rbts/node.hpp index e5042005..47624e70 100644 --- a/immer/detail/rbts/node.hpp +++ b/immer/detail/rbts/node.hpp @@ -199,17 +199,23 @@ struct node } static ownee_t& ownee(node_t* x) { return get(x->impl); } - static node_t* make_inner_n(count_t n) + static node_t* make_inner_n_into(void* buffer, std::size_t size, count_t n) { assert(n <= branches); - auto m = heap::allocate(sizeof_inner_n(n)); - auto p = new (m) node_t; + assert(size >= sizeof_inner_n(n)); + auto p = new (buffer) node_t; p->impl.d.data.inner.relaxed = nullptr; #if IMMER_TAGGED_NODE p->impl.d.kind = node_t::kind_t::inner; #endif return p; } + static node_t* make_inner_n(count_t n) + { + assert(n <= branches); + auto m = heap::allocate(sizeof_inner_n(n)); + return make_inner_n_into(m, sizeof_inner_n(n), n); + } static node_t* make_inner_e(edit_t e) { @@ -310,16 +316,24 @@ struct node }); } - static node_t* make_leaf_n(count_t n) + static node_t* make_leaf_n_into(void* buffer, std::size_t size, count_t n) { assert(n <= branches); - auto p = new (heap::allocate(sizeof_leaf_n(n))) node_t; + assert(size >= sizeof_leaf_n(n)); + auto p = new (buffer) node_t; #if IMMER_TAGGED_NODE p->impl.d.kind = node_t::kind_t::leaf; #endif return p; } + static node_t* make_leaf_n(count_t n) + { + assert(n <= branches); + auto m =heap::allocate(sizeof_leaf_n(n)); + return make_leaf_n_into(m, sizeof_leaf_n(n), n); + } + static node_t* make_leaf_e(edit_t e) { auto p = new (heap::allocate(max_sizeof_leaf)) node_t; diff --git a/immer/detail/rbts/rrbtree.hpp b/immer/detail/rbts/rrbtree.hpp index cd76b4c9..23b65853 100644 --- a/immer/detail/rbts/rrbtree.hpp +++ b/immer/detail/rbts/rrbtree.hpp @@ -49,13 +49,18 @@ struct rrbtree static node_t* empty_root() { - static const auto empty_ = node_t::make_inner_n(0u); + constexpr auto size = node_t::sizeof_inner_n(0); + static std::aligned_storage_t storage; + static const auto empty_ = + node_t::make_inner_n_into(&storage, size, 0u); return empty_->inc(); } static node_t* empty_tail() { - static const auto empty_ = node_t::make_leaf_n(0u); + constexpr auto size = node_t::sizeof_leaf_n(0); + static std::aligned_storage_t storage; + static const auto empty_ = node_t::make_leaf_n_into(&storage, size, 0u); return empty_->inc(); } @@ -90,7 +95,7 @@ struct rrbtree return result; } - rrbtree() + rrbtree() noexcept : size{0} , shift{BL} , root{empty_root()} @@ -99,7 +104,7 @@ struct rrbtree assert(check_tree()); } - rrbtree(size_t sz, shift_t sh, node_t* r, node_t* t) + rrbtree(size_t sz, shift_t sh, node_t* r, node_t* t) noexcept : size{sz} , shift{sh} , root{r} @@ -108,13 +113,13 @@ struct rrbtree assert(check_tree()); } - rrbtree(const rrbtree& other) + rrbtree(const rrbtree& other) noexcept : rrbtree{other.size, other.shift, other.root, other.tail} { inc(); } - rrbtree(rrbtree&& other) + rrbtree(rrbtree&& other) noexcept : rrbtree{} { swap(*this, other); @@ -127,13 +132,13 @@ struct rrbtree return *this; } - rrbtree& operator=(rrbtree&& other) + rrbtree& operator=(rrbtree&& other) noexcept { swap(*this, other); return *this; } - friend void swap(rrbtree& x, rrbtree& y) + friend void swap(rrbtree& x, rrbtree& y) noexcept { using std::swap; swap(x.size, y.size); diff --git a/immer/flex_vector.hpp b/immer/flex_vector.hpp index 79f29db0..18c7e0d0 100644 --- a/immer/flex_vector.hpp +++ b/immer/flex_vector.hpp @@ -616,4 +616,7 @@ class flex_vector impl_t impl_ = {}; }; +static_assert(std::is_nothrow_move_constructible>::value); +static_assert(std::is_nothrow_move_assignable>::value); + } // namespace immer From 9c67e60b44d25db67473710883f097ab6e776aab Mon Sep 17 00:00:00 2001 From: Lars Maier Date: Fri, 30 Dec 2022 12:53:41 +0100 Subject: [PATCH 058/104] Fixing assertions. --- immer/flex_vector.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/immer/flex_vector.hpp b/immer/flex_vector.hpp index 18c7e0d0..f970db91 100644 --- a/immer/flex_vector.hpp +++ b/immer/flex_vector.hpp @@ -616,7 +616,9 @@ class flex_vector impl_t impl_ = {}; }; -static_assert(std::is_nothrow_move_constructible>::value); -static_assert(std::is_nothrow_move_assignable>::value); +static_assert(std::is_nothrow_move_constructible>::value, + "flex_vector is not nothrow move constructible"); +static_assert(std::is_nothrow_move_assignable>::value, + "flex_vector is not nothrow move assignable"); } // namespace immer From b7756f5e85e79374571526118fa026f3d6c441ce Mon Sep 17 00:00:00 2001 From: Lars Maier Date: Tue, 3 Jan 2023 12:33:33 +0100 Subject: [PATCH 059/104] Use lambda for initialization. --- immer/detail/rbts/node.hpp | 2 +- immer/detail/rbts/rrbtree.hpp | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/immer/detail/rbts/node.hpp b/immer/detail/rbts/node.hpp index 47624e70..55a512b0 100644 --- a/immer/detail/rbts/node.hpp +++ b/immer/detail/rbts/node.hpp @@ -330,7 +330,7 @@ struct node static node_t* make_leaf_n(count_t n) { assert(n <= branches); - auto m =heap::allocate(sizeof_leaf_n(n)); + auto m = heap::allocate(sizeof_leaf_n(n)); return make_leaf_n_into(m, sizeof_leaf_n(n), n); } diff --git a/immer/detail/rbts/rrbtree.hpp b/immer/detail/rbts/rrbtree.hpp index 23b65853..53d661be 100644 --- a/immer/detail/rbts/rrbtree.hpp +++ b/immer/detail/rbts/rrbtree.hpp @@ -50,17 +50,20 @@ struct rrbtree static node_t* empty_root() { constexpr auto size = node_t::sizeof_inner_n(0); - static std::aligned_storage_t storage; - static const auto empty_ = - node_t::make_inner_n_into(&storage, size, 0u); + static const auto empty_ = [&]{ + static std::aligned_storage_t storage; + return node_t::make_inner_n_into(&storage, size, 0u); + }(); return empty_->inc(); } static node_t* empty_tail() { constexpr auto size = node_t::sizeof_leaf_n(0); - static std::aligned_storage_t storage; - static const auto empty_ = node_t::make_leaf_n_into(&storage, size, 0u); + static const auto empty_ = [&]{ + static std::aligned_storage_t storage; + return node_t::make_leaf_n_into(&storage, size, 0u); + }(); return empty_->inc(); } From ec81042a26ad42efd26400216ec48220a4a126ae Mon Sep 17 00:00:00 2001 From: urbanwe Date: Thu, 5 Jan 2023 09:45:29 +0100 Subject: [PATCH 060/104] Fix wrong order in operator<(T2, box) --- immer/box.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/immer/box.hpp b/immer/box.hpp index c0a6a97a..711ac78f 100644 --- a/immer/box.hpp +++ b/immer/box.hpp @@ -219,9 +219,9 @@ IMMER_NODISCARD auto operator!=(T2&& b, const box& a) template IMMER_NODISCARD auto operator<(T2&& b, const box& a) -> std::enable_if_t, std::decay_t>::value, - decltype(a.get() < b)> + decltype(b < a.get())> { - return a.get() < b; + return b < a.get(); } } // namespace immer From 54ba35145b37672072564f1498448efb8e88a37b Mon Sep 17 00:00:00 2001 From: Lily Wang <494550702@qq.com> Date: Wed, 11 Jan 2023 19:10:02 -0800 Subject: [PATCH 061/104] Add export CMake Config version file --- CMakeLists.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index a8cfbd3b..dd5adac3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,6 +99,14 @@ install(TARGETS immer EXPORT ImmerConfig) install(EXPORT ImmerConfig DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Immer") install(DIRECTORY immer DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") +include(CMakePackageConfigHelpers) +write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/ImmerConfigVersion.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion ) + +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/ImmerConfigVersion.cmake" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Immer" ) + # development target to be used in tests, examples, benchmarks... immer_canonicalize_cmake_booleans( DISABLE_FREE_LIST From 3630ec4080a40e247182a4c239db1901d43696cb Mon Sep 17 00:00:00 2001 From: Lily Wang <94091114+LilyWangLL@users.noreply.github.com> Date: Thu, 12 Jan 2023 14:54:48 +0800 Subject: [PATCH 062/104] Add quotes --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dd5adac3..0ab73bac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -101,7 +101,7 @@ install(DIRECTORY immer DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") include(CMakePackageConfigHelpers) write_basic_package_version_file( - ${CMAKE_CURRENT_BINARY_DIR}/ImmerConfigVersion.cmake + "${CMAKE_CURRENT_BINARY_DIR}/ImmerConfigVersion.cmake" VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion ) From c69ad466a17d3fd026f2e6dea5f3f4309db426d5 Mon Sep 17 00:00:00 2001 From: tocic Date: Wed, 5 Apr 2023 10:41:29 +0300 Subject: [PATCH 063/104] Fix typos --- README.rst | 4 ++-- doc/design.rst | 12 ++++++------ doc/memory.rst | 8 ++++---- extra/guile/README.rst | 2 +- extra/python/README.rst | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/README.rst b/README.rst index abf4dcea..19506520 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,6 @@ .. image:: https://github.com/arximboldi/immer/workflows/test/badge.svg :target: https://github.com/arximboldi/immer/actions?query=workflow%3Atest+branch%3Amaster - :alt: Github Actions Badge + :alt: GitHub Actions Badge .. image:: https://codecov.io/gh/arximboldi/immer/branch/master/graph/badge.svg :target: https://codecov.io/gh/arximboldi/immer @@ -74,7 +74,7 @@ Example For a **complete example** check `Ewig, a simple didactic text-editor `_ built with this library. You may also wanna check `Lager, a Redux-like library - `_ for writting interactive + `_ for writing interactive software in C++ using a value-oriented design. diff --git a/doc/design.rst b/doc/design.rst index f8dcaacb..f1848f53 100644 --- a/doc/design.rst +++ b/doc/design.rst @@ -79,14 +79,14 @@ removing it, as in: :end-before: move-good/end So, is it bad style then to use ``const`` as much as possible? I -wouldn't say so and it is advisable when ``std::move()`` is not used. -An alternative style is to not use ``const`` but adopt an `AAA-style -`_ (*Almost Always use Auto*). This way, it is easy to look for +wouldn't say so, and it is advisable when ``std::move()`` is not used. +An alternative style is to not use ``const`` but adopt an `AAA-style`_ +(*Almost Always use Auto*). This way, it is easy to look for mutations by looking for lines that contain ``=`` but no ``auto``. Remember that when using our immutable containers ``operator=`` is the only way to mutate a variable. -.. _aaa: https://herbsutter.com/2013/08/12/gotw-94-solution-aaa-style-almost-always-auto/ +.. _AAA-style: https://herbsutter.com/2013/08/12/gotw-94-solution-aaa-style-almost-always-auto/ .. admonition:: Why does ``const`` prevent move semantics? @@ -149,7 +149,7 @@ mutate part of the internal data structure in place when possible. If you don't like this syntax, :doc:`transients` may be used to obtain similar performance benefits. -.. admonition:: Assigment guarantees +.. admonition:: Assignment guarantees From the language point of view, the only requirement on moved from values is that they should still be destructible. We provide the @@ -208,7 +208,7 @@ Efficient batch manipulations Sometimes you may write a function that needs to do multiple changes to a container. Like most code you write with this library, this function is *pure*: it takes one container value in, and produces a -new container value out, no side-effects. +new container value out, no side effects. Let's say we want to write a function that inserts all integers in the range :math:`[first, last)` into an immutable vector: diff --git a/doc/memory.rst b/doc/memory.rst index 9dbfaf56..ba8ec175 100644 --- a/doc/memory.rst +++ b/doc/memory.rst @@ -39,7 +39,7 @@ Memory policy Example: tracing garbage collection ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -It is note worthy that all aspects of a +It is noteworthy that all aspects of a :cpp:class:`immer::memory_policy` are not completely orthogonal. Let's say you want to use a `tracing garbage collector`_. Actually, we @@ -79,7 +79,7 @@ allocate objects of those sizes. .. _metafunction class: http://www.boost.org/doc/libs/1_62_0/libs/mpl/doc/refmanual/metafunction-class.html -A **heap** is a type with a methods ``void* allocate(std::size_t)`` +A **heap** is a type with methods ``void* allocate(std::size_t)`` and ``void deallocate(void*)`` that return and release raw memory. For a canonical model of this concept check the :cpp:class:`immer::cpp_heap`. @@ -95,8 +95,8 @@ For a canonical model of this concept check the On the other hand, having some **scoped state** does make sense for some use-cases of immutable data structures. For example, we might want to support variations of `region - based allocation`_. This interface might evolve to evolve - to support some kind of non-global state to accommodate + based allocation`_. This interface might evolve to support + some kind of non-global state to accommodate these use cases. .. _region based allocation: https://en.wikipedia.org/wiki/Region-based_memory_management diff --git a/extra/guile/README.rst b/extra/guile/README.rst index e59d9977..879850b9 100644 --- a/extra/guile/README.rst +++ b/extra/guile/README.rst @@ -15,7 +15,7 @@ things like: **Do you want to help** making these bindings complete and production ready? Drop a line at `immer@sinusoid.al - `_ or `open an issue on Github + `_ or `open an issue on GitHub `_ .. _GNU Guile: https://www.gnu.org/software/guile/ diff --git a/extra/python/README.rst b/extra/python/README.rst index 73d6ee3f..4b6eb575 100644 --- a/extra/python/README.rst +++ b/extra/python/README.rst @@ -15,7 +15,7 @@ research for the `ICFP'17 paper`_. The interface is quite **Do you want to help** making these bindings complete and production ready? Drop a line at `immer@sinusoid.al - `_ or `open an issue on Github + `_ or `open an issue on GitHub `_ Installation From 17915a5881003151d668fac15f2f38050325d419 Mon Sep 17 00:00:00 2001 From: Dan Larkin-York Date: Mon, 17 Apr 2023 20:20:20 +0000 Subject: [PATCH 064/104] Add GDB pretty-printing support --- tools/gdb_pretty_printers/__init__.py | 1 + tools/gdb_pretty_printers/autoload.py | 11 + tools/gdb_pretty_printers/printers.py | 432 ++++++++++++++++++++++++++ 3 files changed, 444 insertions(+) create mode 100644 tools/gdb_pretty_printers/__init__.py create mode 100644 tools/gdb_pretty_printers/autoload.py create mode 100644 tools/gdb_pretty_printers/printers.py diff --git a/tools/gdb_pretty_printers/__init__.py b/tools/gdb_pretty_printers/__init__.py new file mode 100644 index 00000000..1bb8bf6d --- /dev/null +++ b/tools/gdb_pretty_printers/__init__.py @@ -0,0 +1 @@ +# empty diff --git a/tools/gdb_pretty_printers/autoload.py b/tools/gdb_pretty_printers/autoload.py new file mode 100644 index 00000000..727a7777 --- /dev/null +++ b/tools/gdb_pretty_printers/autoload.py @@ -0,0 +1,11 @@ +import gdb.printing +import os + +path = os.path.dirname(__file__) +if not path in sys.path: + sys.path.append(path) +from printers import immer_lookup_function + +gdb.printing.register_pretty_printer(gdb.current_objfile(), immer_lookup_function) + +print("immer gdb pretty-printers loaded") \ No newline at end of file diff --git a/tools/gdb_pretty_printers/printers.py b/tools/gdb_pretty_printers/printers.py new file mode 100644 index 00000000..4f1fb653 --- /dev/null +++ b/tools/gdb_pretty_printers/printers.py @@ -0,0 +1,432 @@ +# Sourced from https://gist.github.com/dwightguth/283afe96b60b3793f3c02036701457f8 +# with light modifications. + +import gdb.printing +import decimal +import traceback +import re + +MAX = 1 << 64 - 1 + + +class Relaxed: + def __init__(self, node, shift, relaxed, it): + self.node = node + self.shift = shift + self.relaxed = relaxed + self.it = it + self.B = self.node.type.target().template_argument(2) + self.BL = self.node.type.target().template_argument(3) + + def index(self, idx): + offset = idx >> self.shift + while self.relaxed.dereference()['d']['sizes'][offset] <= idx: + offset += 1 + return offset + + def towards(self, idx): + offset = self.index(idx) + left_size = self.relaxed.dereference()['d']['sizes'][offset - 1] if offset else 0 + child = self.it.inner(self.node)[offset] + is_leaf = self.shift == self.BL + next_size = self.relaxed.dereference()['d']['sizes'][offset] - left_size + next_idx = idx - left_size + if is_leaf: + return self.it.visit_leaf(LeafSub(child, next_size), next_idx) + else: + return self.it.visit_maybe_relaxed_sub(child, self.shift - self.B, next_size, next_idx) + + +class LeafSub: + def __init__(self, node, count): + self.node = node + self.count_ = count + self.BL = self.node.type.target().template_argument(3) + self.MASK = (1 << self.BL) - 1 + + def index(self, idx): + return idx & self.MASK + + def count(self): + return self.count_ + + +class FullLeaf: + def __init__(self, node): + self.node = node + self.BL = self.node.type.target().template_argument(3) + self.BRANCHES = 1 << self.BL + self.MASK = self.BRANCHES - 1 + + def index(self, idx): + return idx & self.MASK + + def count(self): + return self.BRANCHES + + +class Leaf: + def __init__(self, node, size): + self.node = node + self.size = size + self.BL = self.node.type.target().template_argument(3) + self.MASK = (1 << self.BL) - 1 + + def index(self, idx): + return idx & self.MASK + + def count(self): + return self.index(self.size - 1) + 1 + + +class RegularSub: + def __init__(self, node, shift, size, it): + self.node = node + self.shift = shift + self.size = size + self.it = it + self.B = self.node.type.target().template_argument(2) + self.MASK = (1 << self.B) - 1 + + def towards(self, idx): + offset = self.index(idx) + count = self.count() + return self.it.towards_regular(self, idx, offset, count) + + def index(self, idx): + return (idx >> self.shift) & self.MASK + + def count(self): + return self.subindex(self.size - 1) + 1 + + def subindex(self, idx): + return idx >> self.shift + + +class Regular: + def __init__(self, node, shift, size, it): + self.node = node + self.shift = shift + self.size = size + self.it = it + self.B = self.node.type.target().template_argument(2) + self.MASK = (1 << self.B) - 1 + + def index(self, idx): + return (idx >> self.shift) & self.MASK + + def count(self): + return self.index(self.size - 1) + 1 + + def towards(self, idx): + offset = self.index(idx) + count = self.count() + return self.it.towards_regular(self, idx, offset, count) + + +class Full: + def __init__(self, node, shift, it): + self.node = node + self.shift = shift + self.it = it + self.B = self.node.type.target().template_argument(2) + self.BL = self.node.type.target().template_argument(3) + self.MASK = (1 << self.B) - 1 + + def index(self, idx): + return (idx >> self.shift) & self.MASK + + def towards(self, idx): + offset = self.index(idx) + is_leaf = self.shift == self.BL + child = self.it.inner(self.node)[offset] + if is_leaf: + return self.it.visit_leaf(FullLeaf(child), idx) + else: + return Full(child, self.shift - self.B, self.it).towards(idx) + + +class ListIter: + def __init__(self, val): + self.v = val['impl_'] + self.size = self.v['size'] + self.i = 0 + self.curr = (None, MAX, MAX) + self.node_ptr_ptr = self.v['root'].type.pointer() + self.B = self.v['root'].type.target().template_argument(2) + self.BL = self.v['root'].type.target().template_argument(3) + + def __iter__(self): + return self + + def __next__(self): + if self.i == self.size: + raise StopIteration + if self.i < self.curr[1] or self.i >= self.curr[2]: + self.curr = self.region() + result = ('[%d]' % self.i, self.curr[0][self.i - self.curr[1]].cast( + gdb.lookup_type(self.v.type.template_argument(0).name))) + self.i += 1 + return result + + def region(self): + tail_off = self.tail_offset() + if self.i >= tail_off: + return (self.leaf(self.v['tail']), tail_off, self.size) + else: + subs = self.visit_maybe_relaxed_sub(self.v['root'], self.v['shift'], tail_off, self.i) + first = self.i - subs[1] + end = first + subs[2] + return (subs[0], first, end) + + def tail_offset(self): + r = self.relaxed(self.v['root']) + if r: + return r.dereference()['d']['sizes'][r.dereference()['d']['count'] - 1] + elif self.size: + return (self.size - 1) & ~self.leaf_mask() + else: + return 0 + + def relaxed(self, node): + return node.dereference()['impl']['d']['data']['inner']['relaxed'] + + def leaf(self, node): + return node.dereference()['impl']['d']['data']['leaf']['buffer'].address + + def inner(self, node): + return node.dereference()['impl']['d']['data']['inner']['buffer'].address.reinterpret_cast( + self.node_ptr_ptr) + + def visit_maybe_relaxed_sub(self, node, shift, size, idx): + relaxed = self.relaxed(node) + if relaxed: + return Relaxed(node, shift, relaxed, self).towards(idx) + else: + return RegularSub(node, shift, size, self).towards(idx) + + def visit_leaf(self, pos, idx): + return (self.leaf(pos.node), pos.index(idx), pos.count()) + + # pos = node, idx = full, offset = shifted & masked, count = shifted + def towards_regular(self, pos, idx, offset, count): + is_leaf = pos.shift == self.BL + child = self.inner(pos.node)[offset] + is_full = offset + 1 != count + if is_full: + if is_leaf: + return self.visit_leaf(FullLeaf(child), idx) + else: + return Full(child, pos.shift - self.B, self).towards(idx) + elif is_leaf: + return self.visit_leaf(Leaf(child, pos.size), idx) + else: + return Regular(child, pos.shift - self.B, pos.size, self).towards(idx) + + def leaf_mask(self): + return (1 << self.BL) - 1 + + +def popcount(x): + b = 0 + while x > 0: + x &= x - 1 + b += 1 + return b + + +class ChampIter: + def __init__(self, val): + self.depth = 0 + self.count = 0 + v = val['impl_']['root'] + self.node_ptr_ptr = v.type.pointer() + m = self.datamap(v) + if m: + self.cur = self.values(v) + self.end = self.values(v) + popcount(m) + else: + self.cur = None + self.end = None + self.path = [v.address] + self.B = v.type.target().template_argument(4) + self.MAX_DEPTH = ((8 * 8) + self.B - 1) / 8 + self.ensure_valid() + + def __iter__(self): + return self + + def __next__(self): + if self.cur == None: + raise StopIteration + result = self.cur.dereference() + self.cur += 1 + self.count += 1 + self.ensure_valid() + return result + + def ensure_valid(self): + while self.cur == self.end: + while self.step_down(): + if self.cur != self.end: + return + if not self.step_right(): + self.cur = None + self.end = None + return + + def step_down(self): + if self.depth < self.MAX_DEPTH: + parent = self.path[self.depth].dereference() + if self.nodemap(parent): + self.depth += 1 + self.path.append(self.children(parent)) + child = self.path[self.depth] + if self.depth < self.MAX_DEPTH: + m = self.datamap(child) + if m: + self.cur = self.values(child) + self.end = self.cur + popcount(m) + else: + self.cur = self.collisions(child) + self.end = self.cur = self.collision_count(child) + return True + return False + + def step_right(self): + while self.depth > 0: + parent = self.path[self.depth - 1].dereference() + last = self.children(parent) + popcount(self.nodemap(parent)) + next_ = self.path[self.depth] + 1 + if next_ < last: + self.path[self.depth] = next_ + child = self.path[self.depth].dereference() + if self.depth < self.MAX_DEPTH: + m = self.datamap(child) + if m: + self.cur = self.values(child) + self.end = self.cur + popcount(m) + else: + self.cur = self.collisions(child) + self.end = self.cur + self.collision_count(child) + return True + self.depth -= 1 + self.path.pop() + return False + + def values(self, node): + return node.dereference()['impl']['d']['data']['inner']['values'].dereference( + )['d']['buffer'].address.cast(self.T_ptr) + + def children(self, node): + return node.dereference()['impl']['d']['data']['inner']['buffer'].address.cast( + self.node_ptr_ptr) + + def datamap(self, node): + return node.dereference()['impl']['d']['data']['inner']['datamap'] + + def nodemap(self, node): + return node.dereference()['impl']['d']['data']['inner']['nodemap'] + + def collision_count(self, node): + return node.dereference()['impl']['d']['data']['collision']['count'] + + def collisions(self, node): + return node.dereference()['impl']['d']['data']['collision']['buffer'].address.cast( + self.T_ptr) + + +class MapIter(ChampIter): + def __init__(self, val): + self.T_ptr = gdb.lookup_type("std::pair<" + val.type.template_argument(0).name + ", " + + val.type.template_argument(1).name + ">").pointer() + ChampIter.__init__(self, val) + self.pair = None + + def __next__(self): + if self.pair: + result = ('[%d]' % self.count, self.pair['second']) + self.pair = None + return result + self.pair = super().__next__() + return ('[%d]' % self.count, self.pair['first']) + + +class SetIter(ChampIter): + def __init__(self, val): + self.T_ptr = gdb.lookup_type(val.type.template_argument(0).name).pointer() + ChampIter.__init__(self, val) + + def __next__(self): + return ('[%d]' % self.count, super().__next__()) + + +def num_elements(num): + return '1 element' if num == 1 else '%d elements' % num + + +class MapPrinter: + "Print an immer::map" + + def __init__(self, val): + self.val = val + + def to_string(self): + return 'immer::map with %s' % num_elements(self.val['impl_']['size']) + + def children(self): + return MapIter(self.val) + + def display_hint(self): + return 'map' + + +class SetPrinter: + "Prints an immer::set" + + def __init__(self, val): + self.val = val + + def to_string(self): + return 'immer::set with %s' % num_elements(self.val['impl_']['size']) + + def children(self): + return SetIter(self.val) + + +class ListPrinter: + "Prints an immer::vector or immer::flex_vector" + + def __init__(self, val, typename): + self.val = val + self.typename = typename + + def to_string(self): + return '%s of length %d' % (self.typename, int(self.val['impl_']['size'])) + + def children(self): + return ListIter(self.val) + + def display_hint(self): + return 'array' + + +def immer_lookup_function(val): + compiled_rx = re.compile('^([a-zA-Z0-9_:]+)(<.*>)?$') + typename = gdb.types.get_basic_type(val.type).tag + if not typename: + return None + match = compiled_rx.match(typename) + if not match: + return None + + basename = match.group(1) + if basename == "immer::map": + return MapPrinter(val) + elif basename == "immer::set": + return SetPrinter(val) + elif basename == "immer::vector": + return ListPrinter(val, "immer::vector") + elif basename == "immer::flex_vector": + return ListPrinter(val, "immer::flex_vector") + return None From 0220ba4389d397ceb214af01b153c4fcba00c315 Mon Sep 17 00:00:00 2001 From: Dan Larkin-York Date: Tue, 18 Apr 2023 12:57:27 +0000 Subject: [PATCH 065/104] Add table printer --- tools/gdb_pretty_printers/printers.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tools/gdb_pretty_printers/printers.py b/tools/gdb_pretty_printers/printers.py index 4f1fb653..3b8a4b67 100644 --- a/tools/gdb_pretty_printers/printers.py +++ b/tools/gdb_pretty_printers/printers.py @@ -394,6 +394,19 @@ def children(self): return SetIter(self.val) +class TablePrinter: + "Prints an immer::table" + + def __init__(self, val): + self.val = val + + def to_string(self): + return 'immer::table with %s' % num_elements(self.val['impl_']['size']) + + def children(self): + return SetIter(self.val) + + class ListPrinter: "Prints an immer::vector or immer::flex_vector" @@ -425,6 +438,8 @@ def immer_lookup_function(val): return MapPrinter(val) elif basename == "immer::set": return SetPrinter(val) + elif basename == "immer::table": + return TablePrinter(val) elif basename == "immer::vector": return ListPrinter(val, "immer::vector") elif basename == "immer::flex_vector": From 9b8e1b340eb9b05650cb628297dd9524cbc53fbc Mon Sep 17 00:00:00 2001 From: Dan Larkin-York Date: Tue, 18 Apr 2023 14:40:13 +0000 Subject: [PATCH 066/104] Add array printer --- tools/gdb_pretty_printers/printers.py | 40 ++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/tools/gdb_pretty_printers/printers.py b/tools/gdb_pretty_printers/printers.py index 3b8a4b67..c33e7efd 100644 --- a/tools/gdb_pretty_printers/printers.py +++ b/tools/gdb_pretty_printers/printers.py @@ -9,6 +9,26 @@ MAX = 1 << 64 - 1 +class ArrayIter: + def __init__(self, val): + self.val_ptr = val.type.template_argument(0).pointer() + self.v = val['impl_'] + self.size = self.v['size'] + self.i = 0 + + def __iter__(self): + return self + + def __next__(self): + if self.i == self.size: + raise StopIteration + ptr = self.v['ptr'] + data = ptr.dereference()['impl']['d']['buffer'].address.reinterpret_cast(self.val_ptr) + result = ('[%d]' % self.i, data[self.i]) + self.i += 1 + return result + + class Relaxed: def __init__(self, node, shift, relaxed, it): self.node = node @@ -365,6 +385,22 @@ def num_elements(num): return '1 element' if num == 1 else '%d elements' % num +class ArrayPrinter: + "Prints an immer::array" + + def __init__(self, val): + self.val = val + + def to_string(self): + return 'immer::array with %s' % num_elements(self.val['impl_']['size']) + + def children(self): + return ArrayIter(self.val) + + def display_hint(self): + return 'array' + + class MapPrinter: "Print an immer::map" @@ -434,7 +470,9 @@ def immer_lookup_function(val): return None basename = match.group(1) - if basename == "immer::map": + if basename == "immer::array": + return ArrayPrinter(val) + elif basename == "immer::map": return MapPrinter(val) elif basename == "immer::set": return SetPrinter(val) From 7ff66e0934c3e2153ff4d9500d90c8d544a9615d Mon Sep 17 00:00:00 2001 From: Dan Larkin-York Date: Tue, 18 Apr 2023 20:05:01 +0000 Subject: [PATCH 067/104] Add .gdbinit file to autoload pretty printers --- .gdbinit | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gdbinit diff --git a/.gdbinit b/.gdbinit new file mode 100644 index 00000000..999e2fce --- /dev/null +++ b/.gdbinit @@ -0,0 +1,2 @@ +# Load pretty printers +source tools/gdb_pretty_printers/autoload.py \ No newline at end of file From a983d6fe1a3f4728db0728b32fed0ee78ccbc302 Mon Sep 17 00:00:00 2001 From: Dan Larkin-York Date: Fri, 28 Apr 2023 12:47:34 +0000 Subject: [PATCH 068/104] Make pretty-printer loading more robust --- tools/gdb_pretty_printers/autoload.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/gdb_pretty_printers/autoload.py b/tools/gdb_pretty_printers/autoload.py index 727a7777..e3d6d059 100644 --- a/tools/gdb_pretty_printers/autoload.py +++ b/tools/gdb_pretty_printers/autoload.py @@ -1,11 +1,14 @@ import gdb.printing import os +import sys +import inspect -path = os.path.dirname(__file__) +filename = inspect.getframeinfo(inspect.currentframe()).filename +path = os.path.dirname(os.path.abspath(filename)) if not path in sys.path: sys.path.append(path) from printers import immer_lookup_function gdb.printing.register_pretty_printer(gdb.current_objfile(), immer_lookup_function) -print("immer gdb pretty-printers loaded") \ No newline at end of file +print("immer gdb pretty-printers loaded") From ffb051721b2d06736a2117e49b5fab93af67bdb7 Mon Sep 17 00:00:00 2001 From: vector-of-bool Date: Tue, 2 May 2023 00:13:05 -0600 Subject: [PATCH 069/104] Fixes and tests for C++20 Range compatibility Define tests on each container that it satisfies the intended range concept. (This will transitively validate the iterators as well) Fix: champ_iterator() was not default-constructible, which is a requirement for iterators Fix: iterator_facade::operator[] returned a reference_proxy, while iterator_facade::operator* returns a ReferenceT. The random_access_iterator concept requires that the operators return an identical type. --- immer/detail/hamts/champ_iterator.hpp | 2 ++ immer/detail/iterator_facade.hpp | 15 +-------------- test/array_transient/default.cpp | 4 ++++ test/flex_vector/generic.ipp | 2 ++ test/flex_vector_transient/generic.ipp | 5 +++++ test/map/generic.ipp | 2 ++ test/map_transient/generic.ipp | 6 ++++++ test/set/generic.ipp | 3 +++ test/set_transient/generic.ipp | 5 +++++ test/table/generic.ipp | 2 ++ test/table_transient/generic.ipp | 5 +++++ test/util.hpp | 12 ++++++++++++ test/vector/generic.ipp | 2 ++ test/vector_transient/generic.ipp | 2 ++ 14 files changed, 53 insertions(+), 14 deletions(-) diff --git a/immer/detail/hamts/champ_iterator.hpp b/immer/detail/hamts/champ_iterator.hpp index 373198bb..5ff31060 100644 --- a/immer/detail/hamts/champ_iterator.hpp +++ b/immer/detail/hamts/champ_iterator.hpp @@ -27,6 +27,8 @@ struct champ_iterator using tree_t = champ; using node_t = typename tree_t::node_t; + champ_iterator() = default; + struct end_t {}; diff --git a/immer/detail/iterator_facade.hpp b/immer/detail/iterator_facade.hpp index 359f3d91..e5729dde 100644 --- a/immer/detail/iterator_facade.hpp +++ b/immer/detail/iterator_facade.hpp @@ -82,19 +82,6 @@ class iterator_facade std::is_base_of::value; - class reference_proxy - { - friend iterator_facade; - DerivedT iter_; - - reference_proxy(DerivedT iter) - : iter_{std::move(iter)} - {} - - public: - operator ReferenceT() const { return *iter_; } - }; - const DerivedT& derived() const { static_assert(std::is_base_of::value, @@ -111,7 +98,7 @@ class iterator_facade public: ReferenceT operator*() const { return access_t::dereference(derived()); } PointerT operator->() const { return &access_t::dereference(derived()); } - reference_proxy operator[](DifferenceTypeT n) const + ReferenceT operator[](DifferenceTypeT n) const { static_assert(is_random_access, ""); return derived() + n; diff --git a/test/array_transient/default.cpp b/test/array_transient/default.cpp index aa603c7b..cfa789b3 100644 --- a/test/array_transient/default.cpp +++ b/test/array_transient/default.cpp @@ -14,6 +14,10 @@ #include "../vector_transient/generic.ipp" +IMMER_RANGES_CHECK(std::ranges::contiguous_range>); +IMMER_RANGES_CHECK( + std::ranges::contiguous_range>); + TEST_CASE("array_transient default constructor compiles") { immer::array_transient transient; diff --git a/test/flex_vector/generic.ipp b/test/flex_vector/generic.ipp index a3fee264..2a127dbe 100644 --- a/test/flex_vector/generic.ipp +++ b/test/flex_vector/generic.ipp @@ -29,6 +29,8 @@ #error "define the vector template to use in VECTOR_T" #endif +IMMER_RANGES_CHECK(std::ranges::random_access_range>); + template > auto make_test_flex_vector(unsigned min, unsigned max) { diff --git a/test/flex_vector_transient/generic.ipp b/test/flex_vector_transient/generic.ipp index 8c93cd16..bcaaa3a0 100644 --- a/test/flex_vector_transient/generic.ipp +++ b/test/flex_vector_transient/generic.ipp @@ -33,6 +33,11 @@ #error "define the vector template to use in VECTOR_T" #endif +IMMER_RANGES_CHECK( + std::ranges::random_access_range>); +IMMER_RANGES_CHECK( + std::ranges::random_access_range>); + template > auto make_test_flex_vector(unsigned min, unsigned max) { diff --git a/test/map/generic.ipp b/test/map/generic.ipp index 2107a4bc..a049fefc 100644 --- a/test/map/generic.ipp +++ b/test/map/generic.ipp @@ -24,6 +24,8 @@ #include #include +IMMER_RANGES_CHECK(std::ranges::forward_range>); + using memory_policy_t = MAP_T::memory_policy_type; template diff --git a/test/map_transient/generic.ipp b/test/map_transient/generic.ipp index 3e20abed..097eaddc 100644 --- a/test/map_transient/generic.ipp +++ b/test/map_transient/generic.ipp @@ -6,6 +6,8 @@ // See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt // +#include "test/util.hpp" + #include #ifndef MAP_T @@ -16,6 +18,10 @@ #error "define the map template to use in MAP_TRANSIENT_T" #endif +IMMER_RANGES_CHECK(std::ranges::forward_range>); +IMMER_RANGES_CHECK( + std::ranges::forward_range>); + TEST_CASE("instantiate") { auto t = MAP_TRANSIENT_T{}; diff --git a/test/set/generic.ipp b/test/set/generic.ipp index 9a9d35af..2a5e6f35 100644 --- a/test/set/generic.ipp +++ b/test/set/generic.ipp @@ -25,6 +25,9 @@ using memory_policy_t = SET_T::memory_policy_type; +IMMER_RANGES_CHECK(std::input_iterator::iterator>); +IMMER_RANGES_CHECK(std::ranges::forward_range>); + template auto make_generator() { diff --git a/test/set_transient/generic.ipp b/test/set_transient/generic.ipp index 3653d48c..6fa0ecfa 100644 --- a/test/set_transient/generic.ipp +++ b/test/set_transient/generic.ipp @@ -6,6 +6,8 @@ // See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt // +#include "test/util.hpp" + #include #ifndef SET_T @@ -16,6 +18,9 @@ #error "define the set template to use in SET_TRANSIENT_T" #endif +IMMER_RANGES_CHECK(std::ranges::forward_range>); +IMMER_RANGES_CHECK(std::ranges::forward_range>); + TEST_CASE("instantiate") { auto t = SET_TRANSIENT_T{}; diff --git a/test/table/generic.ipp b/test/table/generic.ipp index b9a5aa67..144da7ae 100644 --- a/test/table/generic.ipp +++ b/test/table/generic.ipp @@ -63,6 +63,8 @@ using table_map = immer::table, SETUP_T::memory_policy, SETUP_T::bits>; +IMMER_RANGES_CHECK(std::ranges::forward_range>); + template auto make_generator() { diff --git a/test/table_transient/generic.ipp b/test/table_transient/generic.ipp index e0dd41a3..fd518ce2 100644 --- a/test/table_transient/generic.ipp +++ b/test/table_transient/generic.ipp @@ -6,6 +6,8 @@ // See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt // +#include "test/util.hpp" + #include #ifndef SETUP_T @@ -25,6 +27,9 @@ struct Item } }; +IMMER_RANGES_CHECK(std::ranges::forward_range>); +IMMER_RANGES_CHECK(std::ranges::forward_range>); + TEST_CASE("instantiate") { auto t = SETUP_T::table_transient{}; diff --git a/test/util.hpp b/test/util.hpp index 41904f5a..30100dd0 100644 --- a/test/util.hpp +++ b/test/util.hpp @@ -12,6 +12,18 @@ #include #include +#include // For __cpp_lib_ranges + +// If we have ranges, include the header for the concepts, and define +// IMMER_RANGES_CHECK() to expand to a static_assert of the argument, otherwise +// define it as a no-op static_assert. +#if __cpp_lib_ranges +#include +#define IMMER_RANGES_CHECK(...) static_assert(__VA_ARGS__) +#else +#define IMMER_RANGES_CHECK(...) static_assert(true, "") +#endif + namespace { struct identity_t diff --git a/test/vector/generic.ipp b/test/vector/generic.ipp index 0e2dc5cb..e613eef4 100644 --- a/test/vector/generic.ipp +++ b/test/vector/generic.ipp @@ -25,6 +25,8 @@ using namespace std::string_literals; #error "define the vector template to use in VECTOR_T" #endif +IMMER_RANGES_CHECK(std::ranges::random_access_range>); + template > auto make_test_vector(unsigned min, unsigned max) { diff --git a/test/vector_transient/generic.ipp b/test/vector_transient/generic.ipp index 6cd25a56..a889380d 100644 --- a/test/vector_transient/generic.ipp +++ b/test/vector_transient/generic.ipp @@ -20,6 +20,8 @@ #error "define the vector template to use in VECTOR_TRANSIENT_T" #endif +IMMER_RANGES_CHECK(std::ranges::random_access_range>); + template > auto make_test_vector(unsigned min, unsigned max) { From e382feab06b86b92127b791ed3f98d0776448fbd Mon Sep 17 00:00:00 2001 From: Bob Kocisko Date: Tue, 2 May 2023 14:14:15 -0400 Subject: [PATCH 070/104] Fixing annoying unused local variable warning --- immer/heap/cpp_heap.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/immer/heap/cpp_heap.hpp b/immer/heap/cpp_heap.hpp index 119fceec..37897548 100644 --- a/immer/heap/cpp_heap.hpp +++ b/immer/heap/cpp_heap.hpp @@ -33,7 +33,7 @@ struct cpp_heap * `allocate`. One must not use nor deallocate again a memory * region that once it has been deallocated. */ - static void deallocate(std::size_t size, void* data) + static void deallocate(std::size_t, void* data) { ::operator delete(data); } From ae8abe0ce07c27fe79da27adebef449b0f3a64ed Mon Sep 17 00:00:00 2001 From: vector-of-bool Date: Sat, 6 May 2023 16:49:26 -0600 Subject: [PATCH 071/104] Return a direct reference from iterator operator[] --- immer/detail/iterator_facade.hpp | 2 +- test/flex_vector/generic.ipp | 15 ++++++++++++++- test/vector/generic.ipp | 12 ++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/immer/detail/iterator_facade.hpp b/immer/detail/iterator_facade.hpp index e5729dde..1d5578c8 100644 --- a/immer/detail/iterator_facade.hpp +++ b/immer/detail/iterator_facade.hpp @@ -101,7 +101,7 @@ class iterator_facade ReferenceT operator[](DifferenceTypeT n) const { static_assert(is_random_access, ""); - return derived() + n; + return *(derived() + n); } friend bool operator==(const DerivedT& a, const DerivedT& b) diff --git a/test/flex_vector/generic.ipp b/test/flex_vector/generic.ipp index 2a127dbe..d6955dc8 100644 --- a/test/flex_vector/generic.ipp +++ b/test/flex_vector/generic.ipp @@ -29,7 +29,8 @@ #error "define the vector template to use in VECTOR_T" #endif -IMMER_RANGES_CHECK(std::ranges::random_access_range>); +IMMER_RANGES_CHECK( + std::ranges::random_access_range>); template > auto make_test_flex_vector(unsigned min, unsigned max) @@ -108,6 +109,18 @@ TEST_CASE("push_front") } } +TEST_CASE("random_access iteration") +{ + auto v = make_test_flex_vector(0, 10); + auto iter = v.begin(); + CHECK(*iter == 0); + CHECK(iter[0] == 0); + CHECK(iter[3] == 3); + CHECK(iter[9] == 9); + iter += 4; + CHECK(iter[-4] == 0); +} + TEST_CASE("concat") { #if IMMER_SLOW_TESTS diff --git a/test/vector/generic.ipp b/test/vector/generic.ipp index e613eef4..98cea994 100644 --- a/test/vector/generic.ipp +++ b/test/vector/generic.ipp @@ -120,6 +120,18 @@ TEST_CASE("at") #endif } +TEST_CASE("random_access iteration") +{ + auto v = VECTOR_T{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + auto iter = v.begin(); + CHECK(*iter == 0); + CHECK(iter[0] == 0); + CHECK(iter[3] == 3); + CHECK(iter[9] == 9); + iter += 4; + CHECK(iter[-4] == 0); +} + TEST_CASE("push back one element") { SECTION("one element") From 04c7d90fc46cae5c24d8cafdab86692bf550c87e Mon Sep 17 00:00:00 2001 From: tocic Date: Mon, 8 May 2023 11:29:40 +0300 Subject: [PATCH 072/104] Use alternative logo in dark mode Closes https://github.com/arximboldi/immer/issues/254. --- README.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 19506520..834dbe6a 100644 --- a/README.rst +++ b/README.rst @@ -13,7 +13,10 @@ .. raw:: html - Logotype + + + Logotype + .. include:introduction/start From 4513c2f80db335ae62aed35d9e67f7a15b278e34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Mon, 8 May 2023 11:57:39 +0200 Subject: [PATCH 073/104] Upgrade install-nix-action --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 701e76b2..253215f1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,7 +7,7 @@ jobs: - uses: actions/checkout@v2 with: fetch-depth: 0 # needed for fetchGit in default.nix - - uses: cachix/install-nix-action@v18 + - uses: cachix/install-nix-action@v20 with: nix_path: nixpkgs=channel:nixos-unstable - uses: cachix/cachix-action@v12 @@ -30,7 +30,7 @@ jobs: - uses: actions/checkout@v2 with: submodules: true - - uses: cachix/install-nix-action@v18 + - uses: cachix/install-nix-action@v20 with: nix_path: nixpkgs=channel:nixos-unstable - uses: cachix/cachix-action@v12 @@ -93,7 +93,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: cachix/install-nix-action@v18 + - uses: cachix/install-nix-action@v20 with: nix_path: nixpkgs=channel:nixos-unstable - uses: cachix/cachix-action@v12 From 1b66f4ad2a8cb0190f436f03a64b76a3aca5fdad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Tue, 16 May 2023 12:52:54 +0200 Subject: [PATCH 074/104] Fix includes https://issues.guix.gnu.org/issue/63330 --- test/oss-fuzz/flex-vector-0.cpp | 2 +- test/oss-fuzz/flex-vector-bo-0.cpp | 2 +- test/oss-fuzz/flex-vector-gc-0.cpp | 2 +- test/oss-fuzz/map-st-2.cpp | 2 +- test/oss-fuzz/map-st-str-0.cpp | 2 +- test/oss-fuzz/set-gc-1.cpp | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/oss-fuzz/flex-vector-0.cpp b/test/oss-fuzz/flex-vector-0.cpp index f29eeb9f..c5a8d18b 100644 --- a/test/oss-fuzz/flex-vector-0.cpp +++ b/test/oss-fuzz/flex-vector-0.cpp @@ -17,7 +17,7 @@ #define IMMER_FUZZED_TRACE_ENABLE 0 #if IMMER_FUZZED_TRACE_ENABLE -#include +#include #define IMMER_FUZZED_TRACE(...) fmt::print(std::cerr, __VA_ARGS__) #else #define IMMER_FUZZED_TRACE(...) diff --git a/test/oss-fuzz/flex-vector-bo-0.cpp b/test/oss-fuzz/flex-vector-bo-0.cpp index b821734f..6dc7be44 100644 --- a/test/oss-fuzz/flex-vector-bo-0.cpp +++ b/test/oss-fuzz/flex-vector-bo-0.cpp @@ -17,7 +17,7 @@ #define IMMER_FUZZED_TRACE_ENABLE 1 #if IMMER_FUZZED_TRACE_ENABLE -#include +#include #define IMMER_FUZZED_TRACE(...) fmt::print(std::cerr, __VA_ARGS__) #else #define IMMER_FUZZED_TRACE(...) diff --git a/test/oss-fuzz/flex-vector-gc-0.cpp b/test/oss-fuzz/flex-vector-gc-0.cpp index 75dd1ae1..1a669c16 100644 --- a/test/oss-fuzz/flex-vector-gc-0.cpp +++ b/test/oss-fuzz/flex-vector-gc-0.cpp @@ -18,7 +18,7 @@ #define IMMER_FUZZED_TRACE_ENABLE 0 #if IMMER_FUZZED_TRACE_ENABLE -#include +#include #define IMMER_FUZZED_TRACE(...) fmt::print(std::cerr, __VA_ARGS__) #else #define IMMER_FUZZED_TRACE(...) diff --git a/test/oss-fuzz/map-st-2.cpp b/test/oss-fuzz/map-st-2.cpp index 61c8a858..af022e76 100644 --- a/test/oss-fuzz/map-st-2.cpp +++ b/test/oss-fuzz/map-st-2.cpp @@ -19,7 +19,7 @@ #define IMMER_FUZZED_TRACE_ENABLE 0 #if IMMER_FUZZED_TRACE_ENABLE -#include +#include #define IMMER_FUZZED_TRACE(...) fmt::print(std::cerr, __VA_ARGS__) #else #define IMMER_FUZZED_TRACE(...) diff --git a/test/oss-fuzz/map-st-str-0.cpp b/test/oss-fuzz/map-st-str-0.cpp index 1aee3767..3e85308a 100644 --- a/test/oss-fuzz/map-st-str-0.cpp +++ b/test/oss-fuzz/map-st-str-0.cpp @@ -21,7 +21,7 @@ #define IMMER_FUZZED_TRACE_ENABLE 1 #if IMMER_FUZZED_TRACE_ENABLE -#include +#include #define IMMER_FUZZED_TRACE(...) fmt::print(std::cerr, __VA_ARGS__) #else #define IMMER_FUZZED_TRACE(...) diff --git a/test/oss-fuzz/set-gc-1.cpp b/test/oss-fuzz/set-gc-1.cpp index 19b318e6..daee36b5 100644 --- a/test/oss-fuzz/set-gc-1.cpp +++ b/test/oss-fuzz/set-gc-1.cpp @@ -21,7 +21,7 @@ #define IMMER_FUZZED_TRACE_ENABLE 0 #if IMMER_FUZZED_TRACE_ENABLE -#include +#include #define IMMER_FUZZED_TRACE(...) fmt::print(std::cerr, __VA_ARGS__) #else #define IMMER_FUZZED_TRACE(...) From 8a165e7bd290ba459d6b467ededdbfddb38390f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pedro=20Bol=C3=ADvar=20Puente?= Date: Tue, 16 May 2023 12:55:27 +0200 Subject: [PATCH 075/104] Upgrade Nix in Actions --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 701e76b2..253215f1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,7 +7,7 @@ jobs: - uses: actions/checkout@v2 with: fetch-depth: 0 # needed for fetchGit in default.nix - - uses: cachix/install-nix-action@v18 + - uses: cachix/install-nix-action@v20 with: nix_path: nixpkgs=channel:nixos-unstable - uses: cachix/cachix-action@v12 @@ -30,7 +30,7 @@ jobs: - uses: actions/checkout@v2 with: submodules: true - - uses: cachix/install-nix-action@v18 + - uses: cachix/install-nix-action@v20 with: nix_path: nixpkgs=channel:nixos-unstable - uses: cachix/cachix-action@v12 @@ -93,7 +93,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: cachix/install-nix-action@v18 + - uses: cachix/install-nix-action@v20 with: nix_path: nixpkgs=channel:nixos-unstable - uses: cachix/cachix-action@v12 From 5125b83afb7f862adabc1d67431b9721eba1cf4a Mon Sep 17 00:00:00 2001 From: Colugo <68328892+colugomusic@users.noreply.github.com> Date: Wed, 24 May 2023 14:03:02 +0100 Subject: [PATCH 076/104] Fix MSVC compile error C2059 --- immer/detail/hamts/node.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/immer/detail/hamts/node.hpp b/immer/detail/hamts/node.hpp index 6cee4e1b..d5c5f457 100644 --- a/immer/detail/hamts/node.hpp +++ b/immer/detail/hamts/node.hpp @@ -297,7 +297,7 @@ struct node new (vp + 1) T{std::move(x2)}; } IMMER_CATCH (...) { - vp->~T(); + vp->T::~T(); IMMER_RETHROW; } } From 813e891e259888934967af166f3f8c2fca5ce17c Mon Sep 17 00:00:00 2001 From: Colugo <68328892+colugomusic@users.noreply.github.com> Date: Wed, 24 May 2023 21:44:12 +0100 Subject: [PATCH 077/104] Fix MSVC compile error C2975 --- immer/detail/rbts/rrbtree.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/immer/detail/rbts/rrbtree.hpp b/immer/detail/rbts/rrbtree.hpp index 53d661be..71d217b9 100644 --- a/immer/detail/rbts/rrbtree.hpp +++ b/immer/detail/rbts/rrbtree.hpp @@ -49,8 +49,8 @@ struct rrbtree static node_t* empty_root() { - constexpr auto size = node_t::sizeof_inner_n(0); static const auto empty_ = [&]{ + constexpr auto size = node_t::sizeof_inner_n(0); static std::aligned_storage_t storage; return node_t::make_inner_n_into(&storage, size, 0u); }(); @@ -59,8 +59,8 @@ struct rrbtree static node_t* empty_tail() { - constexpr auto size = node_t::sizeof_leaf_n(0); static const auto empty_ = [&]{ + constexpr auto size = node_t::sizeof_leaf_n(0); static std::aligned_storage_t storage; return node_t::make_leaf_n_into(&storage, size, 0u); }(); From 30fb619ce725ab0bc817b3a50d9e8f79bdec823f Mon Sep 17 00:00:00 2001 From: Colugo <68328892+colugomusic@users.noreply.github.com> Date: Wed, 24 May 2023 21:45:20 +0100 Subject: [PATCH 078/104] Use destroy_at to call explicit destructor in make_inner_n --- immer/detail/hamts/node.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/immer/detail/hamts/node.hpp b/immer/detail/hamts/node.hpp index d5c5f457..31c6c295 100644 --- a/immer/detail/hamts/node.hpp +++ b/immer/detail/hamts/node.hpp @@ -297,7 +297,7 @@ struct node new (vp + 1) T{std::move(x2)}; } IMMER_CATCH (...) { - vp->T::~T(); + std::destroy_at(vp); IMMER_RETHROW; } } From 6672e57e26f63e776ccf24bbf572a1a66e188532 Mon Sep 17 00:00:00 2001 From: Colugo <68328892+colugomusic@users.noreply.github.com> Date: Wed, 24 May 2023 21:50:39 +0100 Subject: [PATCH 079/104] Remove unused capture default --- immer/detail/rbts/rrbtree.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/immer/detail/rbts/rrbtree.hpp b/immer/detail/rbts/rrbtree.hpp index 71d217b9..8a727f11 100644 --- a/immer/detail/rbts/rrbtree.hpp +++ b/immer/detail/rbts/rrbtree.hpp @@ -49,7 +49,7 @@ struct rrbtree static node_t* empty_root() { - static const auto empty_ = [&]{ + static const auto empty_ = []{ constexpr auto size = node_t::sizeof_inner_n(0); static std::aligned_storage_t storage; return node_t::make_inner_n_into(&storage, size, 0u); @@ -59,7 +59,7 @@ struct rrbtree static node_t* empty_tail() { - static const auto empty_ = [&]{ + static const auto empty_ = []{ constexpr auto size = node_t::sizeof_leaf_n(0); static std::aligned_storage_t storage; return node_t::make_leaf_n_into(&storage, size, 0u); From 77c2f422fed0094e728624f5f29fb95f60a25073 Mon Sep 17 00:00:00 2001 From: Colugo <68328892+colugomusic@users.noreply.github.com> Date: Wed, 24 May 2023 21:56:24 +0100 Subject: [PATCH 080/104] Fix whitespace --- immer/detail/rbts/rrbtree.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/immer/detail/rbts/rrbtree.hpp b/immer/detail/rbts/rrbtree.hpp index 8a727f11..843921ba 100644 --- a/immer/detail/rbts/rrbtree.hpp +++ b/immer/detail/rbts/rrbtree.hpp @@ -50,7 +50,7 @@ struct rrbtree static node_t* empty_root() { static const auto empty_ = []{ - constexpr auto size = node_t::sizeof_inner_n(0); + constexpr auto size = node_t::sizeof_inner_n(0); static std::aligned_storage_t storage; return node_t::make_inner_n_into(&storage, size, 0u); }(); @@ -60,7 +60,7 @@ struct rrbtree static node_t* empty_tail() { static const auto empty_ = []{ - constexpr auto size = node_t::sizeof_leaf_n(0); + constexpr auto size = node_t::sizeof_leaf_n(0); static std::aligned_storage_t storage; return node_t::make_leaf_n_into(&storage, size, 0u); }(); From 205180bb1360f917c36b6299b51e351395d2bb08 Mon Sep 17 00:00:00 2001 From: Colugo <68328892+colugomusic@users.noreply.github.com> Date: Wed, 24 May 2023 22:07:01 +0100 Subject: [PATCH 081/104] Add missing #include required by gcc --- immer/detail/hamts/node.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/immer/detail/hamts/node.hpp b/immer/detail/hamts/node.hpp index 31c6c295..a4ad4df3 100644 --- a/immer/detail/hamts/node.hpp +++ b/immer/detail/hamts/node.hpp @@ -15,6 +15,7 @@ #include #include +#include namespace immer { namespace detail { From 5516624d671190888437dff1007acded25f38f94 Mon Sep 17 00:00:00 2001 From: Colugo <68328892+colugomusic@users.noreply.github.com> Date: Thu, 25 May 2023 08:02:11 +0100 Subject: [PATCH 082/104] Manual destroy_at for C++14 --- immer/detail/hamts/node.hpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/immer/detail/hamts/node.hpp b/immer/detail/hamts/node.hpp index 31c6c295..4597c56b 100644 --- a/immer/detail/hamts/node.hpp +++ b/immer/detail/hamts/node.hpp @@ -20,6 +20,11 @@ namespace immer { namespace detail { namespace hamts { +// For C++14 support. +// Calling the destructor inline breaks MSVC in some obscure +// corner cases. +template constexpr void destroy_at(T* p) { p->~T(); } + template Date: Thu, 25 May 2023 08:02:53 +0100 Subject: [PATCH 083/104] Remove unneeded include --- immer/detail/hamts/node.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/immer/detail/hamts/node.hpp b/immer/detail/hamts/node.hpp index 7253317c..4597c56b 100644 --- a/immer/detail/hamts/node.hpp +++ b/immer/detail/hamts/node.hpp @@ -15,7 +15,6 @@ #include #include -#include namespace immer { namespace detail { From daff65041cf46c96b3d01dfef051f4948a739930 Mon Sep 17 00:00:00 2001 From: Colugo <68328892+colugomusic@users.noreply.github.com> Date: Thu, 25 May 2023 08:09:08 +0100 Subject: [PATCH 084/104] Trying to satisfy LLVM. --- immer/detail/hamts/node.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/immer/detail/hamts/node.hpp b/immer/detail/hamts/node.hpp index 4597c56b..7f7dc8b1 100644 --- a/immer/detail/hamts/node.hpp +++ b/immer/detail/hamts/node.hpp @@ -302,7 +302,7 @@ struct node new (vp + 1) T{std::move(x2)}; } IMMER_CATCH (...) { - destroy_at(vp); + immer::detail::hamts::destroy_at(vp); IMMER_RETHROW; } } From 168768c5de98973cacfc2bd4b3aca9ff2c04ab12 Mon Sep 17 00:00:00 2001 From: Pino Toscano Date: Sat, 29 Jul 2023 08:14:56 +0200 Subject: [PATCH 085/104] tests: port dvektor to catch2 This is the only test using doctest, so switch it to catch2 like all the other tests. The only change needed was to switch from SUBCASE() to SECTION(); the test works fine also after the switch. --- test/experimental/dvektor.cpp | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/test/experimental/dvektor.cpp b/test/experimental/dvektor.cpp index 437384a7..f3b0a153 100644 --- a/test/experimental/dvektor.cpp +++ b/test/experimental/dvektor.cpp @@ -15,7 +15,7 @@ #include #include -#include +#include using namespace immer; @@ -27,7 +27,7 @@ TEST_CASE("instantiation") TEST_CASE("push back one element") { - SUBCASE("one element") + SECTION("one element") { const auto v1 = dvektor{}; auto v2 = v1.push_back(42); @@ -36,7 +36,7 @@ TEST_CASE("push back one element") CHECK(v2[0] == 42); } - SUBCASE("many elements") + SECTION("many elements") { const auto n = 666u; auto v = dvektor{}; @@ -56,7 +56,7 @@ TEST_CASE("update") for (auto i = 0u; i < n; ++i) v = v.push_back(i); - SUBCASE("assoc") + SECTION("assoc") { const auto u = v.assoc(3u, 13u); CHECK(u.size() == v.size()); @@ -67,7 +67,7 @@ TEST_CASE("update") CHECK(v[3u] == 3u); } - SUBCASE("assoc further") + SECTION("assoc further") { for (auto i = n; i < 666; ++i) v = v.push_back(i); @@ -88,7 +88,7 @@ TEST_CASE("update") CHECK(v[200u] == 200u); } - SUBCASE("assoc further more") + SECTION("assoc further more") { auto v = immer::dvektor{}; @@ -101,7 +101,7 @@ TEST_CASE("update") } } - SUBCASE("update") + SECTION("update") { const auto u = v.update(10u, [](auto x) { return x + 10; }); CHECK(u.size() == v.size()); @@ -123,13 +123,13 @@ TEST_CASE("big") for (auto i = 0u; i < n; ++i) v = v.push_back(i); - SUBCASE("read") + SECTION("read") { for (auto i = 0u; i < n; ++i) CHECK(v[i] == i); } - SUBCASE("assoc") + SECTION("assoc") { for (auto i = 0u; i < n; ++i) { v = v.assoc(i, i + 1); @@ -146,7 +146,7 @@ TEST_CASE("iterator") for (auto i = 0u; i < n; ++i) v = v.push_back(i); - SUBCASE("works with range loop") + SECTION("works with range loop") { auto i = 0u; for (const auto& x : v) @@ -154,16 +154,16 @@ TEST_CASE("iterator") CHECK(i == v.size()); } - SUBCASE("works with standard algorithms") + SECTION("works with standard algorithms") { auto s = std::vector(n); std::iota(s.begin(), s.end(), 0u); std::equal(v.begin(), v.end(), s.begin(), s.end()); } - SUBCASE("can go back from end") { CHECK(n - 1 == *--v.end()); } + SECTION("can go back from end") { CHECK(n - 1 == *--v.end()); } - SUBCASE("works with reversed range adaptor") + SECTION("works with reversed range adaptor") { auto r = v | boost::adaptors::reversed; auto i = n; @@ -171,7 +171,7 @@ TEST_CASE("iterator") CHECK(x == --i); } - SUBCASE("works with strided range adaptor") + SECTION("works with strided range adaptor") { auto r = v | boost::adaptors::strided(5); auto i = 0u; @@ -179,14 +179,14 @@ TEST_CASE("iterator") CHECK(x == 5 * i++); } - SUBCASE("works reversed") + SECTION("works reversed") { auto i = n; for (auto iter = v.rbegin(), last = v.rend(); iter != last; ++iter) CHECK(*iter == --i); } - SUBCASE("advance and distance") + SECTION("advance and distance") { auto i1 = v.begin(); auto i2 = i1 + 100; From b133b774d419fded2644c3cf0c71cc422082c873 Mon Sep 17 00:00:00 2001 From: Pino Toscano Date: Sat, 29 Jul 2023 08:16:55 +0200 Subject: [PATCH 086/104] Drop the unused doctest copy There are no more tests using doctest now; hence, drop the old copy of it, and its related build system bits. --- test/CMakeLists.txt | 1 - tools/include/doctest.h | 5696 --------------------------------------- 2 files changed, 5697 deletions(-) delete mode 100644 tools/include/doctest.h diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4528a157..60584c4f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -16,7 +16,6 @@ foreach(_file IN LISTS immer_unit_tests) add_dependencies(tests ${_target}) target_compile_definitions(${_target} PUBLIC -DIMMER_OSS_FUZZ_DATA_PATH="${CMAKE_CURRENT_SOURCE_DIR}/oss-fuzz/data" - DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN CATCH_CONFIG_MAIN) target_link_libraries(${_target} PUBLIC immer-dev) add_test("test/${_output}" ${_output}) diff --git a/tools/include/doctest.h b/tools/include/doctest.h deleted file mode 100644 index 695c271b..00000000 --- a/tools/include/doctest.h +++ /dev/null @@ -1,5696 +0,0 @@ -// ====================================================================== -// == DO NOT MODIFY THIS FILE BY HAND - IT IS AUTO GENERATED BY CMAKE! == -// ====================================================================== -// -// doctest.h - the lightest feature-rich C++ single-header testing framework for unit tests and TDD -// -// Copyright (c) 2016-2017 Viktor Kirilov -// -// Distributed under the MIT Software License -// See accompanying file LICENSE.txt or copy at -// https://opensource.org/licenses/MIT -// -// The documentation can be found at the library's page: -// https://github.com/onqtam/doctest/blob/master/doc/markdown/readme.md -// -// ================================================================================================= -// ================================================================================================= -// ================================================================================================= -// -// The library is heavily influenced by Catch - https://github.com/philsquared/Catch -// which uses the Boost Software License - Version 1.0 -// see here - https://github.com/philsquared/Catch/blob/master/LICENSE_1_0.txt -// -// The concept of subcases (sections in Catch) and expression decomposition are from there. -// Some parts of the code are taken directly: -// - stringification - the detection of "ostream& operator<<(ostream&, const T&)" and StringMaker<> -// - the Approx() helper class for floating point comparison -// - colors in the console -// - breaking into a debugger -// - signal / SEH handling -// - timer -// -// The expression decomposing templates are taken from lest - https://github.com/martinmoene/lest -// which uses the Boost Software License - Version 1.0 -// see here - https://github.com/martinmoene/lest/blob/master/LICENSE_1_0.txt -// -// The type list and the foreach algorithm on it for C++98 are taken from Loki -// - http://loki-lib.sourceforge.net/ -// - https://en.wikipedia.org/wiki/Loki_%28C%2B%2B%29 -// - https://github.com/snaewe/loki-lib -// which uses the MIT Software License -// -// ================================================================================================= -// ================================================================================================= -// ================================================================================================= - -// Suppress this globally (without push/pop) - there is no way to silence it in the -// expression decomposition macros _Pragma() in macros doesn't work for the c++ front-end of g++ -// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55578 -// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=69543 -// Also the warning is completely worthless nowadays - http://stackoverflow.com/questions/14016993 -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic ignored "-Waggregate-return" -#endif - -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunknown-pragmas" -#pragma clang diagnostic ignored "-Wnon-virtual-dtor" -#pragma clang diagnostic ignored "-Wweak-vtables" -#pragma clang diagnostic ignored "-Wpadded" -#pragma clang diagnostic ignored "-Wdeprecated" -#pragma clang diagnostic ignored "-Wmissing-prototypes" -#pragma clang diagnostic ignored "-Wunused-local-typedef" -#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" -#pragma clang diagnostic ignored "-Wc++11-long-long" -#endif // __clang__ - -#if defined(__GNUC__) && !defined(__clang__) -#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 6) -#pragma GCC diagnostic push -#endif // > gcc 4.6 -#pragma GCC diagnostic ignored "-Wunknown-pragmas" -#pragma GCC diagnostic ignored "-Weffc++" -#pragma GCC diagnostic ignored "-Wstrict-overflow" -#pragma GCC diagnostic ignored "-Wstrict-aliasing" -#pragma GCC diagnostic ignored "-Wctor-dtor-privacy" -#pragma GCC diagnostic ignored "-Wmissing-declarations" -#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" -#pragma GCC diagnostic ignored "-Winline" -#pragma GCC diagnostic ignored "-Wlong-long" -#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 6) -#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant" -#endif // > gcc 4.6 -#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 7) -#pragma GCC diagnostic ignored "-Wunused-local-typedefs" -#endif // > gcc 4.7 -#if __GNUC__ > 5 || (__GNUC__ == 5 && __GNUC_MINOR__ > 3) -#pragma GCC diagnostic ignored "-Wuseless-cast" -#endif // > gcc 5.3 -#endif // __GNUC__ - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4996) // The compiler encountered a deprecated declaration -#pragma warning(disable : 4706) // assignment within conditional expression -#pragma warning(disable : 4512) // 'class' : assignment operator could not be generated -#pragma warning(disable : 4127) // conditional expression is constant -#endif // _MSC_VER - -#ifndef DOCTEST_LIBRARY_INCLUDED -#define DOCTEST_LIBRARY_INCLUDED - -#define DOCTEST_VERSION_MAJOR 1 -#define DOCTEST_VERSION_MINOR 2 -#define DOCTEST_VERSION_PATCH 1 -#define DOCTEST_VERSION_STR "1.2.1" - -#define DOCTEST_VERSION \ - (DOCTEST_VERSION_MAJOR * 10000 + DOCTEST_VERSION_MINOR * 100 + DOCTEST_VERSION_PATCH) - -// ================================================================================================= -// == FEATURE DETECTION ============================================================================ -// ================================================================================================= - -#if __cplusplus >= 201103L -#ifndef DOCTEST_CONFIG_WITH_DELETED_FUNCTIONS -#define DOCTEST_CONFIG_WITH_DELETED_FUNCTIONS -#endif // DOCTEST_CONFIG_WITH_DELETED_FUNCTIONS -#ifndef DOCTEST_CONFIG_WITH_RVALUE_REFERENCES -#define DOCTEST_CONFIG_WITH_RVALUE_REFERENCES -#endif // DOCTEST_CONFIG_WITH_RVALUE_REFERENCES -#ifndef DOCTEST_CONFIG_WITH_NULLPTR -#define DOCTEST_CONFIG_WITH_NULLPTR -#endif // DOCTEST_CONFIG_WITH_NULLPTR -#ifndef DOCTEST_CONFIG_WITH_LONG_LONG -#define DOCTEST_CONFIG_WITH_LONG_LONG -#endif // DOCTEST_CONFIG_WITH_LONG_LONG -#ifndef DOCTEST_CONFIG_WITH_STATIC_ASSERT -#define DOCTEST_CONFIG_WITH_STATIC_ASSERT -#endif // DOCTEST_CONFIG_WITH_STATIC_ASSERT -#ifndef DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#endif // DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#endif // __cplusplus >= 201103L - -#ifndef __has_feature -#define __has_feature(x) 0 -#endif // __has_feature - -// MSVC C++11 feature support table: https://msdn.microsoft.com/en-us/library/hh567368.aspx -// GCC C++11 feature support table: https://gcc.gnu.org/projects/cxx-status.html -// MSVC version table: -// MSVC++ 15.0 _MSC_VER == 1910 (Visual Studio 2017) -// MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015) -// MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013) -// MSVC++ 11.0 _MSC_VER == 1700 (Visual Studio 2012) -// MSVC++ 10.0 _MSC_VER == 1600 (Visual Studio 2010) -// MSVC++ 9.0 _MSC_VER == 1500 (Visual Studio 2008) -// MSVC++ 8.0 _MSC_VER == 1400 (Visual Studio 2005) - -// deleted functions - -#ifndef DOCTEST_CONFIG_WITH_DELETED_FUNCTIONS -#if defined(_MSC_VER) && (_MSC_VER >= 1800) -#define DOCTEST_CONFIG_WITH_DELETED_FUNCTIONS -#endif // _MSC_VER -#if defined(__clang__) && __has_feature(cxx_deleted_functions) -#define DOCTEST_CONFIG_WITH_DELETED_FUNCTIONS -#endif // __clang__ -#if defined(__GNUC__) && ((__GNUC__ == 4 && __GNUC_MINOR__ >= 4) || __GNUC__ > 4) && \ - defined(__GXX_EXPERIMENTAL_CXX0X__) -#define DOCTEST_CONFIG_WITH_DELETED_FUNCTIONS -#endif // __GNUC__ -#endif // DOCTEST_CONFIG_WITH_DELETED_FUNCTIONS - -#if defined(DOCTEST_CONFIG_NO_DELETED_FUNCTIONS) && defined(DOCTEST_CONFIG_WITH_DELETED_FUNCTIONS) -#undef DOCTEST_CONFIG_WITH_DELETED_FUNCTIONS -#endif // DOCTEST_CONFIG_NO_DELETED_FUNCTIONS - -// rvalue references - -#ifndef DOCTEST_CONFIG_WITH_RVALUE_REFERENCES -#if defined(_MSC_VER) && (_MSC_VER >= 1600) -#define DOCTEST_CONFIG_WITH_RVALUE_REFERENCES -#endif // _MSC_VER -#if defined(__clang__) && __has_feature(cxx_rvalue_references) -#define DOCTEST_CONFIG_WITH_RVALUE_REFERENCES -#endif // __clang__ -#if defined(__GNUC__) && ((__GNUC__ == 4 && __GNUC_MINOR__ >= 3) || __GNUC__ > 4) && \ - defined(__GXX_EXPERIMENTAL_CXX0X__) -#define DOCTEST_CONFIG_WITH_RVALUE_REFERENCES -#endif // __GNUC__ -#endif // DOCTEST_CONFIG_WITH_RVALUE_REFERENCES - -#if defined(DOCTEST_CONFIG_NO_RVALUE_REFERENCES) && defined(DOCTEST_CONFIG_WITH_RVALUE_REFERENCES) -#undef DOCTEST_CONFIG_WITH_RVALUE_REFERENCES -#endif // DOCTEST_CONFIG_NO_RVALUE_REFERENCES - -// nullptr - -#ifndef DOCTEST_CONFIG_WITH_NULLPTR -#if defined(__clang__) && __has_feature(cxx_nullptr) -#define DOCTEST_CONFIG_WITH_NULLPTR -#endif // __clang__ -#if defined(__GNUC__) && ((__GNUC__ == 4 && __GNUC_MINOR__ >= 6) || __GNUC__ > 4) && \ - defined(__GXX_EXPERIMENTAL_CXX0X__) -#define DOCTEST_CONFIG_WITH_NULLPTR -#endif // __GNUC__ -#if defined(_MSC_VER) && (_MSC_VER >= 1600) // MSVC 2010 -#define DOCTEST_CONFIG_WITH_NULLPTR -#endif // _MSC_VER -#endif // DOCTEST_CONFIG_WITH_NULLPTR - -#if defined(DOCTEST_CONFIG_NO_NULLPTR) && defined(DOCTEST_CONFIG_WITH_NULLPTR) -#undef DOCTEST_CONFIG_WITH_NULLPTR -#endif // DOCTEST_CONFIG_NO_NULLPTR - -// variadic macros - -#ifndef DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#if defined(_MSC_VER) && _MSC_VER > 1400 && !defined(__EDGE__) -#define DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#endif // _MSC_VER -#if defined(__GNUC__) && ((__GNUC__ == 4 && __GNUC_MINOR__ >= 1) || __GNUC__ > 4) && \ - defined(__GXX_EXPERIMENTAL_CXX0X__) -#define DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#endif // __GNUC__ and clang -#endif // DOCTEST_CONFIG_WITH_VARIADIC_MACROS - -#if defined(DOCTEST_CONFIG_NO_VARIADIC_MACROS) && defined(DOCTEST_CONFIG_WITH_VARIADIC_MACROS) -#undef DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#endif // DOCTEST_CONFIG_NO_VARIADIC_MACROS - -// long long - -#ifndef DOCTEST_CONFIG_WITH_LONG_LONG -#if defined(_MSC_VER) && (_MSC_VER >= 1400) -#define DOCTEST_CONFIG_WITH_LONG_LONG -#endif // _MSC_VER -#if(defined(__clang__) || \ - (defined(__GNUC__) && ((__GNUC__ == 4 && __GNUC_MINOR__ >= 5) || __GNUC__ > 4))) && \ - defined(__GXX_EXPERIMENTAL_CXX0X__) -#define DOCTEST_CONFIG_WITH_LONG_LONG -#endif // __GNUC__ and clang -#endif // DOCTEST_CONFIG_WITH_LONG_LONG - -#if defined(DOCTEST_CONFIG_NO_LONG_LONG) && defined(DOCTEST_CONFIG_WITH_LONG_LONG) -#undef DOCTEST_CONFIG_WITH_LONG_LONG -#endif // DOCTEST_CONFIG_NO_LONG_LONG - -// static_assert - -#ifndef DOCTEST_CONFIG_WITH_STATIC_ASSERT -#if defined(__clang__) && __has_feature(cxx_static_assert) -#define DOCTEST_CONFIG_WITH_STATIC_ASSERT -#endif // __clang__ -#if defined(__GNUC__) && ((__GNUC__ == 4 && __GNUC_MINOR__ >= 3) || __GNUC__ > 4) && \ - defined(__GXX_EXPERIMENTAL_CXX0X__) -#define DOCTEST_CONFIG_WITH_STATIC_ASSERT -#endif // __GNUC__ -#if defined(_MSC_VER) && (_MSC_VER >= 1600) // MSVC 2010 -#define DOCTEST_CONFIG_WITH_STATIC_ASSERT -#endif // _MSC_VER -#endif // DOCTEST_CONFIG_WITH_STATIC_ASSERT - -#if defined(DOCTEST_CONFIG_NO_STATIC_ASSERT) && defined(DOCTEST_CONFIG_WITH_STATIC_ASSERT) -#undef DOCTEST_CONFIG_WITH_STATIC_ASSERT -#endif // DOCTEST_CONFIG_NO_STATIC_ASSERT - -// other stuff... - -#if defined(DOCTEST_CONFIG_WITH_RVALUE_REFERENCES) || defined(DOCTEST_CONFIG_WITH_LONG_LONG) || \ - defined(DOCTEST_CONFIG_WITH_DELETED_FUNCTIONS) || defined(DOCTEST_CONFIG_WITH_NULLPTR) || \ - defined(DOCTEST_CONFIG_WITH_VARIADIC_MACROS) || defined(DOCTEST_CONFIG_WITH_STATIC_ASSERT) -#define DOCTEST_NO_CPP11_COMPAT -#endif // c++11 stuff - -#if defined(__clang__) && defined(DOCTEST_NO_CPP11_COMPAT) -#pragma clang diagnostic ignored "-Wc++98-compat" -#pragma clang diagnostic ignored "-Wc++98-compat-pedantic" -#endif // __clang__ && DOCTEST_NO_CPP11_COMPAT - -#if defined(_MSC_VER) && !defined(DOCTEST_CONFIG_WINDOWS_SEH) -#define DOCTEST_CONFIG_WINDOWS_SEH -#endif // _MSC_VER -#if defined(DOCTEST_CONFIG_NO_WINDOWS_SEH) && defined(DOCTEST_CONFIG_WINDOWS_SEH) -#undef DOCTEST_CONFIG_WINDOWS_SEH -#endif // DOCTEST_CONFIG_NO_WINDOWS_SEH - -#if !defined(_WIN32) && !defined(__QNX__) && !defined(DOCTEST_CONFIG_POSIX_SIGNALS) -#define DOCTEST_CONFIG_POSIX_SIGNALS -#endif // _WIN32 -#if defined(DOCTEST_CONFIG_NO_POSIX_SIGNALS) && defined(DOCTEST_CONFIG_POSIX_SIGNALS) -#undef DOCTEST_CONFIG_POSIX_SIGNALS -#endif // DOCTEST_CONFIG_NO_POSIX_SIGNALS - -#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS -#if defined(__GNUC__) && !defined(__EXCEPTIONS) -#define DOCTEST_CONFIG_NO_EXCEPTIONS -#endif // clang and gcc -// in MSVC _HAS_EXCEPTIONS is defined in a header instead of as a project define -// so we can't do the automatic detection for MSVC without including some header -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS - -#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS -#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS -#define DOCTEST_CONFIG_NO_EXCEPTIONS -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS - -#if defined(DOCTEST_CONFIG_NO_EXCEPTIONS) && !defined(DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS) -#define DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS && !DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS - -#if defined(DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN) && !defined(DOCTEST_CONFIG_IMPLEMENT) -#define DOCTEST_CONFIG_IMPLEMENT -#endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN - -#if defined _WIN32 || defined __CYGWIN__ -#ifdef __GNUC__ -#define DOCTEST_SYMBOL_EXPORT __attribute__((dllexport)) -#define DOCTEST_SYMBOL_IMPORT __attribute__((dllimport)) -#else // __GNUC__ -#define DOCTEST_SYMBOL_EXPORT __declspec(dllexport) -#define DOCTEST_SYMBOL_IMPORT __declspec(dllimport) -#endif // __GNUC__ -#else // _WIN32 -#define DOCTEST_SYMBOL_EXPORT __attribute__((visibility("default"))) -#define DOCTEST_SYMBOL_IMPORT -#endif // _WIN32 - -#ifdef DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL -#ifdef DOCTEST_CONFIG_IMPLEMENT -#define DOCTEST_INTERFACE DOCTEST_SYMBOL_EXPORT -#else // DOCTEST_CONFIG_IMPLEMENT -#define DOCTEST_INTERFACE DOCTEST_SYMBOL_IMPORT -#endif // DOCTEST_CONFIG_IMPLEMENT -#else // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL -#define DOCTEST_INTERFACE -#endif // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL - -#ifdef _MSC_VER -#define DOCTEST_NOINLINE __declspec(noinline) -#else // _MSC_VER -#define DOCTEST_NOINLINE __attribute__((noinline)) -#endif // _MSC_VER - -#ifndef DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK -#define DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK 5 -#endif // DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK - -// ================================================================================================= -// == FEATURE DETECTION END ======================================================================== -// ================================================================================================= - -// internal macros for string concatenation and anonymous variable name generation -#define DOCTEST_CAT_IMPL(s1, s2) s1##s2 -#define DOCTEST_CAT(s1, s2) DOCTEST_CAT_IMPL(s1, s2) -#ifdef __COUNTER__ // not standard and may be missing for some compilers -#define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __COUNTER__) -#else // __COUNTER__ -#define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __LINE__) -#endif // __COUNTER__ - -// macro for making a string out of an identifier -#ifdef DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_TOSTR_IMPL(...) #__VA_ARGS__ -#define DOCTEST_TOSTR(...) DOCTEST_TOSTR_IMPL(__VA_ARGS__) -#else // DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_TOSTR_IMPL(x) #x -#define DOCTEST_TOSTR(x) DOCTEST_TOSTR_IMPL(x) -#endif // DOCTEST_CONFIG_WITH_VARIADIC_MACROS - -// for concatenating literals and making the result a string -#define DOCTEST_STR_CONCAT_TOSTR(s1, s2) DOCTEST_TOSTR(s1) DOCTEST_TOSTR(s2) - -// counts the number of elements in a C string -#define DOCTEST_COUNTOF(x) (sizeof(x) / sizeof(x[0])) - -#ifndef DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE -#define DOCTEST_REF_WRAP(x) x& -#else // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE -#define DOCTEST_REF_WRAP(x) x -#endif // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE - -// not using __APPLE__ because... this is how Catch does it -#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) -#define DOCTEST_PLATFORM_MAC -#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) -#define DOCTEST_PLATFORM_IPHONE -#elif defined(_WIN32) || defined(_MSC_VER) -#define DOCTEST_PLATFORM_WINDOWS -#else -#define DOCTEST_PLATFORM_LINUX -#endif - -#if defined(__clang__) -#define DOCTEST_GLOBAL_NO_WARNINGS(var) \ - _Pragma("clang diagnostic push") \ - _Pragma("clang diagnostic ignored \"-Wglobal-constructors\"") static int var -#define DOCTEST_GLOBAL_NO_WARNINGS_END() _Pragma("clang diagnostic pop") -#elif defined(__GNUC__) -#define DOCTEST_GLOBAL_NO_WARNINGS(var) static int var __attribute__((unused)) -#define DOCTEST_GLOBAL_NO_WARNINGS_END() -#else // MSVC / other -#define DOCTEST_GLOBAL_NO_WARNINGS(var) static int var -#define DOCTEST_GLOBAL_NO_WARNINGS_END() -#endif // MSVC / other - -// should probably take a look at https://github.com/scottt/debugbreak -#ifdef DOCTEST_PLATFORM_MAC -#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) -#elif defined(_MSC_VER) -#define DOCTEST_BREAK_INTO_DEBUGGER() __debugbreak() -#elif defined(__MINGW32__) -extern "C" __declspec(dllimport) void __stdcall DebugBreak(); -#define DOCTEST_BREAK_INTO_DEBUGGER() ::DebugBreak() -#else // linux -#define DOCTEST_BREAK_INTO_DEBUGGER() ((void)0) -#endif // linux - -#ifdef __clang__ -// to detect if libc++ is being used with clang (the _LIBCPP_VERSION identifier) -#include -#endif // __clang__ - -#ifdef _LIBCPP_VERSION -// not forward declaring ostream for libc++ because I had some problems (inline namespaces vs c++98) -// so the header is used - also it is very light and doesn't drag a ton of stuff -#include -#else // _LIBCPP_VERSION -#ifndef DOCTEST_CONFIG_USE_IOSFWD -namespace std -{ -template -struct char_traits; -template <> -struct char_traits; -template -class basic_ostream; -typedef basic_ostream > ostream; -} // namespace std -#else // DOCTEST_CONFIG_USE_IOSFWD -#include -#endif // DOCTEST_CONFIG_USE_IOSFWD -#endif // _LIBCPP_VERSION - -// static assert macro - because of the c++98 support requires that the message is an -// identifier (no spaces and not a C string) - example without quotes: I_am_a_message -// taken from here: http://stackoverflow.com/a/1980156/3162383 -#ifdef DOCTEST_CONFIG_WITH_STATIC_ASSERT -#define DOCTEST_STATIC_ASSERT(expression, message) static_assert(expression, #message) -#else // DOCTEST_CONFIG_WITH_STATIC_ASSERT -#define DOCTEST_STATIC_ASSERT(expression, message) \ - struct DOCTEST_CAT(__static_assertion_at_line_, __LINE__) \ - { \ - doctest::detail::static_assert_impl::StaticAssertion((expression))> \ - DOCTEST_CAT(DOCTEST_CAT(DOCTEST_CAT(STATIC_ASSERTION_FAILED_AT_LINE_, __LINE__), \ - _), \ - message); \ - }; \ - typedef doctest::detail::static_assert_impl::StaticAssertionTest( \ - sizeof(DOCTEST_CAT(__static_assertion_at_line_, __LINE__)))> \ - DOCTEST_CAT(__static_assertion_test_at_line_, __LINE__) -#endif // DOCTEST_CONFIG_WITH_STATIC_ASSERT - -#ifdef DOCTEST_CONFIG_WITH_NULLPTR -#ifdef _LIBCPP_VERSION -#include -#else // _LIBCPP_VERSION -namespace std -{ typedef decltype(nullptr) nullptr_t; } -#endif // _LIBCPP_VERSION -#endif // DOCTEST_CONFIG_WITH_NULLPTR - -#ifndef DOCTEST_CONFIG_DISABLE -namespace doctest -{ -namespace detail -{ - struct TestSuite - { - const char* m_test_suite; - const char* m_description; - bool m_skip; - bool m_may_fail; - bool m_should_fail; - int m_expected_failures; - double m_timeout; - - TestSuite& operator*(const char* in) { - m_test_suite = in; - // clear state - m_description = 0; - m_skip = false; - m_may_fail = false; - m_should_fail = false; - m_expected_failures = 0; - m_timeout = 0; - return *this; - } - - template - TestSuite& operator*(const T& in) { - in.fill(*this); - return *this; - } - }; -} // namespace detail -} // namespace doctest - -// in a separate namespace outside of doctest because the DOCTEST_TEST_SUITE macro -// introduces an anonymous namespace in which getCurrentTestSuite gets overridden -namespace doctest_detail_test_suite_ns -{ -DOCTEST_INTERFACE doctest::detail::TestSuite& getCurrentTestSuite(); -} // namespace doctest_detail_test_suite_ns - -#endif // DOCTEST_CONFIG_DISABLE - -namespace doctest -{ -// A 24 byte string class (can be as small as 17 for x64 and 13 for x86) that can hold strings with length -// of up to 23 chars on the stack before going on the heap - the last byte of the buffer is used for: -// - "is small" bit - the highest bit - if "0" then it is small - otherwise its "1" (128) -// - if small - capacity left before going on the heap - using the lowest 5 bits -// - if small - 2 bits are left unused - the second and third highest ones -// - if small - acts as a null terminator if strlen() is 23 (24 including the null terminator) -// and the "is small" bit remains "0" ("as well as the capacity left") so its OK -// Idea taken from this lecture about the string implementation of facebook/folly - fbstring -// https://www.youtube.com/watch?v=kPR8h4-qZdk -// TODO: -// - optimizations - like not deleting memory unnecessarily in operator= and etc. -// - resize/reserve/clear -// - substr -// - replace -// - back/front -// - iterator stuff -// - find & friends -// - push_back/pop_back -// - assign/insert/erase -// - relational operators as free functions - taking const char* as one of the params -class DOCTEST_INTERFACE String -{ - static const unsigned len = 24; //!OCLINT avoid private static members - static const unsigned last = len - 1; //!OCLINT avoid private static members - - struct view // len should be more than sizeof(view) - because of the final byte for flags - { - char* ptr; - unsigned size; - unsigned capacity; - }; - - union - { - char buf[len]; - view data; - }; - - void copy(const String& other); - - void setOnHeap() { *reinterpret_cast(&buf[last]) = 128; } - void setLast(unsigned in = last) { buf[last] = char(in); } - -public: - String() { - buf[0] = '\0'; - setLast(); - } - - String(const char* in); - - String(const String& other) { copy(other); } - - ~String() { - if(!isOnStack()) - delete[] data.ptr; - } - - // GCC 4.9/5/6 report Wstrict-overflow when optimizations are ON and it got inlined in the vector class somewhere... - // see commit 574ef95f0cd379118be5011704664e4b5351f1e0 and build https://travis-ci.org/onqtam/doctest/builds/230671611 - DOCTEST_NOINLINE String& operator=(const String& other) { - if(!isOnStack()) - delete[] data.ptr; - - copy(other); - - return *this; - } - String& operator+=(const String& other); - - String operator+(const String& other) const { return String(*this) += other; } - -#ifdef DOCTEST_CONFIG_WITH_RVALUE_REFERENCES - String(String&& other); - String& operator=(String&& other); -#endif // DOCTEST_CONFIG_WITH_RVALUE_REFERENCES - - bool isOnStack() const { return (buf[last] & 128) == 0; } - - char operator[](unsigned i) const { return const_cast(this)->operator[](i); } // NOLINT - char& operator[](unsigned i) { - if(isOnStack()) - return reinterpret_cast(buf)[i]; - return data.ptr[i]; - } - - const char* c_str() const { return const_cast(this)->c_str(); } // NOLINT - char* c_str() { - if(isOnStack()) - return reinterpret_cast(buf); - return data.ptr; - } - - unsigned size() const { - if(isOnStack()) - return last - (unsigned(buf[last]) & 31); // using "last" would work only if "len" is 32 - return data.size; - } - - unsigned capacity() const { - if(isOnStack()) - return len; - return data.capacity; - } - - int compare(const char* other, bool no_case = false) const; - int compare(const String& other, bool no_case = false) const; -}; - -// clang-format off -inline bool operator==(const String& lhs, const String& rhs) { return lhs.compare(rhs) == 0; } -inline bool operator!=(const String& lhs, const String& rhs) { return lhs.compare(rhs) != 0; } -inline bool operator< (const String& lhs, const String& rhs) { return lhs.compare(rhs) < 0; } -inline bool operator> (const String& lhs, const String& rhs) { return lhs.compare(rhs) > 0; } -inline bool operator<=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) < 0 : true; } -inline bool operator>=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) > 0 : true; } -// clang-format on - -DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& stream, const String& in); - -namespace detail -{ -#ifndef DOCTEST_CONFIG_WITH_STATIC_ASSERT - namespace static_assert_impl - { - template - struct StaticAssertion; - - template <> - struct StaticAssertion - {}; - - template - struct StaticAssertionTest - {}; - } // namespace static_assert_impl -#endif // DOCTEST_CONFIG_WITH_STATIC_ASSERT - - namespace traits - { - template - struct remove_const - { typedef T type; }; - - template - struct remove_const - { typedef T type; }; - - template - struct remove_volatile - { typedef T type; }; - - template - struct remove_volatile - { typedef T type; }; - - template - struct remove_cv - { typedef typename remove_volatile::type>::type type; }; - - template - struct is_pointer_helper - { static const bool value = false; }; - - template - struct is_pointer_helper - // cppcheck-suppress unusedStructMember - { static const bool value = true; }; - - template - struct is_pointer - // cppcheck-suppress unusedStructMember - { static const bool value = is_pointer_helper::type>::value; }; - - template - struct enable_if - {}; - - template - struct enable_if - { typedef TYPE type; }; - - template - struct remove_reference - { typedef T type; }; - - template - struct remove_reference - { typedef T type; }; - - template - class is_constructible_impl - { - private: - template - static bool test(typename enable_if< //!OCLINT avoid private static members - sizeof(C_T) == - sizeof(C_T(static_cast( - *static_cast::type*>( - 0))))>::type*); - - template - static int test(...); //!OCLINT avoid private static members - - public: - static const bool value = sizeof(test(0)) == sizeof(bool); - }; - - template - class is_constructible_impl - { - private: - template - static C_T testFun(C_T); //!OCLINT avoid private static members - - template - static bool test(typename enable_if< //!OCLINT avoid private static members - sizeof(C_T) == sizeof(testFun(C_T()))>::type*); - - template - static int test(...); //!OCLINT avoid private static members - - public: - static const bool value = sizeof(test(0)) == sizeof(bool); - }; - -// is_constructible<> taken from here: http://stackoverflow.com/a/40443701/3162383 -// for GCC/Clang gives the same results as std::is_constructible<> - see here: https://wandbox.org/permlink/bNWr7Ii2fuz4Vf7A -// modifications: -// - reworked to support only 1 argument (mainly because of MSVC...) -// - removed pointer support -// MSVC support: -// - for versions before 2012 read the CAUTION comment below -// currently intended for use only in the Approx() helper for strong typedefs of double - see issue #62 -#ifndef _MSC_VER - template - class is_constructible - { - public: - static const bool value = is_pointer::type>::value ? - false : - is_constructible_impl::value; - }; -#elif defined(_MSC_VER) && (_MSC_VER >= 1700) - template - struct is_constructible - { static const bool value = __is_constructible(T, AT_1); }; -#elif defined(_MSC_VER) - // !!! USE WITH CAUTION !!! - // will always return false - unable to implement this for versions of MSVC older than 2012 for now... - template - struct is_constructible - { static const bool value = false; }; -#endif // _MSC_VER - } // namespace traits - - template - struct deferred_false - // cppcheck-suppress unusedStructMember - { static const bool value = false; }; - - // to silence the warning "-Wzero-as-null-pointer-constant" only for gcc 5 for the Approx template ctor - pragmas don't work for it... - inline void* getNull() { return 0; } - - namespace has_insertion_operator_impl - { - typedef char no; - typedef char yes[2]; - - struct any_t - { - template - // cppcheck-suppress noExplicitConstructor - any_t(const DOCTEST_REF_WRAP(T)); - }; - - yes& testStreamable(std::ostream&); - no testStreamable(no); - - no operator<<(const std::ostream&, const any_t&); - - template - struct has_insertion_operator - { - static std::ostream& s; - static const DOCTEST_REF_WRAP(T) t; - static const bool value = sizeof(testStreamable(s << t)) == sizeof(yes); - }; - } // namespace has_insertion_operator_impl - - template - struct has_insertion_operator : has_insertion_operator_impl::has_insertion_operator - {}; - - DOCTEST_INTERFACE void my_memcpy(void* dest, const void* src, unsigned num); - DOCTEST_INTERFACE unsigned my_strlen(const char* in); - - DOCTEST_INTERFACE std::ostream* createStream(); - DOCTEST_INTERFACE String getStreamResult(std::ostream*); - DOCTEST_INTERFACE void freeStream(std::ostream*); - - template - struct StringMakerBase - { - template - static String convert(const DOCTEST_REF_WRAP(T)) { - return "{?}"; - } - }; - - template <> - struct StringMakerBase - { - template - static String convert(const DOCTEST_REF_WRAP(T) in) { - std::ostream* stream = createStream(); - *stream << in; - String result = getStreamResult(stream); - freeStream(stream); - return result; - } - }; - - DOCTEST_INTERFACE String rawMemoryToString(const void* object, unsigned size); - - template - String rawMemoryToString(const DOCTEST_REF_WRAP(T) object) { - return rawMemoryToString(&object, sizeof(object)); - } - - class NullType - {}; - - template - struct Typelist - { - typedef T Head; - typedef U Tail; - }; - - // type of recursive function - template - struct ForEachType; - - // Recursion rule - template - struct ForEachType, Callable> : public ForEachType - { - enum - { - value = 1 + ForEachType::value - }; - - explicit ForEachType(Callable& callable) - : ForEachType(callable) { -#if defined(_MSC_VER) && _MSC_VER <= 1900 - callable.operator()(); -#else // _MSC_VER - callable.template operator()(); -#endif // _MSC_VER - } - }; - - // Recursion end - template - struct ForEachType, Callable> - { - public: - enum - { - value = 0 - }; - - explicit ForEachType(Callable& callable) { -#if defined(_MSC_VER) && _MSC_VER <= 1900 - callable.operator()(); -#else // _MSC_VER - callable.template operator()(); -#endif // _MSC_VER - } - }; - - template - const char* type_to_string() { - return "<>"; - } -} // namespace detail - -template -struct Types -{ -private: - typedef typename Types::Result TailResult; - -public: - typedef detail::Typelist Result; -}; - -template <> -struct Types<> -{ typedef detail::NullType Result; }; - -template -struct StringMaker : detail::StringMakerBase::value> -{}; - -template -struct StringMaker -{ - template - static String convert(U* p) { - if(p) - return detail::rawMemoryToString(p); - return "NULL"; - } -}; - -template -struct StringMaker -{ - static String convert(R C::*p) { - if(p) - return detail::rawMemoryToString(p); - return "NULL"; - } -}; - -template -String toString(const DOCTEST_REF_WRAP(T) value) { - return StringMaker::convert(value); -} - -#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING -DOCTEST_INTERFACE String toString(char* in); -DOCTEST_INTERFACE String toString(const char* in); -#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING -DOCTEST_INTERFACE String toString(bool in); -DOCTEST_INTERFACE String toString(float in); -DOCTEST_INTERFACE String toString(double in); -DOCTEST_INTERFACE String toString(double long in); - -DOCTEST_INTERFACE String toString(char in); -DOCTEST_INTERFACE String toString(char signed in); -DOCTEST_INTERFACE String toString(char unsigned in); -DOCTEST_INTERFACE String toString(int short in); -DOCTEST_INTERFACE String toString(int short unsigned in); -DOCTEST_INTERFACE String toString(int in); -DOCTEST_INTERFACE String toString(int unsigned in); -DOCTEST_INTERFACE String toString(int long in); -DOCTEST_INTERFACE String toString(int long unsigned in); - -#ifdef DOCTEST_CONFIG_WITH_LONG_LONG -DOCTEST_INTERFACE String toString(int long long in); -DOCTEST_INTERFACE String toString(int long long unsigned in); -#endif // DOCTEST_CONFIG_WITH_LONG_LONG - -#ifdef DOCTEST_CONFIG_WITH_NULLPTR -DOCTEST_INTERFACE String toString(std::nullptr_t in); -#endif // DOCTEST_CONFIG_WITH_NULLPTR - -class DOCTEST_INTERFACE Approx -{ -public: - explicit Approx(double value); - - Approx operator()(double value) const { - Approx approx(value); - approx.epsilon(m_epsilon); - approx.scale(m_scale); - return approx; - } - - template - explicit Approx(const T& value, - typename detail::traits::enable_if< - detail::traits::is_constructible::value>::type* = - static_cast(detail::getNull())) { - *this = Approx(static_cast(value)); - } - - // clang-format off - // overloads for double - the first one is necessary as it is in the implementation part of doctest - // as for the others - keeping them for potentially faster compile times - DOCTEST_INTERFACE friend bool operator==(double lhs, Approx const& rhs); - friend bool operator==(Approx const& lhs, double rhs) { return operator==(rhs, lhs); } - friend bool operator!=(double lhs, Approx const& rhs) { return !operator==(lhs, rhs); } - friend bool operator!=(Approx const& lhs, double rhs) { return !operator==(rhs, lhs); } - friend bool operator<=(double lhs, Approx const& rhs) { return lhs < rhs.m_value || lhs == rhs; } - friend bool operator<=(Approx const& lhs, double rhs) { return lhs.m_value < rhs || lhs == rhs; } - friend bool operator>=(double lhs, Approx const& rhs) { return lhs > rhs.m_value || lhs == rhs; } - friend bool operator>=(Approx const& lhs, double rhs) { return lhs.m_value > rhs || lhs == rhs; } - friend bool operator< (double lhs, Approx const& rhs) { return lhs < rhs.m_value && lhs != rhs; } - friend bool operator< (Approx const& lhs, double rhs) { return lhs.m_value < rhs && lhs != rhs; } - friend bool operator> (double lhs, Approx const& rhs) { return lhs > rhs.m_value && lhs != rhs; } - friend bool operator> (Approx const& lhs, double rhs) { return lhs.m_value > rhs && lhs != rhs; } - -#define DOCTEST_APPROX_PREFIX \ - template friend typename detail::traits::enable_if::value, bool>::type - - DOCTEST_APPROX_PREFIX operator==(const T& lhs, const Approx& rhs) { return operator==(double(lhs), rhs); } - DOCTEST_APPROX_PREFIX operator==(const Approx& lhs, const T& rhs) { return operator==(rhs, lhs); } - DOCTEST_APPROX_PREFIX operator!=(const T& lhs, const Approx& rhs) { return !operator==(lhs, rhs); } - DOCTEST_APPROX_PREFIX operator!=(const Approx& lhs, const T& rhs) { return !operator==(rhs, lhs); } - DOCTEST_APPROX_PREFIX operator<=(const T& lhs, const Approx& rhs) { return double(lhs) < rhs.m_value || lhs == rhs; } - DOCTEST_APPROX_PREFIX operator<=(const Approx& lhs, const T& rhs) { return lhs.m_value < double(rhs) || lhs == rhs; } - DOCTEST_APPROX_PREFIX operator>=(const T& lhs, const Approx& rhs) { return double(lhs) > rhs.m_value || lhs == rhs; } - DOCTEST_APPROX_PREFIX operator>=(const Approx& lhs, const T& rhs) { return lhs.m_value > double(rhs) || lhs == rhs; } - DOCTEST_APPROX_PREFIX operator< (const T& lhs, const Approx& rhs) { return double(lhs) < rhs.m_value && lhs != rhs; } - DOCTEST_APPROX_PREFIX operator< (const Approx& lhs, const T& rhs) { return lhs.m_value < double(rhs) && lhs != rhs; } - DOCTEST_APPROX_PREFIX operator> (const T& lhs, const Approx& rhs) { return double(lhs) > rhs.m_value && lhs != rhs; } - DOCTEST_APPROX_PREFIX operator> (const Approx& lhs, const T& rhs) { return lhs.m_value > double(rhs) && lhs != rhs; } -#undef DOCTEST_APPROX_PREFIX - // clang-format on - - Approx& epsilon(double newEpsilon) { - m_epsilon = (newEpsilon); - return *this; - } - - template - typename detail::traits::enable_if::value, - Approx&>::type - epsilon(const T& newEpsilon) { - m_epsilon = static_cast(newEpsilon); - return *this; - } - - Approx& scale(double newScale) { - m_scale = (newScale); - return *this; - } - - template - typename detail::traits::enable_if::value, - Approx&>::type - scale(const T& newScale) { - m_scale = static_cast(newScale); - return *this; - } - - String toString() const; - -private: - double m_epsilon; - double m_scale; - double m_value; -}; - -template <> -inline String toString(const DOCTEST_REF_WRAP(Approx) value) { - return value.toString(); -} - -#if !defined(DOCTEST_CONFIG_DISABLE) - -namespace detail -{ - // the function type this library works with - typedef void (*funcType)(); - - namespace assertType - { - enum Enum - { - // macro traits - - is_warn = 1, - is_check = 2, - is_require = 4, - - is_throws = 8, - is_throws_as = 16, - is_nothrow = 32, - - is_fast = 64, // not checked anywhere - used just to distinguish the types - is_false = 128, - is_unary = 256, - - is_eq = 512, - is_ne = 1024, - - is_lt = 2048, - is_gt = 4096, - - is_ge = 8192, - is_le = 16384, - - // macro types - - DT_WARN = is_warn, - DT_CHECK = is_check, - DT_REQUIRE = is_require, - - DT_WARN_FALSE = is_false | is_warn, - DT_CHECK_FALSE = is_false | is_check, - DT_REQUIRE_FALSE = is_false | is_require, - - DT_WARN_THROWS = is_throws | is_warn, - DT_CHECK_THROWS = is_throws | is_check, - DT_REQUIRE_THROWS = is_throws | is_require, - - DT_WARN_THROWS_AS = is_throws_as | is_warn, - DT_CHECK_THROWS_AS = is_throws_as | is_check, - DT_REQUIRE_THROWS_AS = is_throws_as | is_require, - - DT_WARN_NOTHROW = is_nothrow | is_warn, - DT_CHECK_NOTHROW = is_nothrow | is_check, - DT_REQUIRE_NOTHROW = is_nothrow | is_require, - - DT_WARN_EQ = is_eq | is_warn, - DT_CHECK_EQ = is_eq | is_check, - DT_REQUIRE_EQ = is_eq | is_require, - - DT_WARN_NE = is_ne | is_warn, - DT_CHECK_NE = is_ne | is_check, - DT_REQUIRE_NE = is_ne | is_require, - - DT_WARN_GT = is_gt | is_warn, - DT_CHECK_GT = is_gt | is_check, - DT_REQUIRE_GT = is_gt | is_require, - - DT_WARN_LT = is_lt | is_warn, - DT_CHECK_LT = is_lt | is_check, - DT_REQUIRE_LT = is_lt | is_require, - - DT_WARN_GE = is_ge | is_warn, - DT_CHECK_GE = is_ge | is_check, - DT_REQUIRE_GE = is_ge | is_require, - - DT_WARN_LE = is_le | is_warn, - DT_CHECK_LE = is_le | is_check, - DT_REQUIRE_LE = is_le | is_require, - - DT_WARN_UNARY = is_unary | is_warn, - DT_CHECK_UNARY = is_unary | is_check, - DT_REQUIRE_UNARY = is_unary | is_require, - - DT_WARN_UNARY_FALSE = is_false | is_unary | is_warn, - DT_CHECK_UNARY_FALSE = is_false | is_unary | is_check, - DT_REQUIRE_UNARY_FALSE = is_false | is_unary | is_require, - - DT_FAST_WARN_EQ = is_fast | is_eq | is_warn, - DT_FAST_CHECK_EQ = is_fast | is_eq | is_check, - DT_FAST_REQUIRE_EQ = is_fast | is_eq | is_require, - - DT_FAST_WARN_NE = is_fast | is_ne | is_warn, - DT_FAST_CHECK_NE = is_fast | is_ne | is_check, - DT_FAST_REQUIRE_NE = is_fast | is_ne | is_require, - - DT_FAST_WARN_GT = is_fast | is_gt | is_warn, - DT_FAST_CHECK_GT = is_fast | is_gt | is_check, - DT_FAST_REQUIRE_GT = is_fast | is_gt | is_require, - - DT_FAST_WARN_LT = is_fast | is_lt | is_warn, - DT_FAST_CHECK_LT = is_fast | is_lt | is_check, - DT_FAST_REQUIRE_LT = is_fast | is_lt | is_require, - - DT_FAST_WARN_GE = is_fast | is_ge | is_warn, - DT_FAST_CHECK_GE = is_fast | is_ge | is_check, - DT_FAST_REQUIRE_GE = is_fast | is_ge | is_require, - - DT_FAST_WARN_LE = is_fast | is_le | is_warn, - DT_FAST_CHECK_LE = is_fast | is_le | is_check, - DT_FAST_REQUIRE_LE = is_fast | is_le | is_require, - - DT_FAST_WARN_UNARY = is_fast | is_unary | is_warn, - DT_FAST_CHECK_UNARY = is_fast | is_unary | is_check, - DT_FAST_REQUIRE_UNARY = is_fast | is_unary | is_require, - - DT_FAST_WARN_UNARY_FALSE = is_fast | is_false | is_unary | is_warn, - DT_FAST_CHECK_UNARY_FALSE = is_fast | is_false | is_unary | is_check, - DT_FAST_REQUIRE_UNARY_FALSE = is_fast | is_false | is_unary | is_require - }; - } // namespace assertType - - DOCTEST_INTERFACE const char* getAssertString(assertType::Enum val); - - // clang-format off - template struct decay_array { typedef T type; }; - template struct decay_array { typedef T* type; }; - template struct decay_array { typedef T* type; }; - - template struct not_char_pointer { enum { value = 1 }; }; - template<> struct not_char_pointer { enum { value = 0 }; }; - template<> struct not_char_pointer { enum { value = 0 }; }; - - template struct can_use_op : not_char_pointer::type> {}; - // clang-format on - - struct TestFailureException - {}; - - DOCTEST_INTERFACE bool checkIfShouldThrow(assertType::Enum assert_type); - DOCTEST_INTERFACE void fastAssertThrowIfFlagSet(int flags); - DOCTEST_INTERFACE void throwException(); - - struct TestAccessibleContextState - { - bool no_throw; // to skip exceptions-related assertion macros - bool success; // include successful assertions in output - }; - - struct ContextState; - - DOCTEST_INTERFACE TestAccessibleContextState* getTestsContextState(); - - struct DOCTEST_INTERFACE SubcaseSignature - { - const char* m_name; - const char* m_file; - int m_line; - - SubcaseSignature(const char* name, const char* file, int line) - : m_name(name) - , m_file(file) - , m_line(line) {} - - bool operator<(const SubcaseSignature& other) const; - }; - - // cppcheck-suppress copyCtorAndEqOperator - struct DOCTEST_INTERFACE Subcase - { - SubcaseSignature m_signature; - bool m_entered; - - Subcase(const char* name, const char* file, int line); - Subcase(const Subcase& other); - ~Subcase(); - - operator bool() const { return m_entered; } - }; - - template - String stringifyBinaryExpr(const DOCTEST_REF_WRAP(L) lhs, const char* op, - const DOCTEST_REF_WRAP(R) rhs) { - return toString(lhs) + op + toString(rhs); - } - - struct DOCTEST_INTERFACE Result - { - bool m_passed; - String m_decomposition; - - ~Result(); - - DOCTEST_NOINLINE Result(bool passed = false, const String& decomposition = String()) - : m_passed(passed) - , m_decomposition(decomposition) {} - - DOCTEST_NOINLINE Result(const Result& other) - : m_passed(other.m_passed) - , m_decomposition(other.m_decomposition) {} - - Result& operator=(const Result& other); - - operator bool() { return !m_passed; } - - // clang-format off - // forbidding some expressions based on this table: http://en.cppreference.com/w/cpp/language/operator_precedence - template Result& operator& (const R&) { DOCTEST_STATIC_ASSERT(deferred_false::value, Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison); return *this; } - template Result& operator^ (const R&) { DOCTEST_STATIC_ASSERT(deferred_false::value, Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison); return *this; } - template Result& operator| (const R&) { DOCTEST_STATIC_ASSERT(deferred_false::value, Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison); return *this; } - template Result& operator&& (const R&) { DOCTEST_STATIC_ASSERT(deferred_false::value, Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison); return *this; } - template Result& operator|| (const R&) { DOCTEST_STATIC_ASSERT(deferred_false::value, Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison); return *this; } - template Result& operator== (const R&) { DOCTEST_STATIC_ASSERT(deferred_false::value, Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison); return *this; } - template Result& operator!= (const R&) { DOCTEST_STATIC_ASSERT(deferred_false::value, Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison); return *this; } - template Result& operator< (const R&) { DOCTEST_STATIC_ASSERT(deferred_false::value, Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison); return *this; } - template Result& operator> (const R&) { DOCTEST_STATIC_ASSERT(deferred_false::value, Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison); return *this; } - template Result& operator<= (const R&) { DOCTEST_STATIC_ASSERT(deferred_false::value, Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison); return *this; } - template Result& operator>= (const R&) { DOCTEST_STATIC_ASSERT(deferred_false::value, Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison); return *this; } - template Result& operator= (const R&) { DOCTEST_STATIC_ASSERT(deferred_false::value, Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison); return *this; } - template Result& operator+= (const R&) { DOCTEST_STATIC_ASSERT(deferred_false::value, Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison); return *this; } - template Result& operator-= (const R&) { DOCTEST_STATIC_ASSERT(deferred_false::value, Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison); return *this; } - template Result& operator*= (const R&) { DOCTEST_STATIC_ASSERT(deferred_false::value, Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison); return *this; } - template Result& operator/= (const R&) { DOCTEST_STATIC_ASSERT(deferred_false::value, Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison); return *this; } - template Result& operator%= (const R&) { DOCTEST_STATIC_ASSERT(deferred_false::value, Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison); return *this; } - template Result& operator<<=(const R&) { DOCTEST_STATIC_ASSERT(deferred_false::value, Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison); return *this; } - template Result& operator>>=(const R&) { DOCTEST_STATIC_ASSERT(deferred_false::value, Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison); return *this; } - template Result& operator&= (const R&) { DOCTEST_STATIC_ASSERT(deferred_false::value, Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison); return *this; } - template Result& operator^= (const R&) { DOCTEST_STATIC_ASSERT(deferred_false::value, Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison); return *this; } - template Result& operator|= (const R&) { DOCTEST_STATIC_ASSERT(deferred_false::value, Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison); return *this; } - // clang-format on - }; - -#ifndef DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION - -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wsign-conversion" -#pragma clang diagnostic ignored "-Wsign-compare" -//#pragma clang diagnostic ignored "-Wdouble-promotion" -//#pragma clang diagnostic ignored "-Wconversion" -//#pragma clang diagnostic ignored "-Wfloat-equal" -#endif // __clang__ - -#if defined(__GNUC__) && !defined(__clang__) -#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 6) -#pragma GCC diagnostic push -#endif // > gcc 4.6 -#pragma GCC diagnostic ignored "-Wsign-conversion" -#pragma GCC diagnostic ignored "-Wsign-compare" -#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 5) -//#pragma GCC diagnostic ignored "-Wdouble-promotion" -#endif // > gcc 4.5 -//#pragma GCC diagnostic ignored "-Wconversion" -//#pragma GCC diagnostic ignored "-Wfloat-equal" -#endif // __GNUC__ - -#ifdef _MSC_VER -#pragma warning(push) -// http://stackoverflow.com/questions/39479163 what's the difference between C4018 and C4389 -#pragma warning(disable : 4389) // 'operator' : signed/unsigned mismatch -#pragma warning(disable : 4018) // 'expression' : signed/unsigned mismatch -//#pragma warning(disable : 4805) // 'operation' : unsafe mix of type 'type' and type 'type' in operation -#endif // _MSC_VER - -#endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION - -// clang-format off -#ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING -#define DOCTEST_COMPARISON_RETURN_TYPE bool -#else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING -#define DOCTEST_COMPARISON_RETURN_TYPE typename traits::enable_if::value || can_use_op::value, bool>::type - inline bool eq(const char* lhs, const char* rhs) { return String(lhs) == String(rhs); } - inline bool ne(const char* lhs, const char* rhs) { return String(lhs) != String(rhs); } - inline bool lt(const char* lhs, const char* rhs) { return String(lhs) < String(rhs); } - inline bool gt(const char* lhs, const char* rhs) { return String(lhs) > String(rhs); } - inline bool le(const char* lhs, const char* rhs) { return String(lhs) <= String(rhs); } - inline bool ge(const char* lhs, const char* rhs) { return String(lhs) >= String(rhs); } -#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING - - template DOCTEST_COMPARISON_RETURN_TYPE eq(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) { return lhs == rhs; } - template DOCTEST_COMPARISON_RETURN_TYPE ne(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) { return lhs != rhs; } - template DOCTEST_COMPARISON_RETURN_TYPE lt(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) { return lhs < rhs; } - template DOCTEST_COMPARISON_RETURN_TYPE gt(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) { return lhs > rhs; } - template DOCTEST_COMPARISON_RETURN_TYPE le(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) { return lhs <= rhs; } - template DOCTEST_COMPARISON_RETURN_TYPE ge(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) { return lhs >= rhs; } -// clang-format on - -#ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING -#define DOCTEST_CMP_EQ(l, r) l == r -#define DOCTEST_CMP_NE(l, r) l != r -#define DOCTEST_CMP_GT(l, r) l > r -#define DOCTEST_CMP_LT(l, r) l < r -#define DOCTEST_CMP_GE(l, r) l >= r -#define DOCTEST_CMP_LE(l, r) l <= r -#else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING -#define DOCTEST_CMP_EQ(l, r) eq(l, r) -#define DOCTEST_CMP_NE(l, r) ne(l, r) -#define DOCTEST_CMP_GT(l, r) gt(l, r) -#define DOCTEST_CMP_LT(l, r) lt(l, r) -#define DOCTEST_CMP_GE(l, r) ge(l, r) -#define DOCTEST_CMP_LE(l, r) le(l, r) -#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING - -#define DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(op, op_str, op_macro) \ - template \ - DOCTEST_NOINLINE Result operator op(const DOCTEST_REF_WRAP(R) rhs) { \ - bool res = op_macro(lhs, rhs); \ - if(m_assert_type & assertType::is_false) \ - res = !res; \ - if(!res || doctest::detail::getTestsContextState()->success) \ - return Result(res, stringifyBinaryExpr(lhs, op_str, rhs)); \ - return Result(res); \ - } - -#define DOCTEST_FORBIT_EXPRESSION(op) \ - template \ - Expression_lhs& operator op(const R&) { \ - DOCTEST_STATIC_ASSERT(deferred_false::value, \ - Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison); \ - return *this; \ - } - - template - // cppcheck-suppress copyCtorAndEqOperator - struct Expression_lhs - { - L lhs; - assertType::Enum m_assert_type; - - explicit Expression_lhs(L in, assertType::Enum assert_type) - : lhs(in) - , m_assert_type(assert_type) {} - - Expression_lhs(const Expression_lhs& other) - : lhs(other.lhs) {} - - DOCTEST_NOINLINE operator Result() { - bool res = !!lhs; - if(m_assert_type & assertType::is_false) //!OCLINT bitwise operator in conditional - res = !res; - - if(!res || getTestsContextState()->success) - return Result(res, toString(lhs)); - return Result(res); - } - - // clang-format off - DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(==, " == ", DOCTEST_CMP_EQ) //!OCLINT bitwise operator in conditional - DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(!=, " != ", DOCTEST_CMP_NE) //!OCLINT bitwise operator in conditional - DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(>, " > ", DOCTEST_CMP_GT) //!OCLINT bitwise operator in conditional - DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<, " < ", DOCTEST_CMP_LT) //!OCLINT bitwise operator in conditional - DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(>=, " >= ", DOCTEST_CMP_GE) //!OCLINT bitwise operator in conditional - DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<=, " <= ", DOCTEST_CMP_LE) //!OCLINT bitwise operator in conditional - // clang-format on - - // forbidding some expressions based on this table: http://en.cppreference.com/w/cpp/language/operator_precedence - DOCTEST_FORBIT_EXPRESSION(&) - DOCTEST_FORBIT_EXPRESSION (^) - DOCTEST_FORBIT_EXPRESSION(|) - DOCTEST_FORBIT_EXPRESSION(&&) - DOCTEST_FORBIT_EXPRESSION(||) - DOCTEST_FORBIT_EXPRESSION(=) - DOCTEST_FORBIT_EXPRESSION(+=) - DOCTEST_FORBIT_EXPRESSION(-=) - DOCTEST_FORBIT_EXPRESSION(*=) - DOCTEST_FORBIT_EXPRESSION(/=) - DOCTEST_FORBIT_EXPRESSION(%=) - DOCTEST_FORBIT_EXPRESSION(<<=) - DOCTEST_FORBIT_EXPRESSION(>>=) - DOCTEST_FORBIT_EXPRESSION(&=) - DOCTEST_FORBIT_EXPRESSION(^=) - DOCTEST_FORBIT_EXPRESSION(|=) - // these 2 are unfortunate because they should be allowed - they have higher precedence over the comparisons, but the - // ExpressionDecomposer class uses the left shift operator to capture the left operand of the binary expression... - DOCTEST_FORBIT_EXPRESSION(<<) - DOCTEST_FORBIT_EXPRESSION(>>) - }; - -#ifndef DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION - -#if defined(__clang__) -#pragma clang diagnostic pop -#endif // __clang__ - -#if defined(__GNUC__) && !defined(__clang__) -#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 6) -#pragma GCC diagnostic pop -#endif // > gcc 4.6 -#endif // __GNUC__ - -#ifdef _MSC_VER -#pragma warning(pop) -#endif // _MSC_VER - -#endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION - - struct ExpressionDecomposer - { - assertType::Enum m_assert_type; - - ExpressionDecomposer(assertType::Enum assert_type) - : m_assert_type(assert_type) {} - - // The right operator for capturing expressions is "<=" instead of "<<" (based on the operator precedence table) - // but then there will be warnings from GCC about "-Wparentheses" and since "_Pragma()" is problematic this will stay for now... - // https://github.com/philsquared/Catch/issues/870 - // https://github.com/philsquared/Catch/issues/565 - template - Expression_lhs operator<<(const DOCTEST_REF_WRAP(L) operand) { - return Expression_lhs(operand, m_assert_type); - } - }; - - struct DOCTEST_INTERFACE TestCase - { - // not used for determining uniqueness - funcType m_test; // a function pointer to the test case - String m_full_name; // contains the name (only for templated test cases!) + the template type - const char* m_name; // name of the test case - const char* m_type; // for templated test cases - gets appended to the real name - const char* m_test_suite; // the test suite in which the test was added - const char* m_description; - bool m_skip; - bool m_may_fail; - bool m_should_fail; - int m_expected_failures; - double m_timeout; - - // fields by which uniqueness of test cases shall be determined - const char* m_file; // the file in which the test was registered - unsigned m_line; // the line where the test was registered - int m_template_id; // an ID used to distinguish between the different versions of a templated test case - - TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite, - const char* type = "", int template_id = -1); - - // for gcc 4.7 - DOCTEST_NOINLINE ~TestCase() {} - - TestCase& operator*(const char* in); - - template - TestCase& operator*(const T& in) { - in.fill(*this); - return *this; - } - - TestCase(const TestCase& other) { *this = other; } - - TestCase& operator=(const TestCase& other); - - bool operator<(const TestCase& other) const; - }; - - // forward declarations of functions used by the macros - DOCTEST_INTERFACE int regTest(const TestCase& tc); - DOCTEST_INTERFACE int setTestSuite(const TestSuite& ts); - - DOCTEST_INTERFACE void addFailedAssert(assertType::Enum assert_type); - - DOCTEST_INTERFACE void logTestStart(const TestCase& tc); - DOCTEST_INTERFACE void logTestEnd(); - - DOCTEST_INTERFACE void logTestException(const String& what, bool crash = false); - - DOCTEST_INTERFACE void logAssert(bool passed, const char* decomposition, bool threw, - const String& exception, const char* expr, - assertType::Enum assert_type, const char* file, int line); - - DOCTEST_INTERFACE void logAssertThrows(bool threw, const char* expr, - assertType::Enum assert_type, const char* file, - int line); - - DOCTEST_INTERFACE void logAssertThrowsAs(bool threw, bool threw_as, const char* as, - const String& exception, const char* expr, - assertType::Enum assert_type, const char* file, - int line); - - DOCTEST_INTERFACE void logAssertNothrow(bool threw, const String& exception, const char* expr, - assertType::Enum assert_type, const char* file, - int line); - - DOCTEST_INTERFACE bool isDebuggerActive(); - DOCTEST_INTERFACE void writeToDebugConsole(const String&); - - namespace binaryAssertComparison - { - enum Enum - { - eq = 0, - ne, - gt, - lt, - ge, - le - }; - } // namespace binaryAssertComparison - - // clang-format off - template struct RelationalComparator { bool operator()(const DOCTEST_REF_WRAP(L), const DOCTEST_REF_WRAP(R) ) const { return false; } }; - template struct RelationalComparator<0, L, R> { bool operator()(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) const { return eq(lhs, rhs); } }; - template struct RelationalComparator<1, L, R> { bool operator()(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) const { return ne(lhs, rhs); } }; - template struct RelationalComparator<2, L, R> { bool operator()(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) const { return gt(lhs, rhs); } }; - template struct RelationalComparator<3, L, R> { bool operator()(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) const { return lt(lhs, rhs); } }; - template struct RelationalComparator<4, L, R> { bool operator()(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) const { return ge(lhs, rhs); } }; - template struct RelationalComparator<5, L, R> { bool operator()(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) const { return le(lhs, rhs); } }; - // clang-format on - - struct DOCTEST_INTERFACE ResultBuilder - { - assertType::Enum m_assert_type; - const char* m_file; - int m_line; - const char* m_expr; - const char* m_exception_type; - - Result m_result; - bool m_threw; - bool m_threw_as; - bool m_failed; - String m_exception; - - ResultBuilder(assertType::Enum assert_type, const char* file, int line, const char* expr, - const char* exception_type = ""); - - ~ResultBuilder(); - - void setResult(const Result& res) { m_result = res; } - - template - DOCTEST_NOINLINE void binary_assert(const DOCTEST_REF_WRAP(L) lhs, - const DOCTEST_REF_WRAP(R) rhs) { - m_result.m_passed = RelationalComparator()(lhs, rhs); - if(!m_result.m_passed || getTestsContextState()->success) - m_result.m_decomposition = stringifyBinaryExpr(lhs, ", ", rhs); - } - - template - DOCTEST_NOINLINE void unary_assert(const DOCTEST_REF_WRAP(L) val) { - m_result.m_passed = !!val; - - if(m_assert_type & assertType::is_false) //!OCLINT bitwise operator in conditional - m_result.m_passed = !m_result.m_passed; - - if(!m_result.m_passed || getTestsContextState()->success) - m_result.m_decomposition = toString(val); - } - - void unexpectedExceptionOccurred(); - - bool log(); - void react() const; - }; - - namespace assertAction - { - enum Enum - { - nothing = 0, - dbgbreak = 1, - shouldthrow = 2 - }; - } // namespace assertAction - - template - DOCTEST_NOINLINE int fast_binary_assert(assertType::Enum assert_type, const char* file, - int line, const char* expr, - const DOCTEST_REF_WRAP(L) lhs, - const DOCTEST_REF_WRAP(R) rhs) { - ResultBuilder rb(assert_type, file, line, expr); - - rb.m_result.m_passed = RelationalComparator()(lhs, rhs); - - if(!rb.m_result.m_passed || getTestsContextState()->success) - rb.m_result.m_decomposition = stringifyBinaryExpr(lhs, ", ", rhs); - - int res = 0; - - if(rb.log()) - res |= assertAction::dbgbreak; - - if(rb.m_failed && checkIfShouldThrow(assert_type)) - res |= assertAction::shouldthrow; - -#ifdef DOCTEST_CONFIG_SUPER_FAST_ASSERTS - // ######################################################################################### - // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK TO SEE THE FAILING ASSERTION - // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED - // ######################################################################################### - if(res & assertAction::dbgbreak) - DOCTEST_BREAK_INTO_DEBUGGER(); - fastAssertThrowIfFlagSet(res); -#endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS - - return res; - } - - template - DOCTEST_NOINLINE int fast_unary_assert(assertType::Enum assert_type, const char* file, int line, - const char* val_str, const DOCTEST_REF_WRAP(L) val) { - ResultBuilder rb(assert_type, file, line, val_str); - - rb.m_result.m_passed = !!val; - - if(assert_type & assertType::is_false) //!OCLINT bitwise operator in conditional - rb.m_result.m_passed = !rb.m_result.m_passed; - - if(!rb.m_result.m_passed || getTestsContextState()->success) - rb.m_result.m_decomposition = toString(val); - - int res = 0; - - if(rb.log()) - res |= assertAction::dbgbreak; - - if(rb.m_failed && checkIfShouldThrow(assert_type)) - res |= assertAction::shouldthrow; - -#ifdef DOCTEST_CONFIG_SUPER_FAST_ASSERTS - // ######################################################################################### - // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK TO SEE THE FAILING ASSERTION - // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED - // ######################################################################################### - if(res & assertAction::dbgbreak) - DOCTEST_BREAK_INTO_DEBUGGER(); - fastAssertThrowIfFlagSet(res); -#endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS - - return res; - } - - struct DOCTEST_INTERFACE IExceptionTranslator //!OCLINT destructor of virtual class - { - virtual ~IExceptionTranslator(); - virtual bool translate(String&) const = 0; - }; - - template - class ExceptionTranslator : public IExceptionTranslator //!OCLINT destructor of virtual class - { - public: - explicit ExceptionTranslator(String (*translateFunction)(T)) - : m_translateFunction(translateFunction) {} - - bool translate(String& res) const { -#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS - try { - throw; - // cppcheck-suppress catchExceptionByValue - } catch(T ex) { // NOLINT - res = m_translateFunction(ex); //!OCLINT parameter reassignment - return true; - } catch(...) {} //!OCLINT - empty catch statement -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS - ((void)res); // to silence -Wunused-parameter - return false; - } - - protected: - String (*m_translateFunction)(T); - }; - - DOCTEST_INTERFACE void registerExceptionTranslatorImpl( - const IExceptionTranslator* translateFunction); - - // FIX FOR VISUAL STUDIO VERSIONS PRIOR TO 2015 - they failed to compile the call to operator<< with - // std::ostream passed as a reference noting that there is a use of an undefined type (which there isn't) - DOCTEST_INTERFACE void writeStringToStream(std::ostream* stream, const String& str); - - template - struct StringStreamBase - { - template - static void convert(std::ostream* stream, const T& in) { - writeStringToStream(stream, toString(in)); - } - - // always treat char* as a string in this context - no matter - // if DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING is defined - static void convert(std::ostream* stream, const char* in) { - writeStringToStream(stream, String(in)); - } - }; - - template <> - struct StringStreamBase - { - template - static void convert(std::ostream* stream, const T& in) { - *stream << in; - } - }; - - template - struct StringStream : StringStreamBase::value> - {}; - - template - void toStream(std::ostream* stream, const T& value) { - StringStream::convert(stream, value); - } - -#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING - DOCTEST_INTERFACE void toStream(std::ostream* stream, char* in); - DOCTEST_INTERFACE void toStream(std::ostream* stream, const char* in); -#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING - DOCTEST_INTERFACE void toStream(std::ostream* stream, bool in); - DOCTEST_INTERFACE void toStream(std::ostream* stream, float in); - DOCTEST_INTERFACE void toStream(std::ostream* stream, double in); - DOCTEST_INTERFACE void toStream(std::ostream* stream, double long in); - - DOCTEST_INTERFACE void toStream(std::ostream* stream, char in); - DOCTEST_INTERFACE void toStream(std::ostream* stream, char signed in); - DOCTEST_INTERFACE void toStream(std::ostream* stream, char unsigned in); - DOCTEST_INTERFACE void toStream(std::ostream* stream, int short in); - DOCTEST_INTERFACE void toStream(std::ostream* stream, int short unsigned in); - DOCTEST_INTERFACE void toStream(std::ostream* stream, int in); - DOCTEST_INTERFACE void toStream(std::ostream* stream, int unsigned in); - DOCTEST_INTERFACE void toStream(std::ostream* stream, int long in); - DOCTEST_INTERFACE void toStream(std::ostream* stream, int long unsigned in); - -#ifdef DOCTEST_CONFIG_WITH_LONG_LONG - DOCTEST_INTERFACE void toStream(std::ostream* stream, int long long in); - DOCTEST_INTERFACE void toStream(std::ostream* stream, int long long unsigned in); -#endif // DOCTEST_CONFIG_WITH_LONG_LONG - - struct IContextScope //!OCLINT destructor of virtual class - { virtual void build(std::ostream*) = 0; }; - - DOCTEST_INTERFACE void addToContexts(IContextScope* ptr); - DOCTEST_INTERFACE void popFromContexts(); - DOCTEST_INTERFACE void useContextIfExceptionOccurred(IContextScope* ptr); - - // cppcheck-suppress copyCtorAndEqOperator - class ContextBuilder - { - friend class ContextScope; - - struct ICapture //!OCLINT destructor of virtual class - { virtual void toStream(std::ostream*) const = 0; }; - - template - struct Capture : ICapture //!OCLINT destructor of virtual class - { - const T* capture; - - explicit Capture(const T* in) - : capture(in) {} - virtual void toStream(std::ostream* stream) const { // override - doctest::detail::toStream(stream, *capture); - } - }; - - struct Chunk - { - char buf[sizeof(Capture)]; // place to construct a Capture - }; - - struct Node - { - Chunk chunk; - Node* next; - }; - - Chunk stackChunks[DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK]; - int numCaptures; - Node* head; - Node* tail; - - void build(std::ostream* stream) const { - int curr = 0; - // iterate over small buffer - while(curr < numCaptures && curr < DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK) - reinterpret_cast(stackChunks[curr++].buf)->toStream(stream); - // iterate over list - Node* curr_elem = head; - while(curr < numCaptures) { - reinterpret_cast(curr_elem->chunk.buf)->toStream(stream); - curr_elem = curr_elem->next; - ++curr; - } - } - - // steal the contents of the other - acting as a move constructor... - DOCTEST_NOINLINE ContextBuilder(ContextBuilder& other) - : numCaptures(other.numCaptures) - , head(other.head) - , tail(other.tail) { - other.numCaptures = 0; - other.head = 0; - other.tail = 0; - my_memcpy(stackChunks, other.stackChunks, - unsigned(int(sizeof(Chunk)) * DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK)); - } - - public: - // cppcheck-suppress uninitMemberVar - DOCTEST_NOINLINE ContextBuilder() // NOLINT - : numCaptures(0) - , head(0) - , tail(0) {} - - template - DOCTEST_NOINLINE ContextBuilder& operator<<(T& in) { - Capture temp(&in); - - // construct either on stack or on heap - // copy the bytes for the whole object - including the vtable because we cant construct - // the object directly in the buffer using placement new - need the header... - if(numCaptures < DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK) { - my_memcpy(stackChunks[numCaptures].buf, &temp, sizeof(Chunk)); - } else { - Node* curr = new Node; - curr->next = 0; - if(tail) { - tail->next = curr; - tail = curr; - } else { - head = tail = curr; - } - - my_memcpy(tail->chunk.buf, &temp, sizeof(Chunk)); - } - ++numCaptures; - return *this; - } - - DOCTEST_NOINLINE ~ContextBuilder() { - // free the linked list - the ones on the stack are left as-is - // no destructors are called at all - there is no need - while(head) { - Node* next = head->next; - delete head; - head = next; - } - } - -#ifdef DOCTEST_CONFIG_WITH_RVALUE_REFERENCES - template - ContextBuilder& operator<<(const T&&) { - DOCTEST_STATIC_ASSERT( - deferred_false::value, - Cannot_pass_temporaries_or_rvalues_to_the_streaming_operator_because_it_caches_pointers_to_the_passed_objects_for_lazy_evaluation); - return *this; - } -#endif // DOCTEST_CONFIG_WITH_RVALUE_REFERENCES - }; - - class ContextScope : public IContextScope //!OCLINT destructor of virtual class - { - ContextBuilder contextBuilder; - bool built; - - public: - DOCTEST_NOINLINE explicit ContextScope(ContextBuilder& temp) - : contextBuilder(temp) - , built(false) { - addToContexts(this); - } - - DOCTEST_NOINLINE ~ContextScope() { - if(!built) - useContextIfExceptionOccurred(this); - popFromContexts(); - } - - void build(std::ostream* stream) { - built = true; - contextBuilder.build(stream); - } - }; - - class DOCTEST_INTERFACE MessageBuilder - { - std::ostream* m_stream; - const char* m_file; - int m_line; - doctest::detail::assertType::Enum m_severity; - - public: - MessageBuilder(const char* file, int line, doctest::detail::assertType::Enum severity); - ~MessageBuilder(); - - template - MessageBuilder& operator<<(const T& in) { - doctest::detail::toStream(m_stream, in); - return *this; - } - - bool log(); - void react(); - }; -} // namespace detail - -struct test_suite -{ - const char* data; - test_suite(const char* in) - : data(in) {} - void fill(detail::TestCase& state) const { state.m_test_suite = data; } - void fill(detail::TestSuite& state) const { state.m_test_suite = data; } -}; - -struct description -{ - const char* data; - description(const char* in) - : data(in) {} - void fill(detail::TestCase& state) const { state.m_description = data; } - void fill(detail::TestSuite& state) const { state.m_description = data; } -}; - -struct skip -{ - bool data; - skip(bool in = true) - : data(in) {} - void fill(detail::TestCase& state) const { state.m_skip = data; } - void fill(detail::TestSuite& state) const { state.m_skip = data; } -}; - -struct timeout -{ - double data; - timeout(double in) - : data(in) {} - void fill(detail::TestCase& state) const { state.m_timeout = data; } - void fill(detail::TestSuite& state) const { state.m_timeout = data; } -}; - -struct may_fail -{ - bool data; - may_fail(bool in = true) - : data(in) {} - void fill(detail::TestCase& state) const { state.m_may_fail = data; } - void fill(detail::TestSuite& state) const { state.m_may_fail = data; } -}; - -struct should_fail -{ - bool data; - should_fail(bool in = true) - : data(in) {} - void fill(detail::TestCase& state) const { state.m_should_fail = data; } - void fill(detail::TestSuite& state) const { state.m_should_fail = data; } -}; - -struct expected_failures -{ - int data; - expected_failures(int in) - : data(in) {} - void fill(detail::TestCase& state) const { state.m_expected_failures = data; } - void fill(detail::TestSuite& state) const { state.m_expected_failures = data; } -}; - -#endif // DOCTEST_CONFIG_DISABLE - -#ifndef DOCTEST_CONFIG_DISABLE -template -int registerExceptionTranslator(String (*translateFunction)(T)) { -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wexit-time-destructors" -#endif // __clang__ - static detail::ExceptionTranslator exceptionTranslator(translateFunction); -#if defined(__clang__) -#pragma clang diagnostic pop -#endif // __clang__ - detail::registerExceptionTranslatorImpl(&exceptionTranslator); - return 0; -} - -#else // DOCTEST_CONFIG_DISABLE -template -int registerExceptionTranslator(String (*)(T)) { - return 0; -} -#endif // DOCTEST_CONFIG_DISABLE - -DOCTEST_INTERFACE bool isRunningInTest(); - -// cppcheck-suppress noCopyConstructor -class DOCTEST_INTERFACE Context -{ -#if !defined(DOCTEST_CONFIG_DISABLE) - detail::ContextState* p; - - void parseArgs(int argc, const char* const* argv, bool withDefaults = false); - -#endif // DOCTEST_CONFIG_DISABLE - -public: - explicit Context(int argc = 0, const char* const* argv = 0); - - ~Context(); - - void applyCommandLine(int argc, const char* const* argv); - - void addFilter(const char* filter, const char* value); - void clearFilters(); - void setOption(const char* option, int value); - void setOption(const char* option, const char* value); - - bool shouldExit(); - - int run(); -}; - -} // namespace doctest - -// if registering is not disabled -#if !defined(DOCTEST_CONFIG_DISABLE) - -#ifdef DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_EXPAND_VA_ARGS(...) __VA_ARGS__ -#else // DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_EXPAND_VA_ARGS -#endif // DOCTEST_CONFIG_WITH_VARIADIC_MACROS - -#define DOCTEST_STRIP_PARENS(x) x -#define DOCTEST_HANDLE_BRACED_VA_ARGS(expr) DOCTEST_STRIP_PARENS(DOCTEST_EXPAND_VA_ARGS expr) - -// registers the test by initializing a dummy var with a function -#define DOCTEST_REGISTER_FUNCTION(f, decorators) \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = doctest::detail::regTest( \ - doctest::detail::TestCase(f, __FILE__, __LINE__, \ - doctest_detail_test_suite_ns::getCurrentTestSuite()) * \ - decorators); \ - DOCTEST_GLOBAL_NO_WARNINGS_END() - -#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, decorators) \ - namespace \ - { \ - struct der : base \ - { void f(); }; \ - static void func() { \ - der v; \ - v.f(); \ - } \ - DOCTEST_REGISTER_FUNCTION(func, decorators) \ - } \ - inline DOCTEST_NOINLINE void der::f() - -#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, decorators) \ - static void f(); \ - DOCTEST_REGISTER_FUNCTION(f, decorators) \ - static void f() - -// for registering tests -#define DOCTEST_TEST_CASE(decorators) \ - DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), decorators) - -// for registering tests with a fixture -#define DOCTEST_TEST_CASE_FIXTURE(c, decorators) \ - DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(_DOCTEST_ANON_CLASS_), c, \ - DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), decorators) - -// for converting types to strings without the header and demangling -#ifdef DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_TYPE_TO_STRING_IMPL(...) \ - template <> \ - inline const char* type_to_string<__VA_ARGS__>() { \ - return "<" #__VA_ARGS__ ">"; \ - } -#define DOCTEST_TYPE_TO_STRING(...) \ - namespace doctest \ - { \ - namespace detail \ - { DOCTEST_TYPE_TO_STRING_IMPL(__VA_ARGS__) } \ - } \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) -#else // DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_TYPE_TO_STRING_IMPL(x) \ - template <> \ - inline const char* type_to_string() { \ - return "<" #x ">"; \ - } -#define DOCTEST_TYPE_TO_STRING(x) \ - namespace doctest \ - { \ - namespace detail \ - { DOCTEST_TYPE_TO_STRING_IMPL(x) } \ - } \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) -#endif // DOCTEST_CONFIG_WITH_VARIADIC_MACROS - -// for typed tests -#define DOCTEST_TEST_CASE_TEMPLATE_IMPL(decorators, T, types, anon) \ - template \ - inline void anon(); \ - struct DOCTEST_CAT(anon, FUNCTOR) \ - { \ - template \ - void operator()() { \ - doctest::detail::regTest( \ - doctest::detail::TestCase(anon, __FILE__, __LINE__, \ - doctest_detail_test_suite_ns::getCurrentTestSuite(), \ - doctest::detail::type_to_string(), Index) * \ - decorators); \ - } \ - }; \ - inline int DOCTEST_CAT(anon, REG_FUNC)() { \ - DOCTEST_CAT(anon, FUNCTOR) registrar; \ - doctest::detail::ForEachType \ - doIt(registrar); \ - return 0; \ - } \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_CAT(anon, DUMMY)) = DOCTEST_CAT(anon, REG_FUNC)(); \ - DOCTEST_GLOBAL_NO_WARNINGS_END() \ - template \ - inline void anon() - -#ifdef DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_TEST_CASE_TEMPLATE(decorators, T, ...) \ - DOCTEST_TEST_CASE_TEMPLATE_IMPL(decorators, T, (__VA_ARGS__), \ - DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)) -#else // DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_TEST_CASE_TEMPLATE(decorators, T, types) \ - DOCTEST_TEST_CASE_TEMPLATE_IMPL(decorators, T, types, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)) -#endif // DOCTEST_CONFIG_WITH_VARIADIC_MACROS - -#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(decorators, T, id, anon) \ - template \ - inline void anon(); \ - struct DOCTEST_CAT(id, _FUNCTOR) \ - { \ - int m_line; \ - DOCTEST_CAT(id, _FUNCTOR) \ - (int line) \ - : m_line(line) {} \ - template \ - void operator()() { \ - doctest::detail::regTest( \ - doctest::detail::TestCase(anon, __FILE__, __LINE__, \ - doctest_detail_test_suite_ns::getCurrentTestSuite(), \ - doctest::detail::type_to_string(), \ - m_line * 1000 + Index) * \ - decorators); \ - } \ - }; \ - template \ - inline void anon() - -#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(decorators, T, id) \ - DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(decorators, T, id, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)) - -#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, types, anon) \ - static int DOCTEST_CAT(anon, REG_FUNC)() { \ - DOCTEST_CAT(id, _FUNCTOR) registrar(__LINE__); \ - doctest::detail::ForEachType \ - doIt(registrar); \ - return 0; \ - } \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_CAT(anon, DUMMY)) = DOCTEST_CAT(anon, REG_FUNC)(); \ - DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) - -#ifdef DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE(id, ...) \ - DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, (__VA_ARGS__), \ - DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)) -#else // DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE(id, types) \ - DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, types, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)) -#endif // DOCTEST_CONFIG_WITH_VARIADIC_MACROS - -// for subcases -#if defined(__GNUC__) -#define DOCTEST_SUBCASE(name) \ - if(const doctest::detail::Subcase & DOCTEST_ANONYMOUS(_DOCTEST_ANON_SUBCASE_) \ - __attribute__((unused)) = \ - doctest::detail::Subcase(name, __FILE__, __LINE__)) -#else // __GNUC__ -#define DOCTEST_SUBCASE(name) \ - if(const doctest::detail::Subcase & DOCTEST_ANONYMOUS(_DOCTEST_ANON_SUBCASE_) = \ - doctest::detail::Subcase(name, __FILE__, __LINE__)) -#endif // __GNUC__ - -// for grouping tests in test suites by using code blocks -#define DOCTEST_TEST_SUITE_IMPL(decorators, ns_name) \ - namespace ns_name \ - { \ - namespace doctest_detail_test_suite_ns \ - { \ - inline DOCTEST_NOINLINE doctest::detail::TestSuite& getCurrentTestSuite() { \ - static doctest::detail::TestSuite data; \ - static bool inited = false; \ - if(!inited) { \ - data* decorators; \ - inited = true; \ - } \ - return data; \ - } \ - } \ - } \ - namespace ns_name - -#define DOCTEST_TEST_SUITE(decorators) \ - DOCTEST_TEST_SUITE_IMPL(decorators, DOCTEST_ANONYMOUS(_DOCTEST_ANON_SUITE_)) - -// for starting a testsuite block -#define DOCTEST_TEST_SUITE_BEGIN(decorators) \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \ - doctest::detail::setTestSuite(doctest::detail::TestSuite() * decorators); \ - DOCTEST_GLOBAL_NO_WARNINGS_END() \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) - -// for ending a testsuite block -#define DOCTEST_TEST_SUITE_END \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \ - doctest::detail::setTestSuite(doctest::detail::TestSuite() * ""); \ - DOCTEST_GLOBAL_NO_WARNINGS_END() \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) - -// for registering exception translators -#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(translatorName, signature) \ - static doctest::String translatorName(signature); \ - DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_)) = \ - doctest::registerExceptionTranslator(translatorName); \ - DOCTEST_GLOBAL_NO_WARNINGS_END() \ - static doctest::String translatorName(signature) - -#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \ - DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_), \ - signature) - -// for logging -#define DOCTEST_INFO(x) \ - doctest::detail::ContextScope DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_)( \ - doctest::detail::ContextBuilder() << x) -#define DOCTEST_CAPTURE(x) DOCTEST_INFO(#x " := " << x) - -#define DOCTEST_ADD_AT_IMPL(type, file, line, mb, x) \ - do { \ - doctest::detail::MessageBuilder mb(file, line, doctest::detail::assertType::type); \ - mb << x; \ - if(mb.log()) \ - DOCTEST_BREAK_INTO_DEBUGGER(); \ - mb.react(); \ - } while((void)0, 0) - -// clang-format off -#define DOCTEST_ADD_MESSAGE_AT(file, line, x) DOCTEST_ADD_AT_IMPL(is_warn, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), x) -#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, x) DOCTEST_ADD_AT_IMPL(is_check, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), x) -#define DOCTEST_ADD_FAIL_AT(file, line, x) DOCTEST_ADD_AT_IMPL(is_require, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), x) -// clang-format on - -#define DOCTEST_MESSAGE(x) DOCTEST_ADD_MESSAGE_AT(__FILE__, __LINE__, x) -#define DOCTEST_FAIL_CHECK(x) DOCTEST_ADD_FAIL_CHECK_AT(__FILE__, __LINE__, x) -#define DOCTEST_FAIL(x) DOCTEST_ADD_FAIL_AT(__FILE__, __LINE__, x) - -#if __cplusplus >= 201402L || (defined(_MSC_VER) && _MSC_VER >= 1910) -template -constexpr T to_lvalue = x; -#define DOCTEST_TO_LVALUE(...) to_lvalue -#else -#ifdef DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_TO_LVALUE(...) TO_LVALUE_CAN_BE_USED_ONLY_IN_CPP14_MODE_OR_WITH_VS_2017_OR_NEWER -#else // DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_TO_LVALUE(x) TO_LVALUE_CAN_BE_USED_ONLY_IN_CPP14_MODE_OR_WITH_VS_2017_OR_NEWER -#endif // DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#endif // TO_LVALUE hack for logging macros like INFO() - -// common code in asserts - for convenience -#define DOCTEST_ASSERT_LOG_AND_REACT(rb) \ - if(rb.log()) \ - DOCTEST_BREAK_INTO_DEBUGGER(); \ - rb.react() - -#ifdef DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS -#define DOCTEST_WRAP_IN_TRY(x) x; -#else // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS -#define DOCTEST_WRAP_IN_TRY(x) \ - try { \ - x; \ - } catch(...) { _DOCTEST_RB.unexpectedExceptionOccurred(); } -#endif // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS - -#define DOCTEST_ASSERT_IMPLEMENT_3(expr, assert_type) \ - doctest::detail::ResultBuilder _DOCTEST_RB( \ - doctest::detail::assertType::assert_type, __FILE__, __LINE__, \ - DOCTEST_TOSTR(DOCTEST_HANDLE_BRACED_VA_ARGS(expr))); \ - DOCTEST_WRAP_IN_TRY(_DOCTEST_RB.setResult( \ - doctest::detail::ExpressionDecomposer(doctest::detail::assertType::assert_type) \ - << DOCTEST_HANDLE_BRACED_VA_ARGS(expr))) \ - DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB) - -#if defined(__clang__) -#define DOCTEST_ASSERT_IMPLEMENT_2(expr, assert_type) \ - _Pragma("clang diagnostic push") \ - _Pragma("clang diagnostic ignored \"-Woverloaded-shift-op-parentheses\"") \ - DOCTEST_ASSERT_IMPLEMENT_3(expr, assert_type); \ - _Pragma("clang diagnostic pop") -#else // __clang__ -#define DOCTEST_ASSERT_IMPLEMENT_2(expr, assert_type) DOCTEST_ASSERT_IMPLEMENT_3(expr, assert_type); -#endif // __clang__ - -#define DOCTEST_ASSERT_IMPLEMENT_1(expr, assert_type) \ - do { \ - DOCTEST_ASSERT_IMPLEMENT_2(expr, assert_type); \ - } while((void)0, 0) - -#ifdef DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_WARN(...) DOCTEST_ASSERT_IMPLEMENT_1((__VA_ARGS__), DT_WARN) -#define DOCTEST_CHECK(...) DOCTEST_ASSERT_IMPLEMENT_1((__VA_ARGS__), DT_CHECK) -#define DOCTEST_REQUIRE(...) DOCTEST_ASSERT_IMPLEMENT_1((__VA_ARGS__), DT_REQUIRE) -#define DOCTEST_WARN_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1((__VA_ARGS__), DT_WARN_FALSE) -#define DOCTEST_CHECK_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1((__VA_ARGS__), DT_CHECK_FALSE) -#define DOCTEST_REQUIRE_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1((__VA_ARGS__), DT_REQUIRE_FALSE) -#else // DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_WARN(expr) DOCTEST_ASSERT_IMPLEMENT_1(expr, DT_WARN) -#define DOCTEST_CHECK(expr) DOCTEST_ASSERT_IMPLEMENT_1(expr, DT_CHECK) -#define DOCTEST_REQUIRE(expr) DOCTEST_ASSERT_IMPLEMENT_1(expr, DT_REQUIRE) -#define DOCTEST_WARN_FALSE(expr) DOCTEST_ASSERT_IMPLEMENT_1(expr, DT_WARN_FALSE) -#define DOCTEST_CHECK_FALSE(expr) DOCTEST_ASSERT_IMPLEMENT_1(expr, DT_CHECK_FALSE) -#define DOCTEST_REQUIRE_FALSE(expr) DOCTEST_ASSERT_IMPLEMENT_1(expr, DT_REQUIRE_FALSE) -#endif // DOCTEST_CONFIG_WITH_VARIADIC_MACROS - -// clang-format off -#ifdef DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_WARN_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2((cond), DT_WARN); } while((void)0, 0) -#define DOCTEST_CHECK_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2((cond), DT_CHECK); } while((void)0, 0) -#define DOCTEST_REQUIRE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2((cond), DT_REQUIRE); } while((void)0, 0) -#define DOCTEST_WARN_FALSE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2((cond), DT_WARN_FALSE); } while((void)0, 0) -#define DOCTEST_CHECK_FALSE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2((cond), DT_CHECK_FALSE); } while((void)0, 0) -#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2((cond), DT_REQUIRE_FALSE); } while((void)0, 0) -#else // DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_WARN_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(cond, DT_WARN); } while((void)0, 0) -#define DOCTEST_CHECK_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(cond, DT_CHECK); } while((void)0, 0) -#define DOCTEST_REQUIRE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(cond, DT_REQUIRE); } while((void)0, 0) -#define DOCTEST_WARN_FALSE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(cond, DT_WARN_FALSE); } while((void)0, 0) -#define DOCTEST_CHECK_FALSE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(cond, DT_CHECK_FALSE); } while((void)0, 0) -#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(cond, DT_REQUIRE_FALSE); } while((void)0, 0) -#endif // DOCTEST_CONFIG_WITH_VARIADIC_MACROS -// clang-format on - -#define DOCTEST_ASSERT_THROWS(expr, assert_type) \ - do { \ - if(!doctest::detail::getTestsContextState()->no_throw) { \ - doctest::detail::ResultBuilder _DOCTEST_RB(doctest::detail::assertType::assert_type, \ - __FILE__, __LINE__, #expr); \ - try { \ - expr; \ - } catch(...) { _DOCTEST_RB.m_threw = true; } \ - DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ - } \ - } while((void)0, 0) - -#define DOCTEST_ASSERT_THROWS_AS(expr, as, assert_type) \ - do { \ - if(!doctest::detail::getTestsContextState()->no_throw) { \ - doctest::detail::ResultBuilder _DOCTEST_RB( \ - doctest::detail::assertType::assert_type, __FILE__, __LINE__, #expr, \ - DOCTEST_TOSTR(DOCTEST_HANDLE_BRACED_VA_ARGS(as))); \ - try { \ - expr; \ - } catch(DOCTEST_HANDLE_BRACED_VA_ARGS(as)) { \ - _DOCTEST_RB.m_threw = true; \ - _DOCTEST_RB.m_threw_as = true; \ - } catch(...) { _DOCTEST_RB.unexpectedExceptionOccurred(); } \ - DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ - } \ - } while((void)0, 0) - -#define DOCTEST_ASSERT_NOTHROW(expr, assert_type) \ - do { \ - if(!doctest::detail::getTestsContextState()->no_throw) { \ - doctest::detail::ResultBuilder _DOCTEST_RB(doctest::detail::assertType::assert_type, \ - __FILE__, __LINE__, #expr); \ - try { \ - expr; \ - } catch(...) { _DOCTEST_RB.unexpectedExceptionOccurred(); } \ - DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ - } \ - } while((void)0, 0) - -#define DOCTEST_WARN_THROWS(expr) DOCTEST_ASSERT_THROWS(expr, DT_WARN_THROWS) -#define DOCTEST_CHECK_THROWS(expr) DOCTEST_ASSERT_THROWS(expr, DT_CHECK_THROWS) -#define DOCTEST_REQUIRE_THROWS(expr) DOCTEST_ASSERT_THROWS(expr, DT_REQUIRE_THROWS) - -// clang-format off -#ifdef DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, (__VA_ARGS__), DT_WARN_THROWS_AS) -#define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, (__VA_ARGS__), DT_CHECK_THROWS_AS) -#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, (__VA_ARGS__), DT_REQUIRE_THROWS_AS) -#else // DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_WARN_THROWS_AS(expr, ex) DOCTEST_ASSERT_THROWS_AS(expr, ex, DT_WARN_THROWS_AS) -#define DOCTEST_CHECK_THROWS_AS(expr, ex) DOCTEST_ASSERT_THROWS_AS(expr, ex, DT_CHECK_THROWS_AS) -#define DOCTEST_REQUIRE_THROWS_AS(expr, ex) DOCTEST_ASSERT_THROWS_AS(expr, ex, DT_REQUIRE_THROWS_AS) -#endif // DOCTEST_CONFIG_WITH_VARIADIC_MACROS -// clang-format on - -#define DOCTEST_WARN_NOTHROW(expr) DOCTEST_ASSERT_NOTHROW(expr, DT_WARN_NOTHROW) -#define DOCTEST_CHECK_NOTHROW(expr) DOCTEST_ASSERT_NOTHROW(expr, DT_CHECK_NOTHROW) -#define DOCTEST_REQUIRE_NOTHROW(expr) DOCTEST_ASSERT_NOTHROW(expr, DT_REQUIRE_NOTHROW) - -// clang-format off -#define DOCTEST_WARN_THROWS_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS(expr); } while((void)0, 0) -#define DOCTEST_CHECK_THROWS_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS(expr); } while((void)0, 0) -#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS(expr); } while((void)0, 0) -#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS_AS(expr, ex); } while((void)0, 0) -#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS_AS(expr, ex); } while((void)0, 0) -#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS_AS(expr, ex); } while((void)0, 0) -#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_NOTHROW(expr); } while((void)0, 0) -#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_NOTHROW(expr); } while((void)0, 0) -#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_NOTHROW(expr); } while((void)0, 0) -// clang-format on - -#ifdef DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_BINARY_ASSERT(assert_type, expr, comp) \ - do { \ - doctest::detail::ResultBuilder _DOCTEST_RB( \ - doctest::detail::assertType::assert_type, __FILE__, __LINE__, \ - DOCTEST_TOSTR(DOCTEST_HANDLE_BRACED_VA_ARGS(expr))); \ - DOCTEST_WRAP_IN_TRY( \ - _DOCTEST_RB.binary_assert( \ - DOCTEST_HANDLE_BRACED_VA_ARGS(expr))) \ - DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ - } while((void)0, 0) -#else // DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_BINARY_ASSERT(assert_type, lhs, rhs, comp) \ - do { \ - doctest::detail::ResultBuilder _DOCTEST_RB(doctest::detail::assertType::assert_type, \ - __FILE__, __LINE__, #lhs ", " #rhs); \ - DOCTEST_WRAP_IN_TRY( \ - _DOCTEST_RB.binary_assert(lhs, \ - rhs)) \ - DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ - } while((void)0, 0) -#endif // DOCTEST_CONFIG_WITH_VARIADIC_MACROS - -#define DOCTEST_UNARY_ASSERT(assert_type, expr) \ - do { \ - doctest::detail::ResultBuilder _DOCTEST_RB( \ - doctest::detail::assertType::assert_type, __FILE__, __LINE__, \ - DOCTEST_TOSTR(DOCTEST_HANDLE_BRACED_VA_ARGS(expr))); \ - DOCTEST_WRAP_IN_TRY(_DOCTEST_RB.unary_assert(DOCTEST_HANDLE_BRACED_VA_ARGS(expr))) \ - DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ - } while((void)0, 0) - -#ifdef DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_WARN_EQ(...) DOCTEST_BINARY_ASSERT(DT_WARN_EQ, (__VA_ARGS__), eq) -#define DOCTEST_CHECK_EQ(...) DOCTEST_BINARY_ASSERT(DT_CHECK_EQ, (__VA_ARGS__), eq) -#define DOCTEST_REQUIRE_EQ(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_EQ, (__VA_ARGS__), eq) -#define DOCTEST_WARN_NE(...) DOCTEST_BINARY_ASSERT(DT_WARN_NE, (__VA_ARGS__), ne) -#define DOCTEST_CHECK_NE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_NE, (__VA_ARGS__), ne) -#define DOCTEST_REQUIRE_NE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_NE, (__VA_ARGS__), ne) -#define DOCTEST_WARN_GT(...) DOCTEST_BINARY_ASSERT(DT_WARN_GT, (__VA_ARGS__), gt) -#define DOCTEST_CHECK_GT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GT, (__VA_ARGS__), gt) -#define DOCTEST_REQUIRE_GT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GT, (__VA_ARGS__), gt) -#define DOCTEST_WARN_LT(...) DOCTEST_BINARY_ASSERT(DT_WARN_LT, (__VA_ARGS__), lt) -#define DOCTEST_CHECK_LT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LT, (__VA_ARGS__), lt) -#define DOCTEST_REQUIRE_LT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LT, (__VA_ARGS__), lt) -#define DOCTEST_WARN_GE(...) DOCTEST_BINARY_ASSERT(DT_WARN_GE, (__VA_ARGS__), ge) -#define DOCTEST_CHECK_GE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GE, (__VA_ARGS__), ge) -#define DOCTEST_REQUIRE_GE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GE, (__VA_ARGS__), ge) -#define DOCTEST_WARN_LE(...) DOCTEST_BINARY_ASSERT(DT_WARN_LE, (__VA_ARGS__), le) -#define DOCTEST_CHECK_LE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LE, (__VA_ARGS__), le) -#define DOCTEST_REQUIRE_LE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LE, (__VA_ARGS__), le) - -#define DOCTEST_WARN_UNARY(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY, (__VA_ARGS__)) -#define DOCTEST_CHECK_UNARY(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY, (__VA_ARGS__)) -#define DOCTEST_REQUIRE_UNARY(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY, (__VA_ARGS__)) -#define DOCTEST_WARN_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY_FALSE, (__VA_ARGS__)) -#define DOCTEST_CHECK_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY_FALSE, (__VA_ARGS__)) -#define DOCTEST_REQUIRE_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY_FALSE, (__VA_ARGS__)) -#else // DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_WARN_EQ(lhs, rhs) DOCTEST_BINARY_ASSERT(DT_WARN_EQ, lhs, rhs, eq) -#define DOCTEST_CHECK_EQ(lhs, rhs) DOCTEST_BINARY_ASSERT(DT_CHECK_EQ, lhs, rhs, eq) -#define DOCTEST_REQUIRE_EQ(lhs, rhs) DOCTEST_BINARY_ASSERT(DT_REQUIRE_EQ, lhs, rhs, eq) -#define DOCTEST_WARN_NE(lhs, rhs) DOCTEST_BINARY_ASSERT(DT_WARN_NE, lhs, rhs, ne) -#define DOCTEST_CHECK_NE(lhs, rhs) DOCTEST_BINARY_ASSERT(DT_CHECK_NE, lhs, rhs, ne) -#define DOCTEST_REQUIRE_NE(lhs, rhs) DOCTEST_BINARY_ASSERT(DT_REQUIRE_NE, lhs, rhs, ne) -#define DOCTEST_WARN_GT(lhs, rhs) DOCTEST_BINARY_ASSERT(DT_WARN_GT, lhs, rhs, gt) -#define DOCTEST_CHECK_GT(lhs, rhs) DOCTEST_BINARY_ASSERT(DT_CHECK_GT, lhs, rhs, gt) -#define DOCTEST_REQUIRE_GT(lhs, rhs) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GT, lhs, rhs, gt) -#define DOCTEST_WARN_LT(lhs, rhs) DOCTEST_BINARY_ASSERT(DT_WARN_LT, lhs, rhs, lt) -#define DOCTEST_CHECK_LT(lhs, rhs) DOCTEST_BINARY_ASSERT(DT_CHECK_LT, lhs, rhs, lt) -#define DOCTEST_REQUIRE_LT(lhs, rhs) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LT, lhs, rhs, lt) -#define DOCTEST_WARN_GE(lhs, rhs) DOCTEST_BINARY_ASSERT(DT_WARN_GE, lhs, rhs, ge) -#define DOCTEST_CHECK_GE(lhs, rhs) DOCTEST_BINARY_ASSERT(DT_CHECK_GE, lhs, rhs, ge) -#define DOCTEST_REQUIRE_GE(lhs, rhs) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GE, lhs, rhs, ge) -#define DOCTEST_WARN_LE(lhs, rhs) DOCTEST_BINARY_ASSERT(DT_WARN_LE, lhs, rhs, le) -#define DOCTEST_CHECK_LE(lhs, rhs) DOCTEST_BINARY_ASSERT(DT_CHECK_LE, lhs, rhs, le) -#define DOCTEST_REQUIRE_LE(lhs, rhs) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LE, lhs, rhs, le) - -#define DOCTEST_WARN_UNARY(v) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY, v) -#define DOCTEST_CHECK_UNARY(v) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY, v) -#define DOCTEST_REQUIRE_UNARY(v) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY, v) -#define DOCTEST_WARN_UNARY_FALSE(v) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY_FALSE, v) -#define DOCTEST_CHECK_UNARY_FALSE(v) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY_FALSE, v) -#define DOCTEST_REQUIRE_UNARY_FALSE(v) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY_FALSE, v) -#endif // DOCTEST_CONFIG_WITH_VARIADIC_MACROS - -#ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS - -#ifdef DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_FAST_BINARY_ASSERT(assert_type, expr, comparison) \ - do { \ - int _DOCTEST_FAST_RES = doctest::detail::fast_binary_assert< \ - doctest::detail::binaryAssertComparison::comparison>( \ - doctest::detail::assertType::assert_type, __FILE__, __LINE__, \ - DOCTEST_TOSTR(DOCTEST_HANDLE_BRACED_VA_ARGS(expr)), \ - DOCTEST_HANDLE_BRACED_VA_ARGS(expr)); \ - if(_DOCTEST_FAST_RES & doctest::detail::assertAction::dbgbreak) \ - DOCTEST_BREAK_INTO_DEBUGGER(); \ - doctest::detail::fastAssertThrowIfFlagSet(_DOCTEST_FAST_RES); \ - } while((void)0, 0) -#else // DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_FAST_BINARY_ASSERT(assert_type, lhs, rhs, comparison) \ - do { \ - int _DOCTEST_FAST_RES = doctest::detail::fast_binary_assert< \ - doctest::detail::binaryAssertComparison::comparison>( \ - doctest::detail::assertType::assert_type, __FILE__, __LINE__, #lhs ", " #rhs, lhs, \ - rhs); \ - if(_DOCTEST_FAST_RES & doctest::detail::assertAction::dbgbreak) \ - DOCTEST_BREAK_INTO_DEBUGGER(); \ - doctest::detail::fastAssertThrowIfFlagSet(_DOCTEST_FAST_RES); \ - } while((void)0, 0) -#endif // DOCTEST_CONFIG_WITH_VARIADIC_MACROS - -#define DOCTEST_FAST_UNARY_ASSERT(assert_type, expr) \ - do { \ - int _DOCTEST_FAST_RES = doctest::detail::fast_unary_assert( \ - doctest::detail::assertType::assert_type, __FILE__, __LINE__, \ - DOCTEST_TOSTR(DOCTEST_HANDLE_BRACED_VA_ARGS(expr)), \ - DOCTEST_HANDLE_BRACED_VA_ARGS(expr)); \ - if(_DOCTEST_FAST_RES & doctest::detail::assertAction::dbgbreak) \ - DOCTEST_BREAK_INTO_DEBUGGER(); \ - doctest::detail::fastAssertThrowIfFlagSet(_DOCTEST_FAST_RES); \ - } while((void)0, 0) - -#else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS - -#ifdef DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_FAST_BINARY_ASSERT(assert_type, expr, comparison) \ - doctest::detail::fast_binary_assert( \ - doctest::detail::assertType::assert_type, __FILE__, __LINE__, \ - DOCTEST_TOSTR(DOCTEST_HANDLE_BRACED_VA_ARGS(expr)), \ - DOCTEST_HANDLE_BRACED_VA_ARGS(expr)) -#else // DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_FAST_BINARY_ASSERT(assert_type, lhs, rhs, comparison) \ - doctest::detail::fast_binary_assert( \ - doctest::detail::assertType::assert_type, __FILE__, __LINE__, #lhs ", " #rhs, lhs, \ - rhs) -#endif // DOCTEST_CONFIG_WITH_VARIADIC_MACROS - -#define DOCTEST_FAST_UNARY_ASSERT(assert_type, expr) \ - doctest::detail::fast_unary_assert(doctest::detail::assertType::assert_type, __FILE__, \ - __LINE__, \ - DOCTEST_TOSTR(DOCTEST_HANDLE_BRACED_VA_ARGS(expr)), \ - DOCTEST_HANDLE_BRACED_VA_ARGS(expr)) - -#endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS - -// clang-format off -#ifdef DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_FAST_WARN_EQ(...) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_WARN_EQ, (__VA_ARGS__), eq) -#define DOCTEST_FAST_CHECK_EQ(...) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_CHECK_EQ, (__VA_ARGS__), eq) -#define DOCTEST_FAST_REQUIRE_EQ(...) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_REQUIRE_EQ, (__VA_ARGS__), eq) -#define DOCTEST_FAST_WARN_NE(...) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_WARN_NE, (__VA_ARGS__), ne) -#define DOCTEST_FAST_CHECK_NE(...) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_CHECK_NE, (__VA_ARGS__), ne) -#define DOCTEST_FAST_REQUIRE_NE(...) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_REQUIRE_NE, (__VA_ARGS__), ne) -#define DOCTEST_FAST_WARN_GT(...) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_WARN_GT, (__VA_ARGS__), gt) -#define DOCTEST_FAST_CHECK_GT(...) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_CHECK_GT, (__VA_ARGS__), gt) -#define DOCTEST_FAST_REQUIRE_GT(...) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_REQUIRE_GT, (__VA_ARGS__), gt) -#define DOCTEST_FAST_WARN_LT(...) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_WARN_LT, (__VA_ARGS__), lt) -#define DOCTEST_FAST_CHECK_LT(...) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_CHECK_LT, (__VA_ARGS__), lt) -#define DOCTEST_FAST_REQUIRE_LT(...) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_REQUIRE_LT, (__VA_ARGS__), lt) -#define DOCTEST_FAST_WARN_GE(...) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_WARN_GE, (__VA_ARGS__), ge) -#define DOCTEST_FAST_CHECK_GE(...) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_CHECK_GE, (__VA_ARGS__), ge) -#define DOCTEST_FAST_REQUIRE_GE(...) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_REQUIRE_GE, (__VA_ARGS__), ge) -#define DOCTEST_FAST_WARN_LE(...) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_WARN_LE, (__VA_ARGS__), le) -#define DOCTEST_FAST_CHECK_LE(...) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_CHECK_LE, (__VA_ARGS__), le) -#define DOCTEST_FAST_REQUIRE_LE(...) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_REQUIRE_LE, (__VA_ARGS__), le) - -#define DOCTEST_FAST_WARN_UNARY(...) DOCTEST_FAST_UNARY_ASSERT(DT_FAST_WARN_UNARY, (__VA_ARGS__)) -#define DOCTEST_FAST_CHECK_UNARY(...) DOCTEST_FAST_UNARY_ASSERT(DT_FAST_CHECK_UNARY, (__VA_ARGS__)) -#define DOCTEST_FAST_REQUIRE_UNARY(...) DOCTEST_FAST_UNARY_ASSERT(DT_FAST_REQUIRE_UNARY, (__VA_ARGS__)) -#define DOCTEST_FAST_WARN_UNARY_FALSE(...) DOCTEST_FAST_UNARY_ASSERT(DT_FAST_WARN_UNARY_FALSE, (__VA_ARGS__)) -#define DOCTEST_FAST_CHECK_UNARY_FALSE(...) DOCTEST_FAST_UNARY_ASSERT(DT_FAST_CHECK_UNARY_FALSE, (__VA_ARGS__)) -#define DOCTEST_FAST_REQUIRE_UNARY_FALSE(...) DOCTEST_FAST_UNARY_ASSERT(DT_FAST_REQUIRE_UNARY_FALSE, (__VA_ARGS__)) -#else // DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_FAST_WARN_EQ(l, r) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_WARN_EQ, l, r, eq) -#define DOCTEST_FAST_CHECK_EQ(l, r) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_CHECK_EQ, l, r, eq) -#define DOCTEST_FAST_REQUIRE_EQ(l, r) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_REQUIRE_EQ, l, r, eq) -#define DOCTEST_FAST_WARN_NE(l, r) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_WARN_NE, l, r, ne) -#define DOCTEST_FAST_CHECK_NE(l, r) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_CHECK_NE, l, r, ne) -#define DOCTEST_FAST_REQUIRE_NE(l, r) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_REQUIRE_NE, l, r, ne) -#define DOCTEST_FAST_WARN_GT(l, r) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_WARN_GT, l, r, gt) -#define DOCTEST_FAST_CHECK_GT(l, r) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_CHECK_GT, l, r, gt) -#define DOCTEST_FAST_REQUIRE_GT(l, r) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_REQUIRE_GT, l, r, gt) -#define DOCTEST_FAST_WARN_LT(l, r) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_WARN_LT, l, r, lt) -#define DOCTEST_FAST_CHECK_LT(l, r) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_CHECK_LT, l, r, lt) -#define DOCTEST_FAST_REQUIRE_LT(l, r) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_REQUIRE_LT, l, r, lt) -#define DOCTEST_FAST_WARN_GE(l, r) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_WARN_GE, l, r, ge) -#define DOCTEST_FAST_CHECK_GE(l, r) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_CHECK_GE, l, r, ge) -#define DOCTEST_FAST_REQUIRE_GE(l, r) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_REQUIRE_GE, l, r, ge) -#define DOCTEST_FAST_WARN_LE(l, r) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_WARN_LE, l, r, le) -#define DOCTEST_FAST_CHECK_LE(l, r) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_CHECK_LE, l, r, le) -#define DOCTEST_FAST_REQUIRE_LE(l, r) DOCTEST_FAST_BINARY_ASSERT(DT_FAST_REQUIRE_LE, l, r, le) - -#define DOCTEST_FAST_WARN_UNARY(v) DOCTEST_FAST_UNARY_ASSERT(DT_FAST_WARN_UNARY, v) -#define DOCTEST_FAST_CHECK_UNARY(v) DOCTEST_FAST_UNARY_ASSERT(DT_FAST_CHECK_UNARY, v) -#define DOCTEST_FAST_REQUIRE_UNARY(v) DOCTEST_FAST_UNARY_ASSERT(DT_FAST_REQUIRE_UNARY, v) -#define DOCTEST_FAST_WARN_UNARY_FALSE(v) DOCTEST_FAST_UNARY_ASSERT(DT_FAST_WARN_UNARY_FALSE, v) -#define DOCTEST_FAST_CHECK_UNARY_FALSE(v) DOCTEST_FAST_UNARY_ASSERT(DT_FAST_CHECK_UNARY_FALSE, v) -#define DOCTEST_FAST_REQUIRE_UNARY_FALSE(v) DOCTEST_FAST_UNARY_ASSERT(DT_FAST_REQUIRE_UNARY_FALSE, v) -#endif // DOCTEST_CONFIG_WITH_VARIADIC_MACROS -// clang-format on - -#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS - -#undef DOCTEST_WARN_THROWS -#undef DOCTEST_CHECK_THROWS -#undef DOCTEST_REQUIRE_THROWS -#undef DOCTEST_WARN_THROWS_AS -#undef DOCTEST_CHECK_THROWS_AS -#undef DOCTEST_REQUIRE_THROWS_AS -#undef DOCTEST_WARN_NOTHROW -#undef DOCTEST_CHECK_NOTHROW -#undef DOCTEST_REQUIRE_NOTHROW - -#undef DOCTEST_WARN_THROWS_MESSAGE -#undef DOCTEST_CHECK_THROWS_MESSAGE -#undef DOCTEST_REQUIRE_THROWS_MESSAGE -#undef DOCTEST_WARN_THROWS_AS_MESSAGE -#undef DOCTEST_CHECK_THROWS_AS_MESSAGE -#undef DOCTEST_REQUIRE_THROWS_AS_MESSAGE -#undef DOCTEST_WARN_NOTHROW_MESSAGE -#undef DOCTEST_CHECK_NOTHROW_MESSAGE -#undef DOCTEST_REQUIRE_NOTHROW_MESSAGE - -#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS - -#define DOCTEST_WARN_THROWS(expr) ((void)0) -#define DOCTEST_CHECK_THROWS(expr) ((void)0) -#define DOCTEST_REQUIRE_THROWS(expr) ((void)0) -#ifdef DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_WARN_THROWS_AS(expr, ...) ((void)0) -#define DOCTEST_CHECK_THROWS_AS(expr, ...) ((void)0) -#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) ((void)0) -#else // DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_WARN_THROWS_AS(expr, ex) ((void)0) -#define DOCTEST_CHECK_THROWS_AS(expr, ex) ((void)0) -#define DOCTEST_REQUIRE_THROWS_AS(expr, ex) ((void)0) -#endif // DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_WARN_NOTHROW(expr) ((void)0) -#define DOCTEST_CHECK_NOTHROW(expr) ((void)0) -#define DOCTEST_REQUIRE_NOTHROW(expr) ((void)0) - -#define DOCTEST_WARN_THROWS_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_CHECK_THROWS_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) -#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) -#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) -#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, msg) ((void)0) - -#else // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS - -#undef DOCTEST_REQUIRE -#undef DOCTEST_REQUIRE_FALSE -#undef DOCTEST_REQUIRE_MESSAGE -#undef DOCTEST_REQUIRE_FALSE_MESSAGE -#undef DOCTEST_REQUIRE_EQ -#undef DOCTEST_REQUIRE_NE -#undef DOCTEST_REQUIRE_GT -#undef DOCTEST_REQUIRE_LT -#undef DOCTEST_REQUIRE_GE -#undef DOCTEST_REQUIRE_LE -#undef DOCTEST_REQUIRE_UNARY -#undef DOCTEST_REQUIRE_UNARY_FALSE -#undef DOCTEST_FAST_REQUIRE_EQ -#undef DOCTEST_FAST_REQUIRE_NE -#undef DOCTEST_FAST_REQUIRE_GT -#undef DOCTEST_FAST_REQUIRE_LT -#undef DOCTEST_FAST_REQUIRE_GE -#undef DOCTEST_FAST_REQUIRE_LE -#undef DOCTEST_FAST_REQUIRE_UNARY -#undef DOCTEST_FAST_REQUIRE_UNARY_FALSE - -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS - -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS - -// ================================================================================================= -// == WHAT FOLLOWS IS VERSIONS OF THE MACROS THAT DO NOT DO ANY REGISTERING! == -// == THIS CAN BE ENABLED BY DEFINING DOCTEST_CONFIG_DISABLE GLOBALLY! == -// ================================================================================================= -#else // DOCTEST_CONFIG_DISABLE - -#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, name) \ - namespace \ - { \ - template \ - struct der : base \ - { void f(); }; \ - } \ - template \ - inline void der::f() - -#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, name) \ - template \ - static inline void f() - -// for registering tests -#define DOCTEST_TEST_CASE(name) \ - DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name) - -// for registering tests with a fixture -#define DOCTEST_TEST_CASE_FIXTURE(x, name) \ - DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(_DOCTEST_ANON_CLASS_), x, \ - DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name) - -// for converting types to strings without the header and demangling -#ifdef DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_TYPE_TO_STRING(...) typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) -#define DOCTEST_TYPE_TO_STRING_IMPL(...) -#else // DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_TYPE_TO_STRING(x) typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) -#define DOCTEST_TYPE_TO_STRING_IMPL(x) -#endif // DOCTEST_CONFIG_WITH_VARIADIC_MACROS - -// for typed tests -#define DOCTEST_TEST_CASE_TEMPLATE(name, type, types) \ - template \ - inline void DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)() - -#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, type, id) \ - template \ - inline void DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)() - -#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE(id, types) \ - typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) - -// for subcases -#define DOCTEST_SUBCASE(name) - -// for a testsuite block -#define DOCTEST_TEST_SUITE(name) namespace - -// for starting a testsuite block -#define DOCTEST_TEST_SUITE_BEGIN(name) typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) - -// for ending a testsuite block -#define DOCTEST_TEST_SUITE_END typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) - -#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \ - template \ - static inline doctest::String DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_)(signature) - -#define DOCTEST_INFO(x) ((void)0) -#define DOCTEST_CAPTURE(x) ((void)0) -#define DOCTEST_ADD_MESSAGE_AT(file, line, x) ((void)0) -#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, x) ((void)0) -#define DOCTEST_ADD_FAIL_AT(file, line, x) ((void)0) -#define DOCTEST_MESSAGE(x) ((void)0) -#define DOCTEST_FAIL_CHECK(x) ((void)0) -#define DOCTEST_FAIL(x) ((void)0) - -#ifdef DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_WARN(...) ((void)0) -#define DOCTEST_CHECK(...) ((void)0) -#define DOCTEST_REQUIRE(...) ((void)0) -#define DOCTEST_WARN_FALSE(...) ((void)0) -#define DOCTEST_CHECK_FALSE(...) ((void)0) -#define DOCTEST_REQUIRE_FALSE(...) ((void)0) -#else // DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_WARN(expr) ((void)0) -#define DOCTEST_CHECK(expr) ((void)0) -#define DOCTEST_REQUIRE(expr) ((void)0) -#define DOCTEST_WARN_FALSE(expr) ((void)0) -#define DOCTEST_CHECK_FALSE(expr) ((void)0) -#define DOCTEST_REQUIRE_FALSE(expr) ((void)0) -#endif // DOCTEST_CONFIG_WITH_VARIADIC_MACROS - -#define DOCTEST_WARN_MESSAGE(cond, msg) ((void)0) -#define DOCTEST_CHECK_MESSAGE(cond, msg) ((void)0) -#define DOCTEST_REQUIRE_MESSAGE(cond, msg) ((void)0) -#define DOCTEST_WARN_FALSE_MESSAGE(cond, msg) ((void)0) -#define DOCTEST_CHECK_FALSE_MESSAGE(cond, msg) ((void)0) -#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, msg) ((void)0) - -#define DOCTEST_WARN_THROWS(expr) ((void)0) -#define DOCTEST_CHECK_THROWS(expr) ((void)0) -#define DOCTEST_REQUIRE_THROWS(expr) ((void)0) -#ifdef DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_WARN_THROWS_AS(expr, ...) ((void)0) -#define DOCTEST_CHECK_THROWS_AS(expr, ...) ((void)0) -#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) ((void)0) -#else // DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_WARN_THROWS_AS(expr, ex) ((void)0) -#define DOCTEST_CHECK_THROWS_AS(expr, ex) ((void)0) -#define DOCTEST_REQUIRE_THROWS_AS(expr, ex) ((void)0) -#endif // DOCTEST_CONFIG_WITH_VARIADIC_MACROS -#define DOCTEST_WARN_NOTHROW(expr) ((void)0) -#define DOCTEST_CHECK_NOTHROW(expr) ((void)0) -#define DOCTEST_REQUIRE_NOTHROW(expr) ((void)0) - -#define DOCTEST_WARN_THROWS_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_CHECK_THROWS_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) -#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) -#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) -#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, msg) ((void)0) - -#ifdef DOCTEST_CONFIG_WITH_VARIADIC_MACROS - -#define DOCTEST_WARN_EQ(...) ((void)0) -#define DOCTEST_CHECK_EQ(...) ((void)0) -#define DOCTEST_REQUIRE_EQ(...) ((void)0) -#define DOCTEST_WARN_NE(...) ((void)0) -#define DOCTEST_CHECK_NE(...) ((void)0) -#define DOCTEST_REQUIRE_NE(...) ((void)0) -#define DOCTEST_WARN_GT(...) ((void)0) -#define DOCTEST_CHECK_GT(...) ((void)0) -#define DOCTEST_REQUIRE_GT(...) ((void)0) -#define DOCTEST_WARN_LT(...) ((void)0) -#define DOCTEST_CHECK_LT(...) ((void)0) -#define DOCTEST_REQUIRE_LT(...) ((void)0) -#define DOCTEST_WARN_GE(...) ((void)0) -#define DOCTEST_CHECK_GE(...) ((void)0) -#define DOCTEST_REQUIRE_GE(...) ((void)0) -#define DOCTEST_WARN_LE(...) ((void)0) -#define DOCTEST_CHECK_LE(...) ((void)0) -#define DOCTEST_REQUIRE_LE(...) ((void)0) - -#define DOCTEST_WARN_UNARY(...) ((void)0) -#define DOCTEST_CHECK_UNARY(...) ((void)0) -#define DOCTEST_REQUIRE_UNARY(...) ((void)0) -#define DOCTEST_WARN_UNARY_FALSE(...) ((void)0) -#define DOCTEST_CHECK_UNARY_FALSE(...) ((void)0) -#define DOCTEST_REQUIRE_UNARY_FALSE(...) ((void)0) - -#define DOCTEST_FAST_WARN_EQ(...) ((void)0) -#define DOCTEST_FAST_CHECK_EQ(...) ((void)0) -#define DOCTEST_FAST_REQUIRE_EQ(...) ((void)0) -#define DOCTEST_FAST_WARN_NE(...) ((void)0) -#define DOCTEST_FAST_CHECK_NE(...) ((void)0) -#define DOCTEST_FAST_REQUIRE_NE(...) ((void)0) -#define DOCTEST_FAST_WARN_GT(...) ((void)0) -#define DOCTEST_FAST_CHECK_GT(...) ((void)0) -#define DOCTEST_FAST_REQUIRE_GT(...) ((void)0) -#define DOCTEST_FAST_WARN_LT(...) ((void)0) -#define DOCTEST_FAST_CHECK_LT(...) ((void)0) -#define DOCTEST_FAST_REQUIRE_LT(...) ((void)0) -#define DOCTEST_FAST_WARN_GE(...) ((void)0) -#define DOCTEST_FAST_CHECK_GE(...) ((void)0) -#define DOCTEST_FAST_REQUIRE_GE(...) ((void)0) -#define DOCTEST_FAST_WARN_LE(...) ((void)0) -#define DOCTEST_FAST_CHECK_LE(...) ((void)0) -#define DOCTEST_FAST_REQUIRE_LE(...) ((void)0) - -#define DOCTEST_FAST_WARN_UNARY(...) ((void)0) -#define DOCTEST_FAST_CHECK_UNARY(...) ((void)0) -#define DOCTEST_FAST_REQUIRE_UNARY(...) ((void)0) -#define DOCTEST_FAST_WARN_UNARY_FALSE(...) ((void)0) -#define DOCTEST_FAST_CHECK_UNARY_FALSE(...) ((void)0) -#define DOCTEST_FAST_REQUIRE_UNARY_FALSE(...) ((void)0) - -#else // DOCTEST_CONFIG_WITH_VARIADIC_MACROS - -#define DOCTEST_WARN_EQ(lhs, rhs) ((void)0) -#define DOCTEST_CHECK_EQ(lhs, rhs) ((void)0) -#define DOCTEST_REQUIRE_EQ(lhs, rhs) ((void)0) -#define DOCTEST_WARN_NE(lhs, rhs) ((void)0) -#define DOCTEST_CHECK_NE(lhs, rhs) ((void)0) -#define DOCTEST_REQUIRE_NE(lhs, rhs) ((void)0) -#define DOCTEST_WARN_GT(lhs, rhs) ((void)0) -#define DOCTEST_CHECK_GT(lhs, rhs) ((void)0) -#define DOCTEST_REQUIRE_GT(lhs, rhs) ((void)0) -#define DOCTEST_WARN_LT(lhs, rhs) ((void)0) -#define DOCTEST_CHECK_LT(lhs, rhs) ((void)0) -#define DOCTEST_REQUIRE_LT(lhs, rhs) ((void)0) -#define DOCTEST_WARN_GE(lhs, rhs) ((void)0) -#define DOCTEST_CHECK_GE(lhs, rhs) ((void)0) -#define DOCTEST_REQUIRE_GE(lhs, rhs) ((void)0) -#define DOCTEST_WARN_LE(lhs, rhs) ((void)0) -#define DOCTEST_CHECK_LE(lhs, rhs) ((void)0) -#define DOCTEST_REQUIRE_LE(lhs, rhs) ((void)0) - -#define DOCTEST_WARN_UNARY(val) ((void)0) -#define DOCTEST_CHECK_UNARY(val) ((void)0) -#define DOCTEST_REQUIRE_UNARY(val) ((void)0) -#define DOCTEST_WARN_UNARY_FALSE(val) ((void)0) -#define DOCTEST_CHECK_UNARY_FALSE(val) ((void)0) -#define DOCTEST_REQUIRE_UNARY_FALSE(val) ((void)0) - -#define DOCTEST_FAST_WARN_EQ(lhs, rhs) ((void)0) -#define DOCTEST_FAST_CHECK_EQ(lhs, rhs) ((void)0) -#define DOCTEST_FAST_REQUIRE_EQ(lhs, rhs) ((void)0) -#define DOCTEST_FAST_WARN_NE(lhs, rhs) ((void)0) -#define DOCTEST_FAST_CHECK_NE(lhs, rhs) ((void)0) -#define DOCTEST_FAST_REQUIRE_NE(lhs, rhs) ((void)0) -#define DOCTEST_FAST_WARN_GT(lhs, rhs) ((void)0) -#define DOCTEST_FAST_CHECK_GT(lhs, rhs) ((void)0) -#define DOCTEST_FAST_REQUIRE_GT(lhs, rhs) ((void)0) -#define DOCTEST_FAST_WARN_LT(lhs, rhs) ((void)0) -#define DOCTEST_FAST_CHECK_LT(lhs, rhs) ((void)0) -#define DOCTEST_FAST_REQUIRE_LT(lhs, rhs) ((void)0) -#define DOCTEST_FAST_WARN_GE(lhs, rhs) ((void)0) -#define DOCTEST_FAST_CHECK_GE(lhs, rhs) ((void)0) -#define DOCTEST_FAST_REQUIRE_GE(lhs, rhs) ((void)0) -#define DOCTEST_FAST_WARN_LE(lhs, rhs) ((void)0) -#define DOCTEST_FAST_CHECK_LE(lhs, rhs) ((void)0) -#define DOCTEST_FAST_REQUIRE_LE(lhs, rhs) ((void)0) - -#define DOCTEST_FAST_WARN_UNARY(val) ((void)0) -#define DOCTEST_FAST_CHECK_UNARY(val) ((void)0) -#define DOCTEST_FAST_REQUIRE_UNARY(val) ((void)0) -#define DOCTEST_FAST_WARN_UNARY_FALSE(val) ((void)0) -#define DOCTEST_FAST_CHECK_UNARY_FALSE(val) ((void)0) -#define DOCTEST_FAST_REQUIRE_UNARY_FALSE(val) ((void)0) - -#endif // DOCTEST_CONFIG_WITH_VARIADIC_MACROS - -#endif // DOCTEST_CONFIG_DISABLE - -// BDD style macros -// clang-format off -#define DOCTEST_SCENARIO(name) TEST_CASE(" Scenario: " name) -#define DOCTEST_GIVEN(name) SUBCASE(" Given: " name) -#define DOCTEST_WHEN(name) SUBCASE(" When: " name) -#define DOCTEST_AND_WHEN(name) SUBCASE("And when: " name) -#define DOCTEST_THEN(name) SUBCASE(" Then: " name) -#define DOCTEST_AND_THEN(name) SUBCASE(" And: " name) -// clang-format on - -// == SHORT VERSIONS OF THE MACROS -#if !defined(DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES) - -#define TEST_CASE DOCTEST_TEST_CASE -#define TEST_CASE_FIXTURE DOCTEST_TEST_CASE_FIXTURE -#define TYPE_TO_STRING DOCTEST_TYPE_TO_STRING -#define TEST_CASE_TEMPLATE DOCTEST_TEST_CASE_TEMPLATE -#define TEST_CASE_TEMPLATE_DEFINE DOCTEST_TEST_CASE_TEMPLATE_DEFINE -#define TEST_CASE_TEMPLATE_INSTANTIATE DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE -#define SUBCASE DOCTEST_SUBCASE -#define TEST_SUITE DOCTEST_TEST_SUITE -#define TEST_SUITE_BEGIN DOCTEST_TEST_SUITE_BEGIN -#define TEST_SUITE_END DOCTEST_TEST_SUITE_END -#define REGISTER_EXCEPTION_TRANSLATOR DOCTEST_REGISTER_EXCEPTION_TRANSLATOR -#define INFO DOCTEST_INFO -#define CAPTURE DOCTEST_CAPTURE -#define ADD_MESSAGE_AT DOCTEST_ADD_MESSAGE_AT -#define ADD_FAIL_CHECK_AT DOCTEST_ADD_FAIL_CHECK_AT -#define ADD_FAIL_AT DOCTEST_ADD_FAIL_AT -#define MESSAGE DOCTEST_MESSAGE -#define FAIL_CHECK DOCTEST_FAIL_CHECK -#define FAIL DOCTEST_FAIL -#define TO_LVALUE DOCTEST_TO_LVALUE - -#define WARN DOCTEST_WARN -#define WARN_FALSE DOCTEST_WARN_FALSE -#define WARN_THROWS DOCTEST_WARN_THROWS -#define WARN_THROWS_AS DOCTEST_WARN_THROWS_AS -#define WARN_NOTHROW DOCTEST_WARN_NOTHROW -#define CHECK DOCTEST_CHECK -#define CHECK_FALSE DOCTEST_CHECK_FALSE -#define CHECK_THROWS DOCTEST_CHECK_THROWS -#define CHECK_THROWS_AS DOCTEST_CHECK_THROWS_AS -#define CHECK_NOTHROW DOCTEST_CHECK_NOTHROW -#define REQUIRE DOCTEST_REQUIRE -#define REQUIRE_FALSE DOCTEST_REQUIRE_FALSE -#define REQUIRE_THROWS DOCTEST_REQUIRE_THROWS -#define REQUIRE_THROWS_AS DOCTEST_REQUIRE_THROWS_AS -#define REQUIRE_NOTHROW DOCTEST_REQUIRE_NOTHROW - -#define WARN_MESSAGE DOCTEST_WARN_MESSAGE -#define WARN_FALSE_MESSAGE DOCTEST_WARN_FALSE_MESSAGE -#define WARN_THROWS_MESSAGE DOCTEST_WARN_THROWS_MESSAGE -#define WARN_THROWS_AS_MESSAGE DOCTEST_WARN_THROWS_AS_MESSAGE -#define WARN_NOTHROW_MESSAGE DOCTEST_WARN_NOTHROW_MESSAGE -#define CHECK_MESSAGE DOCTEST_CHECK_MESSAGE -#define CHECK_FALSE_MESSAGE DOCTEST_CHECK_FALSE_MESSAGE -#define CHECK_THROWS_MESSAGE DOCTEST_CHECK_THROWS_MESSAGE -#define CHECK_THROWS_AS_MESSAGE DOCTEST_CHECK_THROWS_AS_MESSAGE -#define CHECK_NOTHROW_MESSAGE DOCTEST_CHECK_NOTHROW_MESSAGE -#define REQUIRE_MESSAGE DOCTEST_REQUIRE_MESSAGE -#define REQUIRE_FALSE_MESSAGE DOCTEST_REQUIRE_FALSE_MESSAGE -#define REQUIRE_THROWS_MESSAGE DOCTEST_REQUIRE_THROWS_MESSAGE -#define REQUIRE_THROWS_AS_MESSAGE DOCTEST_REQUIRE_THROWS_AS_MESSAGE -#define REQUIRE_NOTHROW_MESSAGE DOCTEST_REQUIRE_NOTHROW_MESSAGE - -#define SCENARIO DOCTEST_SCENARIO -#define GIVEN DOCTEST_GIVEN -#define WHEN DOCTEST_WHEN -#define AND_WHEN DOCTEST_AND_WHEN -#define THEN DOCTEST_THEN -#define AND_THEN DOCTEST_AND_THEN - -#define WARN_EQ DOCTEST_WARN_EQ -#define CHECK_EQ DOCTEST_CHECK_EQ -#define REQUIRE_EQ DOCTEST_REQUIRE_EQ -#define WARN_NE DOCTEST_WARN_NE -#define CHECK_NE DOCTEST_CHECK_NE -#define REQUIRE_NE DOCTEST_REQUIRE_NE -#define WARN_GT DOCTEST_WARN_GT -#define CHECK_GT DOCTEST_CHECK_GT -#define REQUIRE_GT DOCTEST_REQUIRE_GT -#define WARN_LT DOCTEST_WARN_LT -#define CHECK_LT DOCTEST_CHECK_LT -#define REQUIRE_LT DOCTEST_REQUIRE_LT -#define WARN_GE DOCTEST_WARN_GE -#define CHECK_GE DOCTEST_CHECK_GE -#define REQUIRE_GE DOCTEST_REQUIRE_GE -#define WARN_LE DOCTEST_WARN_LE -#define CHECK_LE DOCTEST_CHECK_LE -#define REQUIRE_LE DOCTEST_REQUIRE_LE -#define WARN_UNARY DOCTEST_WARN_UNARY -#define CHECK_UNARY DOCTEST_CHECK_UNARY -#define REQUIRE_UNARY DOCTEST_REQUIRE_UNARY -#define WARN_UNARY_FALSE DOCTEST_WARN_UNARY_FALSE -#define CHECK_UNARY_FALSE DOCTEST_CHECK_UNARY_FALSE -#define REQUIRE_UNARY_FALSE DOCTEST_REQUIRE_UNARY_FALSE - -#define FAST_WARN_EQ DOCTEST_FAST_WARN_EQ -#define FAST_CHECK_EQ DOCTEST_FAST_CHECK_EQ -#define FAST_REQUIRE_EQ DOCTEST_FAST_REQUIRE_EQ -#define FAST_WARN_NE DOCTEST_FAST_WARN_NE -#define FAST_CHECK_NE DOCTEST_FAST_CHECK_NE -#define FAST_REQUIRE_NE DOCTEST_FAST_REQUIRE_NE -#define FAST_WARN_GT DOCTEST_FAST_WARN_GT -#define FAST_CHECK_GT DOCTEST_FAST_CHECK_GT -#define FAST_REQUIRE_GT DOCTEST_FAST_REQUIRE_GT -#define FAST_WARN_LT DOCTEST_FAST_WARN_LT -#define FAST_CHECK_LT DOCTEST_FAST_CHECK_LT -#define FAST_REQUIRE_LT DOCTEST_FAST_REQUIRE_LT -#define FAST_WARN_GE DOCTEST_FAST_WARN_GE -#define FAST_CHECK_GE DOCTEST_FAST_CHECK_GE -#define FAST_REQUIRE_GE DOCTEST_FAST_REQUIRE_GE -#define FAST_WARN_LE DOCTEST_FAST_WARN_LE -#define FAST_CHECK_LE DOCTEST_FAST_CHECK_LE -#define FAST_REQUIRE_LE DOCTEST_FAST_REQUIRE_LE -#define FAST_WARN_UNARY DOCTEST_FAST_WARN_UNARY -#define FAST_CHECK_UNARY DOCTEST_FAST_CHECK_UNARY -#define FAST_REQUIRE_UNARY DOCTEST_FAST_REQUIRE_UNARY -#define FAST_WARN_UNARY_FALSE DOCTEST_FAST_WARN_UNARY_FALSE -#define FAST_CHECK_UNARY_FALSE DOCTEST_FAST_CHECK_UNARY_FALSE -#define FAST_REQUIRE_UNARY_FALSE DOCTEST_FAST_REQUIRE_UNARY_FALSE - -#endif // DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES - -// this is here to clear the 'current test suite' for the current translation unit - at the top -DOCTEST_TEST_SUITE_END(); - -// add stringification for primitive/fundamental types -namespace doctest -{ -namespace detail -{ - DOCTEST_TYPE_TO_STRING_IMPL(bool) - DOCTEST_TYPE_TO_STRING_IMPL(float) - DOCTEST_TYPE_TO_STRING_IMPL(double) - DOCTEST_TYPE_TO_STRING_IMPL(long double) - DOCTEST_TYPE_TO_STRING_IMPL(char) - DOCTEST_TYPE_TO_STRING_IMPL(signed char) - DOCTEST_TYPE_TO_STRING_IMPL(unsigned char) - DOCTEST_TYPE_TO_STRING_IMPL(wchar_t) - DOCTEST_TYPE_TO_STRING_IMPL(short int) - DOCTEST_TYPE_TO_STRING_IMPL(unsigned short int) - DOCTEST_TYPE_TO_STRING_IMPL(int) - DOCTEST_TYPE_TO_STRING_IMPL(unsigned int) - DOCTEST_TYPE_TO_STRING_IMPL(long int) - DOCTEST_TYPE_TO_STRING_IMPL(unsigned long int) -#ifdef DOCTEST_CONFIG_WITH_LONG_LONG - DOCTEST_TYPE_TO_STRING_IMPL(long long int) - DOCTEST_TYPE_TO_STRING_IMPL(unsigned long long int) -#endif // DOCTEST_CONFIG_WITH_LONG_LONG -} // namespace detail -} // namespace doctest - -#endif // DOCTEST_LIBRARY_INCLUDED - -#if defined(__clang__) -#pragma clang diagnostic pop -#endif // __clang__ - -#if defined(__GNUC__) && !defined(__clang__) -#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 6) -#pragma GCC diagnostic pop -#endif // > gcc 4.6 -#endif // __GNUC__ - -#ifdef _MSC_VER -#pragma warning(pop) -#endif // _MSC_VER - -#ifndef DOCTEST_SINGLE_HEADER -#define DOCTEST_SINGLE_HEADER -#endif // DOCTEST_SINGLE_HEADER - -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunknown-pragmas" -#pragma clang diagnostic ignored "-Wpadded" -#pragma clang diagnostic ignored "-Wglobal-constructors" -#pragma clang diagnostic ignored "-Wexit-time-destructors" -#pragma clang diagnostic ignored "-Wmissing-prototypes" -#pragma clang diagnostic ignored "-Wsign-conversion" -#pragma clang diagnostic ignored "-Wshorten-64-to-32" -#pragma clang diagnostic ignored "-Wmissing-variable-declarations" -#pragma clang diagnostic ignored "-Wswitch" -#pragma clang diagnostic ignored "-Wswitch-enum" -#pragma clang diagnostic ignored "-Wcovered-switch-default" -#pragma clang diagnostic ignored "-Wmissing-noreturn" -#pragma clang diagnostic ignored "-Wunused-local-typedef" -#pragma clang diagnostic ignored "-Wdisabled-macro-expansion" -#pragma clang diagnostic ignored "-Wmissing-braces" -#pragma clang diagnostic ignored "-Wmissing-field-initializers" -#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" -#pragma clang diagnostic ignored "-Wc++11-long-long" -#endif // __clang__ - -#if defined(__GNUC__) && !defined(__clang__) -#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 6) -#pragma GCC diagnostic push -#endif // > gcc 4.6 -#pragma GCC diagnostic ignored "-Wunknown-pragmas" -#pragma GCC diagnostic ignored "-Wconversion" -#pragma GCC diagnostic ignored "-Weffc++" -#pragma GCC diagnostic ignored "-Wsign-conversion" -#pragma GCC diagnostic ignored "-Wstrict-overflow" -#pragma GCC diagnostic ignored "-Wmissing-field-initializers" -#pragma GCC diagnostic ignored "-Wmissing-braces" -#pragma GCC diagnostic ignored "-Wmissing-declarations" -#pragma GCC diagnostic ignored "-Winline" -#pragma GCC diagnostic ignored "-Wswitch" -#pragma GCC diagnostic ignored "-Wswitch-enum" -#pragma GCC diagnostic ignored "-Wswitch-default" -#pragma GCC diagnostic ignored "-Wunsafe-loop-optimizations" -#pragma GCC diagnostic ignored "-Wlong-long" -#pragma GCC diagnostic ignored "-Wold-style-cast" -#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 6) -#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant" -#endif // > gcc 4.6 -#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 7) -#pragma GCC diagnostic ignored "-Wunused-local-typedefs" -#endif // > gcc 4.7 -#if __GNUC__ > 5 || (__GNUC__ == 5 && __GNUC_MINOR__ > 3) -#pragma GCC diagnostic ignored "-Wuseless-cast" -#endif // > gcc 5.3 -#endif // __GNUC__ - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4996) // The compiler encountered a deprecated declaration -#pragma warning(disable : 4267) // 'var' : conversion from 'size_t' to 'type', possible loss of data -#pragma warning(disable : 4706) // assignment within conditional expression -#pragma warning(disable : 4512) // 'class' : assignment operator could not be generated -#pragma warning(disable : 4127) // conditional expression is constant -#pragma warning(disable : 4530) // C++ exception handler used, but unwind semantics are not enabled -#pragma warning(disable : 4577) // 'noexcept' used with no exception handling mode specified -#endif // _MSC_VER - -#if defined(DOCTEST_CONFIG_IMPLEMENT) || !defined(DOCTEST_SINGLE_HEADER) -#ifndef DOCTEST_LIBRARY_IMPLEMENTATION -#define DOCTEST_LIBRARY_IMPLEMENTATION - -#ifndef DOCTEST_SINGLE_HEADER -#include "doctest_fwd.h" -#endif // DOCTEST_SINGLE_HEADER - -#if defined(__clang__) && defined(DOCTEST_NO_CPP11_COMPAT) -#pragma clang diagnostic ignored "-Wc++98-compat" -#pragma clang diagnostic ignored "-Wc++98-compat-pedantic" -#endif // __clang__ && DOCTEST_NO_CPP11_COMPAT - -// snprintf() not in the C++98 standard -#ifdef _MSC_VER -#define DOCTEST_SNPRINTF _snprintf -#else -#define DOCTEST_SNPRINTF std::snprintf -#endif - -#define DOCTEST_LOG_START() \ - do { \ - if(!contextState->hasLoggedCurrentTestStart) { \ - doctest::detail::logTestStart(*contextState->currentTest); \ - contextState->hasLoggedCurrentTestStart = true; \ - } \ - } while(false) - -// required includes - will go only in one translation unit! -#include -#include -// borland (Embarcadero) compiler requires math.h and not cmath - https://github.com/onqtam/doctest/pull/37 -#ifdef __BORLANDC__ -#include -#endif // __BORLANDC__ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifndef _MSC_VER -#include -#endif // _MSC_VER - -namespace doctest -{ -namespace detail -{ - // lowers ascii letters - char tolower(const char c) { return (c >= 'A' && c <= 'Z') ? static_cast(c + 32) : c; } - - template - T my_max(const T& lhs, const T& rhs) { - return lhs > rhs ? lhs : rhs; - } - - // case insensitive strcmp - int stricmp(char const* a, char const* b) { - for(;; a++, b++) { - int d = tolower(*a) - tolower(*b); - if(d != 0 || !*a) - return d; - } - } - - void my_memcpy(void* dest, const void* src, unsigned num) { - const char* csrc = static_cast(src); - char* cdest = static_cast(dest); - for(unsigned i = 0; i < num; ++i) - cdest[i] = csrc[i]; - } - - // not using std::strlen() because of valgrind errors when optimizations are turned on - // 'Invalid read of size 4' when the test suite len (with '\0') is not a multiple of 4 - // for details see http://stackoverflow.com/questions/35671155 - unsigned my_strlen(const char* in) { - const char* temp = in; - while(temp && *temp) - ++temp; - return unsigned(temp - in); - } - - template - String fpToString(T value, int precision) { - std::ostringstream oss; - oss << std::setprecision(precision) << std::fixed << value; - std::string d = oss.str(); - size_t i = d.find_last_not_of('0'); - if(i != std::string::npos && i != d.size() - 1) { - if(d[i] == '.') - i++; - d = d.substr(0, i + 1); - } - return d.c_str(); - } - - struct Endianness - { - enum Arch - { - Big, - Little - }; - - static Arch which() { - union _ - { - int asInt; - char asChar[sizeof(int)]; - } u; - - u.asInt = 1; // NOLINT - return (u.asChar[sizeof(int) - 1] == 1) ? Big : Little; // NOLINT - } - }; - - String rawMemoryToString(const void* object, unsigned size) { - // Reverse order for little endian architectures - int i = 0, end = static_cast(size), inc = 1; - if(Endianness::which() == Endianness::Little) { - i = end - 1; - end = inc = -1; - } - - unsigned char const* bytes = static_cast(object); - std::ostringstream os; - os << "0x" << std::setfill('0') << std::hex; - for(; i != end; i += inc) - os << std::setw(2) << static_cast(bytes[i]); - return os.str().c_str(); - } - - std::ostream* createStream() { return new std::ostringstream(); } - String getStreamResult(std::ostream* in) { - return static_cast(in)->str().c_str(); // NOLINT - } - void freeStream(std::ostream* in) { delete in; } - -#ifndef DOCTEST_CONFIG_DISABLE - - // this holds both parameters for the command line and runtime data for tests - struct ContextState : TestAccessibleContextState //!OCLINT too many fields - { - // == parameters from the command line - - std::vector > filters; - - String order_by; // how tests should be ordered - unsigned rand_seed; // the seed for rand ordering - - unsigned first; // the first (matching) test to be executed - unsigned last; // the last (matching) test to be executed - - int abort_after; // stop tests after this many failed assertions - int subcase_filter_levels; // apply the subcase filters for the first N levels - bool case_sensitive; // if filtering should be case sensitive - bool exit; // if the program should be exited after the tests are ran/whatever - bool duration; // print the time duration of each test case - bool no_exitcode; // if the framework should return 0 as the exitcode - bool no_run; // to not run the tests at all (can be done with an "*" exclude) - bool no_version; // to not print the version of the framework - bool no_colors; // if output to the console should be colorized - bool force_colors; // forces the use of colors even when a tty cannot be detected - bool no_breaks; // to not break into the debugger - bool no_skip; // don't skip test cases which are marked to be skipped - bool no_path_in_filenames; // if the path to files should be removed from the output - bool no_line_numbers; // if source code line numbers should be omitted from the output - bool no_skipped_summary; // don't print "skipped" in the summary !!! UNDOCUMENTED !!! - - bool help; // to print the help - bool version; // to print the version - bool count; // if only the count of matching tests is to be retreived - bool list_test_cases; // to list all tests matching the filters - bool list_test_suites; // to list all suites matching the filters - - // == data for the tests being ran - - unsigned numTestsPassingFilters; - unsigned numTestSuitesPassingFilters; - unsigned numFailed; - const TestCase* currentTest; - bool hasLoggedCurrentTestStart; - int numAssertionsForCurrentTestcase; - int numAssertions; - int numFailedAssertionsForCurrentTestcase; - int numFailedAssertions; - bool hasCurrentTestFailed; - - std::vector contexts; // for logging with INFO() and friends - std::vector exceptionalContexts; // logging from INFO() due to an exception - - // stuff for subcases - std::set subcasesPassed; - std::set subcasesEnteredLevels; - std::vector subcasesStack; - int subcasesCurrentLevel; - bool subcasesHasSkipped; - - void resetRunData() { - numTestsPassingFilters = 0; - numTestSuitesPassingFilters = 0; - numFailed = 0; - numAssertions = 0; - numFailedAssertions = 0; - numFailedAssertionsForCurrentTestcase = 0; - } - - // cppcheck-suppress uninitMemberVar - ContextState() - : filters(8) // 8 different filters total - { - resetRunData(); - } - }; - - ContextState* contextState = 0; -#endif // DOCTEST_CONFIG_DISABLE -} // namespace detail - -void String::copy(const String& other) { - if(other.isOnStack()) { - detail::my_memcpy(buf, other.buf, len); - } else { - setOnHeap(); - data.size = other.data.size; - data.capacity = data.size + 1; - data.ptr = new char[data.capacity]; - detail::my_memcpy(data.ptr, other.data.ptr, data.size + 1); - } -} - -String::String(const char* in) { - unsigned in_len = detail::my_strlen(in); - if(in_len <= last) { - detail::my_memcpy(buf, in, in_len + 1); - setLast(last - in_len); - } else { - setOnHeap(); - data.size = in_len; - data.capacity = data.size + 1; - data.ptr = new char[data.capacity]; - detail::my_memcpy(data.ptr, in, in_len + 1); - } -} - -String& String::operator+=(const String& other) { - unsigned my_old_size = size(); - unsigned other_size = other.size(); - unsigned total_size = my_old_size + other_size; - if(isOnStack()) { - if(total_size < len) { - // append to the current stack space - detail::my_memcpy(buf + my_old_size, other.c_str(), other_size + 1); - setLast(last - total_size); - } else { - // alloc new chunk - char* temp = new char[total_size + 1]; - // copy current data to new location before writing in the union - detail::my_memcpy(temp, buf, my_old_size); // skip the +1 ('\0') for speed - // update data in union - setOnHeap(); - data.size = total_size; - data.capacity = data.size + 1; - data.ptr = temp; - // transfer the rest of the data - detail::my_memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); - } - } else { - if(data.capacity > total_size) { - // append to the current heap block - data.size = total_size; - detail::my_memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); - } else { - // resize - data.capacity *= 2; - if(data.capacity <= total_size) - data.capacity = total_size + 1; - // alloc new chunk - char* temp = new char[data.capacity]; - // copy current data to new location before releasing it - detail::my_memcpy(temp, data.ptr, my_old_size); // skip the +1 ('\0') for speed - // release old chunk - delete[] data.ptr; - // update the rest of the union members - data.size = total_size; - data.ptr = temp; - // transfer the rest of the data - detail::my_memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); - } - } - - return *this; -} - -#ifdef DOCTEST_CONFIG_WITH_RVALUE_REFERENCES -String::String(String&& other) { - detail::my_memcpy(buf, other.buf, len); - other.buf[0] = '\0'; - other.setLast(); -} - -String& String::operator=(String&& other) { - if(!isOnStack()) - delete[] data.ptr; - detail::my_memcpy(buf, other.buf, len); - other.buf[0] = '\0'; - other.setLast(); - return *this; -} -#endif // DOCTEST_CONFIG_WITH_RVALUE_REFERENCES - -int String::compare(const char* other, bool no_case) const { - if(no_case) - return detail::stricmp(c_str(), other); - return std::strcmp(c_str(), other); -} - -int String::compare(const String& other, bool no_case) const { - return compare(other.c_str(), no_case); -} - -std::ostream& operator<<(std::ostream& stream, const String& in) { - stream << in.c_str(); - return stream; -} - -Approx::Approx(double value) - : m_epsilon(static_cast(std::numeric_limits::epsilon()) * 100) - , m_scale(1.0) - , m_value(value) {} - -bool operator==(double lhs, Approx const& rhs) { - // Thanks to Richard Harris for his help refining this formula - return std::fabs(lhs - rhs.m_value) < - rhs.m_epsilon * (rhs.m_scale + detail::my_max(std::fabs(lhs), std::fabs(rhs.m_value))); -} - -String Approx::toString() const { return String("Approx( ") + doctest::toString(m_value) + " )"; } - -#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING -String toString(char* in) { return toString(static_cast(in)); } -String toString(const char* in) { return String("\"") + (in ? in : "{null string}") + "\""; } -#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING -String toString(bool in) { return in ? "true" : "false"; } -String toString(float in) { return detail::fpToString(in, 5) + "f"; } -String toString(double in) { return detail::fpToString(in, 10); } -String toString(double long in) { return detail::fpToString(in, 15); } - -String toString(char in) { - char buf[64]; - std::sprintf(buf, "%d", in); - return buf; -} - -String toString(char signed in) { - char buf[64]; - std::sprintf(buf, "%d", in); - return buf; -} - -String toString(char unsigned in) { - char buf[64]; - std::sprintf(buf, "%ud", in); - return buf; -} - -String toString(int short in) { - char buf[64]; - std::sprintf(buf, "%d", in); - return buf; -} - -String toString(int short unsigned in) { - char buf[64]; - std::sprintf(buf, "%u", in); - return buf; -} - -String toString(int in) { - char buf[64]; - std::sprintf(buf, "%d", in); - return buf; -} - -String toString(int unsigned in) { - char buf[64]; - std::sprintf(buf, "%u", in); - return buf; -} - -String toString(int long in) { - char buf[64]; - std::sprintf(buf, "%ld", in); - return buf; -} - -String toString(int long unsigned in) { - char buf[64]; - std::sprintf(buf, "%lu", in); - return buf; -} - -#ifdef DOCTEST_CONFIG_WITH_LONG_LONG -String toString(int long long in) { - char buf[64]; - std::sprintf(buf, "%lld", in); - return buf; -} -String toString(int long long unsigned in) { - char buf[64]; - std::sprintf(buf, "%llu", in); - return buf; -} -#endif // DOCTEST_CONFIG_WITH_LONG_LONG - -#ifdef DOCTEST_CONFIG_WITH_NULLPTR -String toString(std::nullptr_t) { return "nullptr"; } -#endif // DOCTEST_CONFIG_WITH_NULLPTR - -} // namespace doctest - -#ifdef DOCTEST_CONFIG_DISABLE -namespace doctest -{ -bool isRunningInTest() { return false; } -Context::Context(int, const char* const*) {} -Context::~Context() {} -void Context::applyCommandLine(int, const char* const*) {} -void Context::addFilter(const char*, const char*) {} -void Context::clearFilters() {} -void Context::setOption(const char*, int) {} -void Context::setOption(const char*, const char*) {} -bool Context::shouldExit() { return false; } -int Context::run() { return 0; } -} // namespace doctest -#else // DOCTEST_CONFIG_DISABLE - -#if !defined(DOCTEST_CONFIG_COLORS_NONE) -#if !defined(DOCTEST_CONFIG_COLORS_WINDOWS) && !defined(DOCTEST_CONFIG_COLORS_ANSI) -#ifdef DOCTEST_PLATFORM_WINDOWS -#define DOCTEST_CONFIG_COLORS_WINDOWS -#else // linux -#define DOCTEST_CONFIG_COLORS_ANSI -#endif // platform -#endif // DOCTEST_CONFIG_COLORS_WINDOWS && DOCTEST_CONFIG_COLORS_ANSI -#endif // DOCTEST_CONFIG_COLORS_NONE - -#define DOCTEST_PRINTF_COLORED(buffer, color) \ - do { \ - doctest::detail::Color col(color); \ - std::printf("%s", buffer); \ - } while((void)0, 0) - -// the buffer size used for snprintf() calls -#if !defined(DOCTEST_SNPRINTF_BUFFER_LENGTH) -#define DOCTEST_SNPRINTF_BUFFER_LENGTH 1024 -#endif // DOCTEST_SNPRINTF_BUFFER_LENGTH - -#if defined(_MSC_VER) || defined(__MINGW32__) -#if defined(_MSC_VER) && _MSC_VER >= 1700 -#define DOCTEST_WINDOWS_SAL_IN_OPT _In_opt_ -#else // _MSC_VER -#define DOCTEST_WINDOWS_SAL_IN_OPT -#endif // _MSC_VER -extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA( - DOCTEST_WINDOWS_SAL_IN_OPT const char*); -extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); -#endif // _MSC_VER || __MINGW32__ - -#ifdef DOCTEST_CONFIG_COLORS_ANSI -#include -#endif // DOCTEST_CONFIG_COLORS_ANSI - -#ifdef _WIN32 - -// defines for a leaner windows.h -#ifndef WIN32_MEAN_AND_LEAN -#define WIN32_MEAN_AND_LEAN -#endif // WIN32_MEAN_AND_LEAN -#ifndef VC_EXTRA_LEAN -#define VC_EXTRA_LEAN -#endif // VC_EXTRA_LEAN -#ifndef NOMINMAX -#define NOMINMAX -#endif // NOMINMAX - -// not sure what AfxWin.h is for - here I do what Catch does -#ifdef __AFXDLL -#include -#else -#include -#endif -#include - -#else // _WIN32 - -#include - -#endif // _WIN32 - -namespace doctest_detail_test_suite_ns -{ -// holds the current test suite -doctest::detail::TestSuite& getCurrentTestSuite() { - static doctest::detail::TestSuite data; - return data; -} -} // namespace doctest_detail_test_suite_ns - -namespace doctest -{ -namespace detail -{ - TestCase::TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite, - const char* type, int template_id) - : m_test(test) - , m_name(0) - , m_type(type) - , m_test_suite(test_suite.m_test_suite) - , m_description(test_suite.m_description) - , m_skip(test_suite.m_skip) - , m_may_fail(test_suite.m_may_fail) - , m_should_fail(test_suite.m_should_fail) - , m_expected_failures(test_suite.m_expected_failures) - , m_timeout(test_suite.m_timeout) - , m_file(file) - , m_line(line) - , m_template_id(template_id) {} - - TestCase& TestCase::operator*(const char* in) { - m_name = in; - // make a new name with an appended type for templated test case - if(m_template_id != -1) { - m_full_name = String(m_name) + m_type; - // redirect the name to point to the newly constructed full name - m_name = m_full_name.c_str(); - } - return *this; - } - - TestCase& TestCase::operator=(const TestCase& other) { - m_test = other.m_test; - m_full_name = other.m_full_name; - m_name = other.m_name; - m_type = other.m_type; - m_test_suite = other.m_test_suite; - m_description = other.m_description; - m_skip = other.m_skip; - m_may_fail = other.m_may_fail; - m_should_fail = other.m_should_fail; - m_expected_failures = other.m_expected_failures; - m_timeout = other.m_timeout; - m_file = other.m_file; - m_line = other.m_line; - m_template_id = other.m_template_id; - - if(m_template_id != -1) - m_name = m_full_name.c_str(); - return *this; - } - - bool TestCase::operator<(const TestCase& other) const { - if(m_line != other.m_line) - return m_line < other.m_line; - int file_cmp = std::strcmp(m_file, other.m_file); - if(file_cmp != 0) - return file_cmp < 0; - return m_template_id < other.m_template_id; - } - - const char* getAssertString(assertType::Enum val) { - switch(val) { //!OCLINT missing default in switch statements - // clang-format off - case assertType::DT_WARN : return "WARN"; - case assertType::DT_CHECK : return "CHECK"; - case assertType::DT_REQUIRE : return "REQUIRE"; - - case assertType::DT_WARN_FALSE : return "WARN_FALSE"; - case assertType::DT_CHECK_FALSE : return "CHECK_FALSE"; - case assertType::DT_REQUIRE_FALSE : return "REQUIRE_FALSE"; - - case assertType::DT_WARN_THROWS : return "WARN_THROWS"; - case assertType::DT_CHECK_THROWS : return "CHECK_THROWS"; - case assertType::DT_REQUIRE_THROWS : return "REQUIRE_THROWS"; - - case assertType::DT_WARN_THROWS_AS : return "WARN_THROWS_AS"; - case assertType::DT_CHECK_THROWS_AS : return "CHECK_THROWS_AS"; - case assertType::DT_REQUIRE_THROWS_AS : return "REQUIRE_THROWS_AS"; - - case assertType::DT_WARN_NOTHROW : return "WARN_NOTHROW"; - case assertType::DT_CHECK_NOTHROW : return "CHECK_NOTHROW"; - case assertType::DT_REQUIRE_NOTHROW : return "REQUIRE_NOTHROW"; - - case assertType::DT_WARN_EQ : return "WARN_EQ"; - case assertType::DT_CHECK_EQ : return "CHECK_EQ"; - case assertType::DT_REQUIRE_EQ : return "REQUIRE_EQ"; - case assertType::DT_WARN_NE : return "WARN_NE"; - case assertType::DT_CHECK_NE : return "CHECK_NE"; - case assertType::DT_REQUIRE_NE : return "REQUIRE_NE"; - case assertType::DT_WARN_GT : return "WARN_GT"; - case assertType::DT_CHECK_GT : return "CHECK_GT"; - case assertType::DT_REQUIRE_GT : return "REQUIRE_GT"; - case assertType::DT_WARN_LT : return "WARN_LT"; - case assertType::DT_CHECK_LT : return "CHECK_LT"; - case assertType::DT_REQUIRE_LT : return "REQUIRE_LT"; - case assertType::DT_WARN_GE : return "WARN_GE"; - case assertType::DT_CHECK_GE : return "CHECK_GE"; - case assertType::DT_REQUIRE_GE : return "REQUIRE_GE"; - case assertType::DT_WARN_LE : return "WARN_LE"; - case assertType::DT_CHECK_LE : return "CHECK_LE"; - case assertType::DT_REQUIRE_LE : return "REQUIRE_LE"; - - case assertType::DT_WARN_UNARY : return "WARN_UNARY"; - case assertType::DT_CHECK_UNARY : return "CHECK_UNARY"; - case assertType::DT_REQUIRE_UNARY : return "REQUIRE_UNARY"; - case assertType::DT_WARN_UNARY_FALSE : return "WARN_UNARY_FALSE"; - case assertType::DT_CHECK_UNARY_FALSE : return "CHECK_UNARY_FALSE"; - case assertType::DT_REQUIRE_UNARY_FALSE : return "REQUIRE_UNARY_FALSE"; - - case assertType::DT_FAST_WARN_EQ : return "FAST_WARN_EQ"; - case assertType::DT_FAST_CHECK_EQ : return "FAST_CHECK_EQ"; - case assertType::DT_FAST_REQUIRE_EQ : return "FAST_REQUIRE_EQ"; - case assertType::DT_FAST_WARN_NE : return "FAST_WARN_NE"; - case assertType::DT_FAST_CHECK_NE : return "FAST_CHECK_NE"; - case assertType::DT_FAST_REQUIRE_NE : return "FAST_REQUIRE_NE"; - case assertType::DT_FAST_WARN_GT : return "FAST_WARN_GT"; - case assertType::DT_FAST_CHECK_GT : return "FAST_CHECK_GT"; - case assertType::DT_FAST_REQUIRE_GT : return "FAST_REQUIRE_GT"; - case assertType::DT_FAST_WARN_LT : return "FAST_WARN_LT"; - case assertType::DT_FAST_CHECK_LT : return "FAST_CHECK_LT"; - case assertType::DT_FAST_REQUIRE_LT : return "FAST_REQUIRE_LT"; - case assertType::DT_FAST_WARN_GE : return "FAST_WARN_GE"; - case assertType::DT_FAST_CHECK_GE : return "FAST_CHECK_GE"; - case assertType::DT_FAST_REQUIRE_GE : return "FAST_REQUIRE_GE"; - case assertType::DT_FAST_WARN_LE : return "FAST_WARN_LE"; - case assertType::DT_FAST_CHECK_LE : return "FAST_CHECK_LE"; - case assertType::DT_FAST_REQUIRE_LE : return "FAST_REQUIRE_LE"; - - case assertType::DT_FAST_WARN_UNARY : return "FAST_WARN_UNARY"; - case assertType::DT_FAST_CHECK_UNARY : return "FAST_CHECK_UNARY"; - case assertType::DT_FAST_REQUIRE_UNARY : return "FAST_REQUIRE_UNARY"; - case assertType::DT_FAST_WARN_UNARY_FALSE : return "FAST_WARN_UNARY_FALSE"; - case assertType::DT_FAST_CHECK_UNARY_FALSE : return "FAST_CHECK_UNARY_FALSE"; - case assertType::DT_FAST_REQUIRE_UNARY_FALSE: return "FAST_REQUIRE_UNARY_FALSE"; - // clang-format on - } - return ""; - } - - bool checkIfShouldThrow(assertType::Enum assert_type) { - if(assert_type & assertType::is_require) //!OCLINT bitwise operator in conditional - return true; - - if((assert_type & assertType::is_check) //!OCLINT bitwise operator in conditional - && contextState->abort_after > 0 && - contextState->numFailedAssertions >= contextState->abort_after) - return true; - - return false; - } - void fastAssertThrowIfFlagSet(int flags) { - if(flags & assertAction::shouldthrow) //!OCLINT bitwise operator in conditional - throwException(); - } - void throwException() { -#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS - throw TestFailureException(); -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS - } - - // matching of a string against a wildcard mask (case sensitivity configurable) taken from - // http://www.emoticode.net/c/simple-wildcard-string-compare-globbing-function.html - int wildcmp(const char* str, const char* wild, bool caseSensitive) { - const char* cp = 0; - const char* mp = 0; - - // rolled my own tolower() to not include more headers - while((*str) && (*wild != '*')) { - if((caseSensitive ? (*wild != *str) : (tolower(*wild) != tolower(*str))) && - (*wild != '?')) { - return 0; - } - wild++; - str++; - } - - while(*str) { - if(*wild == '*') { - if(!*++wild) { - return 1; - } - mp = wild; - cp = str + 1; - } else if((caseSensitive ? (*wild == *str) : (tolower(*wild) == tolower(*str))) || - (*wild == '?')) { - wild++; - str++; - } else { - wild = mp; //!OCLINT parameter reassignment - str = cp++; //!OCLINT parameter reassignment - } - } - - while(*wild == '*') { - wild++; - } - return !*wild; - } - - //// C string hash function (djb2) - taken from http://www.cse.yorku.ca/~oz/hash.html - //unsigned hashStr(unsigned const char* str) { - // unsigned long hash = 5381; - // char c; - // while((c = *str++)) - // hash = ((hash << 5) + hash) + c; // hash * 33 + c - // return hash; - //} - - // checks if the name matches any of the filters (and can be configured what to do when empty) - bool matchesAny(const char* name, const std::vector& filters, int matchEmpty, - bool caseSensitive) { - if(filters.empty() && matchEmpty) - return true; - for(unsigned i = 0; i < filters.size(); ++i) - if(wildcmp(name, filters[i].c_str(), caseSensitive)) - return true; - return false; - } - -#ifdef _WIN32 - - typedef unsigned long long UInt64; - - UInt64 getCurrentTicks() { - static UInt64 hz = 0, hzo = 0; - if(!hz) { - QueryPerformanceFrequency(reinterpret_cast(&hz)); - QueryPerformanceCounter(reinterpret_cast(&hzo)); - } - UInt64 t; - QueryPerformanceCounter(reinterpret_cast(&t)); - return ((t - hzo) * 1000000) / hz; - } -#else // _WIN32 - - typedef uint64_t UInt64; - - UInt64 getCurrentTicks() { - timeval t; - gettimeofday(&t, 0); - return static_cast(t.tv_sec) * 1000000 + static_cast(t.tv_usec); - } -#endif // _WIN32 - - class Timer - { - public: - Timer() - : m_ticks(0) {} - void start() { m_ticks = getCurrentTicks(); } - unsigned int getElapsedMicroseconds() const { - return static_cast(getCurrentTicks() - m_ticks); - } - unsigned int getElapsedMilliseconds() const { - return static_cast(getElapsedMicroseconds() / 1000); - } - double getElapsedSeconds() const { return getElapsedMicroseconds() / 1000000.0; } - - private: - UInt64 m_ticks; - }; - - TestAccessibleContextState* getTestsContextState() { return contextState; } - - bool SubcaseSignature::operator<(const SubcaseSignature& other) const { - if(m_line != other.m_line) - return m_line < other.m_line; - if(std::strcmp(m_file, other.m_file) != 0) - return std::strcmp(m_file, other.m_file) < 0; - return std::strcmp(m_name, other.m_name) < 0; - } - - Subcase::Subcase(const char* name, const char* file, int line) - : m_signature(name, file, line) - , m_entered(false) { - ContextState* s = contextState; - - // if we have already completed it - if(s->subcasesPassed.count(m_signature) != 0) - return; - - // check subcase filters - if(s->subcasesCurrentLevel < s->subcase_filter_levels) { - if(!matchesAny(m_signature.m_name, s->filters[6], 1, s->case_sensitive)) - return; - if(matchesAny(m_signature.m_name, s->filters[7], 0, s->case_sensitive)) - return; - } - - // if a Subcase on the same level has already been entered - if(s->subcasesEnteredLevels.count(s->subcasesCurrentLevel) != 0) { - s->subcasesHasSkipped = true; - return; - } - - s->subcasesStack.push_back(*this); - if(s->hasLoggedCurrentTestStart) - logTestEnd(); - s->hasLoggedCurrentTestStart = false; - - s->subcasesEnteredLevels.insert(s->subcasesCurrentLevel++); - m_entered = true; - } - - Subcase::Subcase(const Subcase& other) - : m_signature(other.m_signature.m_name, other.m_signature.m_file, - other.m_signature.m_line) - , m_entered(other.m_entered) {} - - Subcase::~Subcase() { - if(m_entered) { - ContextState* s = contextState; - - s->subcasesCurrentLevel--; - // only mark the subcase as passed if no subcases have been skipped - if(s->subcasesHasSkipped == false) - s->subcasesPassed.insert(m_signature); - - if(!s->subcasesStack.empty()) - s->subcasesStack.pop_back(); - if(s->hasLoggedCurrentTestStart) - logTestEnd(); - s->hasLoggedCurrentTestStart = false; - } - } - - Result::~Result() {} - - Result& Result::operator=(const Result& other) { - m_passed = other.m_passed; - m_decomposition = other.m_decomposition; - - return *this; - } - - // for sorting tests by file/line - int fileOrderComparator(const void* a, const void* b) { - const TestCase* lhs = *static_cast(a); - const TestCase* rhs = *static_cast(b); -#ifdef _MSC_VER - // this is needed because MSVC gives different case for drive letters - // for __FILE__ when evaluated in a header and a source file - int res = stricmp(lhs->m_file, rhs->m_file); -#else // _MSC_VER - int res = std::strcmp(lhs->m_file, rhs->m_file); -#endif // _MSC_VER - if(res != 0) - return res; - return static_cast(lhs->m_line - rhs->m_line); - } - - // for sorting tests by suite/file/line - int suiteOrderComparator(const void* a, const void* b) { - const TestCase* lhs = *static_cast(a); - const TestCase* rhs = *static_cast(b); - - int res = std::strcmp(lhs->m_test_suite, rhs->m_test_suite); - if(res != 0) - return res; - return fileOrderComparator(a, b); - } - - // for sorting tests by name/suite/file/line - int nameOrderComparator(const void* a, const void* b) { - const TestCase* lhs = *static_cast(a); - const TestCase* rhs = *static_cast(b); - - int res_name = std::strcmp(lhs->m_name, rhs->m_name); - if(res_name != 0) - return res_name; - return suiteOrderComparator(a, b); - } - - // sets the current test suite - int setTestSuite(const TestSuite& ts) { - doctest_detail_test_suite_ns::getCurrentTestSuite() = ts; - return 0; - } - - // all the registered tests - std::set& getRegisteredTests() { - static std::set data; - return data; - } - - // used by the macros for registering tests - int regTest(const TestCase& tc) { - getRegisteredTests().insert(tc); - return 0; - } - - struct Color - { - enum Code - { - None = 0, - White, - Red, - Green, - Blue, - Cyan, - Yellow, - Grey, - - Bright = 0x10, - - BrightRed = Bright | Red, - BrightGreen = Bright | Green, - LightGrey = Bright | Grey, - BrightWhite = Bright | White - }; - explicit Color(Code code) { use(code); } - ~Color() { use(None); } - - static void use(Code code); - static void init(); - }; - -#ifdef DOCTEST_CONFIG_COLORS_WINDOWS - HANDLE g_stdoutHandle; - WORD g_originalForegroundAttributes; - WORD g_originalBackgroundAttributes; - bool g_attrsInitted = false; -#endif // DOCTEST_CONFIG_COLORS_WINDOWS - - void Color::init() { -#ifdef DOCTEST_CONFIG_COLORS_WINDOWS - if(!g_attrsInitted) { - g_stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); - g_attrsInitted = true; - CONSOLE_SCREEN_BUFFER_INFO csbiInfo; - GetConsoleScreenBufferInfo(g_stdoutHandle, &csbiInfo); - g_originalForegroundAttributes = - csbiInfo.wAttributes & - ~(BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY); - g_originalBackgroundAttributes = - csbiInfo.wAttributes & - ~(FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY); - } -#endif // DOCTEST_CONFIG_COLORS_WINDOWS - } - - void Color::use(Code -#ifndef DOCTEST_CONFIG_COLORS_NONE - code -#endif // DOCTEST_CONFIG_COLORS_NONE - ) { - const ContextState* p = contextState; - if(p->no_colors) - return; -#ifdef DOCTEST_CONFIG_COLORS_ANSI - if(isatty(STDOUT_FILENO) == false && p->force_colors == false) - return; - - const char* col = ""; - // clang-format off - switch(code) { //!OCLINT missing break in switch statement / unnecessary default statement in covered switch statement - case Color::Red: col = "[0;31m"; break; - case Color::Green: col = "[0;32m"; break; - case Color::Blue: col = "[0;34m"; break; - case Color::Cyan: col = "[0;36m"; break; - case Color::Yellow: col = "[0;33m"; break; - case Color::Grey: col = "[1;30m"; break; - case Color::LightGrey: col = "[0;37m"; break; - case Color::BrightRed: col = "[1;31m"; break; - case Color::BrightGreen: col = "[1;32m"; break; - case Color::BrightWhite: col = "[1;37m"; break; - case Color::Bright: // invalid - case Color::None: - case Color::White: - default: col = "[0m"; - } - // clang-format on - std::printf("\033%s", col); -#endif // DOCTEST_CONFIG_COLORS_ANSI - -#ifdef DOCTEST_CONFIG_COLORS_WINDOWS - if(isatty(fileno(stdout)) == false && p->force_colors == false) - return; - -#define DOCTEST_SET_ATTR(x) \ - SetConsoleTextAttribute(g_stdoutHandle, x | g_originalBackgroundAttributes) - - // clang-format off - switch (code) { - case Color::White: DOCTEST_SET_ATTR(FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break; - case Color::Red: DOCTEST_SET_ATTR(FOREGROUND_RED); break; - case Color::Green: DOCTEST_SET_ATTR(FOREGROUND_GREEN); break; - case Color::Blue: DOCTEST_SET_ATTR(FOREGROUND_BLUE); break; - case Color::Cyan: DOCTEST_SET_ATTR(FOREGROUND_BLUE | FOREGROUND_GREEN); break; - case Color::Yellow: DOCTEST_SET_ATTR(FOREGROUND_RED | FOREGROUND_GREEN); break; - case Color::Grey: DOCTEST_SET_ATTR(0); break; - case Color::LightGrey: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY); break; - case Color::BrightRed: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_RED); break; - case Color::BrightGreen: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN); break; - case Color::BrightWhite: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break; - case Color::None: - case Color::Bright: // invalid - default: DOCTEST_SET_ATTR(g_originalForegroundAttributes); - } -// clang-format on -#undef DOCTEST_SET_ATTR -#endif // DOCTEST_CONFIG_COLORS_WINDOWS - } - - IExceptionTranslator::~IExceptionTranslator() {} - - std::vector& getExceptionTranslators() { - static std::vector data; - return data; - } - - void registerExceptionTranslatorImpl(const IExceptionTranslator* translateFunction) { - getExceptionTranslators().push_back(translateFunction); - } - - String translateActiveException() { -#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS - String res; - std::vector& translators = getExceptionTranslators(); - for(size_t i = 0; i < translators.size(); ++i) - if(translators[i]->translate(res)) - return res; - // clang-format off - try { - throw; - } catch(std::exception& ex) { - return ex.what(); - } catch(std::string& msg) { - return msg.c_str(); - } catch(const char* msg) { - return msg; - } catch(...) { - return "unknown exception"; - } -// clang-format on -#else // DOCTEST_CONFIG_NO_EXCEPTIONS - return ""; -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS - } - - void writeStringToStream(std::ostream* stream, const String& str) { *stream << str; } - -#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING - void toStream(std::ostream* stream, char* in) { *stream << in; } - void toStream(std::ostream* stream, const char* in) { *stream << in; } -#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING - void toStream(std::ostream* stream, bool in) { - *stream << std::boolalpha << in << std::noboolalpha; - } - void toStream(std::ostream* stream, float in) { *stream << in; } - void toStream(std::ostream* stream, double in) { *stream << in; } - void toStream(std::ostream* stream, double long in) { *stream << in; } - - void toStream(std::ostream* stream, char in) { *stream << in; } - void toStream(std::ostream* stream, char signed in) { *stream << in; } - void toStream(std::ostream* stream, char unsigned in) { *stream << in; } - void toStream(std::ostream* stream, int short in) { *stream << in; } - void toStream(std::ostream* stream, int short unsigned in) { *stream << in; } - void toStream(std::ostream* stream, int in) { *stream << in; } - void toStream(std::ostream* stream, int unsigned in) { *stream << in; } - void toStream(std::ostream* stream, int long in) { *stream << in; } - void toStream(std::ostream* stream, int long unsigned in) { *stream << in; } - -#ifdef DOCTEST_CONFIG_WITH_LONG_LONG - void toStream(std::ostream* stream, int long long in) { *stream << in; } - void toStream(std::ostream* stream, int long long unsigned in) { *stream << in; } -#endif // DOCTEST_CONFIG_WITH_LONG_LONG - - void addToContexts(IContextScope* ptr) { contextState->contexts.push_back(ptr); } - void popFromContexts() { contextState->contexts.pop_back(); } - void useContextIfExceptionOccurred(IContextScope* ptr) { - if(std::uncaught_exception()) { - std::ostringstream stream; - ptr->build(&stream); - contextState->exceptionalContexts.push_back(stream.str()); - } - } - - void printSummary(); - -#if !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && !defined(DOCTEST_CONFIG_WINDOWS_SEH) - void reportFatal(const std::string&) {} - struct FatalConditionHandler - { - void reset() {} - }; -#else // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH - - void reportFatal(const std::string& message) { - DOCTEST_LOG_START(); - - contextState->numAssertions += contextState->numAssertionsForCurrentTestcase; - logTestException(message.c_str(), true); - logTestEnd(); - contextState->numFailed++; - - printSummary(); - } - -#ifdef DOCTEST_PLATFORM_WINDOWS - - struct SignalDefs - { - DWORD id; - const char* name; - }; - // There is no 1-1 mapping between signals and windows exceptions. - // Windows can easily distinguish between SO and SigSegV, - // but SigInt, SigTerm, etc are handled differently. - SignalDefs signalDefs[] = { - {EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal"}, - {EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow"}, - {EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal"}, - {EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error"}, - }; - - struct FatalConditionHandler - { - static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { - for(size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { - if(ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) { - reportFatal(signalDefs[i].name); - } - } - // If its not an exception we care about, pass it along. - // This stops us from eating debugger breaks etc. - return EXCEPTION_CONTINUE_SEARCH; - } - - FatalConditionHandler() { - isSet = true; - // 32k seems enough for doctest to handle stack overflow, - // but the value was found experimentally, so there is no strong guarantee - guaranteeSize = 32 * 1024; - exceptionHandlerHandle = 0; - // Register as first handler in current chain - exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); - // Pass in guarantee size to be filled - SetThreadStackGuarantee(&guaranteeSize); - } - - static void reset() { - if(isSet) { - // Unregister handler and restore the old guarantee - RemoveVectoredExceptionHandler(exceptionHandlerHandle); - SetThreadStackGuarantee(&guaranteeSize); - exceptionHandlerHandle = 0; - isSet = false; - } - } - - ~FatalConditionHandler() { reset(); } - - private: - static bool isSet; - static ULONG guaranteeSize; - static PVOID exceptionHandlerHandle; - }; - - bool FatalConditionHandler::isSet = false; - ULONG FatalConditionHandler::guaranteeSize = 0; - PVOID FatalConditionHandler::exceptionHandlerHandle = 0; - -#else // DOCTEST_PLATFORM_WINDOWS - - struct SignalDefs - { - int id; - const char* name; - }; - SignalDefs signalDefs[] = {{SIGINT, "SIGINT - Terminal interrupt signal"}, - {SIGILL, "SIGILL - Illegal instruction signal"}, - {SIGFPE, "SIGFPE - Floating point error signal"}, - {SIGSEGV, "SIGSEGV - Segmentation violation signal"}, - {SIGTERM, "SIGTERM - Termination request signal"}, - {SIGABRT, "SIGABRT - Abort (abnormal termination) signal"}}; - - struct FatalConditionHandler - { - static bool isSet; - static struct sigaction oldSigActions[sizeof(signalDefs) / sizeof(SignalDefs)]; - static stack_t oldSigStack; - static char altStackMem[SIGSTKSZ]; - - static void handleSignal(int sig) { - std::string name = ""; - for(std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { - SignalDefs& def = signalDefs[i]; - if(sig == def.id) { - name = def.name; - break; - } - } - reset(); - reportFatal(name); - raise(sig); - } - - FatalConditionHandler() { - isSet = true; - stack_t sigStack; - sigStack.ss_sp = altStackMem; - sigStack.ss_size = SIGSTKSZ; - sigStack.ss_flags = 0; - sigaltstack(&sigStack, &oldSigStack); - struct sigaction sa = {0}; - - sa.sa_handler = handleSignal; // NOLINT - sa.sa_flags = SA_ONSTACK; - for(std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { - sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); - } - } - - ~FatalConditionHandler() { reset(); } - static void reset() { - if(isSet) { - // Set signals back to previous values -- hopefully nobody overwrote them in the meantime - for(std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { - sigaction(signalDefs[i].id, &oldSigActions[i], 0); - } - // Return the old stack - sigaltstack(&oldSigStack, 0); - isSet = false; - } - } - }; - - bool FatalConditionHandler::isSet = false; - struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs) / sizeof(SignalDefs)] = - {}; - stack_t FatalConditionHandler::oldSigStack = {}; - char FatalConditionHandler::altStackMem[SIGSTKSZ] = {}; - -#endif // DOCTEST_PLATFORM_WINDOWS -#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH - - // depending on the current options this will remove the path of filenames - const char* fileForOutput(const char* file) { - if(contextState->no_path_in_filenames) { - const char* back = std::strrchr(file, '\\'); - const char* forward = std::strrchr(file, '/'); - if(back || forward) { - if(back > forward) - forward = back; - return forward + 1; - } - } - return file; - } - - // depending on the current options this will substitute the line numbers with 0 - int lineForOutput(int line) { - if(contextState->no_line_numbers) - return 0; - return line; - } - -#ifdef DOCTEST_PLATFORM_MAC -#include -#include -#include - // The following function is taken directly from the following technical note: - // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html - // Returns true if the current process is being debugged (either - // running under the debugger or has a debugger attached post facto). - bool isDebuggerActive() { - int mib[4]; - kinfo_proc info; - size_t size; - // Initialize the flags so that, if sysctl fails for some bizarre - // reason, we get a predictable result. - info.kp_proc.p_flag = 0; - // Initialize mib, which tells sysctl the info we want, in this case - // we're looking for information about a specific process ID. - mib[0] = CTL_KERN; - mib[1] = KERN_PROC; - mib[2] = KERN_PROC_PID; - mib[3] = getpid(); - // Call sysctl. - size = sizeof(info); - if(sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, 0, 0) != 0) { - fprintf(stderr, "\n** Call to sysctl failed - unable to determine if debugger is " - "active **\n\n"); - return false; - } - // We're being debugged if the P_TRACED flag is set. - return ((info.kp_proc.p_flag & P_TRACED) != 0); - } -#elif defined(_MSC_VER) || defined(__MINGW32__) - bool isDebuggerActive() { return ::IsDebuggerPresent() != 0; } -#else - bool isDebuggerActive() { return false; } -#endif // Platform - -#ifdef DOCTEST_PLATFORM_WINDOWS - void myOutputDebugString(const String& text) { ::OutputDebugStringA(text.c_str()); } -#else - // TODO: integration with XCode and other IDEs - void myOutputDebugString(const String&) {} -#endif // Platform - - const char* getSeparator() { - return "===============================================================================\n"; - } - - void printToDebugConsole(const String& text) { - if(isDebuggerActive()) - myOutputDebugString(text.c_str()); - } - - void addFailedAssert(assertType::Enum assert_type) { - if((assert_type & assertType::is_warn) == 0) { //!OCLINT bitwise operator in conditional - contextState->numFailedAssertions++; - contextState->numFailedAssertionsForCurrentTestcase++; - contextState->hasCurrentTestFailed = true; - } - } - - void logTestStart(const TestCase& tc) { - char loc[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - DOCTEST_SNPRINTF(loc, DOCTEST_COUNTOF(loc), "%s(%d)\n", fileForOutput(tc.m_file), - lineForOutput(tc.m_line)); - - char ts1[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - DOCTEST_SNPRINTF(ts1, DOCTEST_COUNTOF(ts1), "TEST SUITE: "); - char ts2[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - DOCTEST_SNPRINTF(ts2, DOCTEST_COUNTOF(ts2), "%s\n", tc.m_test_suite); - char n1[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - DOCTEST_SNPRINTF(n1, DOCTEST_COUNTOF(n1), "TEST CASE: "); - char n2[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - DOCTEST_SNPRINTF(n2, DOCTEST_COUNTOF(n2), "%s\n", tc.m_name); - char d1[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - DOCTEST_SNPRINTF(d1, DOCTEST_COUNTOF(d1), "DESCRIPTION: "); - char d2[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - DOCTEST_SNPRINTF(d2, DOCTEST_COUNTOF(d2), "%s\n", tc.m_description); - - // hack for BDD style of macros - to not print "TEST CASE:" - char scenario[] = " Scenario:"; - if(std::string(tc.m_name).substr(0, DOCTEST_COUNTOF(scenario) - 1) == scenario) - n1[0] = '\0'; - - DOCTEST_PRINTF_COLORED(getSeparator(), Color::Yellow); - DOCTEST_PRINTF_COLORED(loc, Color::LightGrey); - - String forDebugConsole; - if(tc.m_description) { - DOCTEST_PRINTF_COLORED(d1, Color::Yellow); - DOCTEST_PRINTF_COLORED(d2, Color::None); - forDebugConsole += d1; - forDebugConsole += d2; - } - if(tc.m_test_suite[0] != '\0') { - DOCTEST_PRINTF_COLORED(ts1, Color::Yellow); - DOCTEST_PRINTF_COLORED(ts2, Color::None); - forDebugConsole += ts1; - forDebugConsole += ts2; - } - DOCTEST_PRINTF_COLORED(n1, Color::Yellow); - DOCTEST_PRINTF_COLORED(n2, Color::None); - - String subcaseStuff; - std::vector& subcasesStack = contextState->subcasesStack; - for(unsigned i = 0; i < subcasesStack.size(); ++i) { - if(subcasesStack[i].m_signature.m_name[0] != '\0') { - char subcase[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - DOCTEST_SNPRINTF(subcase, DOCTEST_COUNTOF(loc), " %s\n", - subcasesStack[i].m_signature.m_name); - DOCTEST_PRINTF_COLORED(subcase, Color::None); - subcaseStuff += subcase; - } - } - - DOCTEST_PRINTF_COLORED("\n", Color::None); - - printToDebugConsole(String(getSeparator()) + loc + forDebugConsole.c_str() + n1 + n2 + - subcaseStuff.c_str() + "\n"); - } - - void logTestEnd() {} - - void logTestException(const String& what, bool crash) { - char msg[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - - DOCTEST_SNPRINTF(msg, DOCTEST_COUNTOF(msg), "TEST CASE FAILED!\n"); - - char info1[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - char info2[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - info1[0] = 0; - info2[0] = 0; - DOCTEST_SNPRINTF(info1, DOCTEST_COUNTOF(info1), - crash ? "crashed:\n" : "threw exception:\n"); - DOCTEST_SNPRINTF(info2, DOCTEST_COUNTOF(info2), " %s\n", what.c_str()); - - std::string contextStr; - - if(!contextState->exceptionalContexts.empty()) { - contextStr += "with context:\n"; - for(size_t i = contextState->exceptionalContexts.size(); i > 0; --i) { - contextStr += " "; - contextStr += contextState->exceptionalContexts[i - 1]; - contextStr += "\n"; - } - } - - DOCTEST_PRINTF_COLORED(msg, Color::Red); - DOCTEST_PRINTF_COLORED(info1, Color::None); - DOCTEST_PRINTF_COLORED(info2, Color::Cyan); - DOCTEST_PRINTF_COLORED(contextStr.c_str(), Color::None); - DOCTEST_PRINTF_COLORED("\n", Color::None); - - printToDebugConsole(String(msg) + info1 + info2 + contextStr.c_str() + "\n"); - } - - String logContext() { - std::ostringstream stream; - std::vector& contexts = contextState->contexts; - if(!contexts.empty()) - stream << "with context:\n"; - for(size_t i = 0; i < contexts.size(); ++i) { - stream << " "; - contexts[i]->build(&stream); - stream << "\n"; - } - return stream.str().c_str(); - } - - const char* getFailString(assertType::Enum assert_type) { - if(assert_type & assertType::is_warn) //!OCLINT bitwise operator in conditional - return "WARNING"; - if(assert_type & assertType::is_check) //!OCLINT bitwise operator in conditional - return "ERROR"; - if(assert_type & assertType::is_require) //!OCLINT bitwise operator in conditional - return "FATAL ERROR"; - return ""; - } - - void logAssert(bool passed, const char* decomposition, bool threw, const String& exception, - const char* expr, assertType::Enum assert_type, const char* file, int line) { - char loc[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - DOCTEST_SNPRINTF(loc, DOCTEST_COUNTOF(loc), "%s(%d)", fileForOutput(file), - lineForOutput(line)); - - char msg[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - DOCTEST_SNPRINTF(msg, DOCTEST_COUNTOF(msg), " %s!\n", - passed ? "PASSED" : getFailString(assert_type)); - - char info1[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - DOCTEST_SNPRINTF(info1, DOCTEST_COUNTOF(info1), " %s( %s )\n", - getAssertString(assert_type), expr); - - char info2[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - char info3[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - info2[0] = 0; - info3[0] = 0; - if(threw) { - DOCTEST_SNPRINTF(info2, DOCTEST_COUNTOF(info2), "threw exception:\n"); - DOCTEST_SNPRINTF(info3, DOCTEST_COUNTOF(info3), " %s\n", exception.c_str()); - } else { - DOCTEST_SNPRINTF(info2, DOCTEST_COUNTOF(info2), "with expansion:\n"); - DOCTEST_SNPRINTF(info3, DOCTEST_COUNTOF(info3), " %s( %s )\n", - getAssertString(assert_type), decomposition); - } - - bool isWarn = assert_type & assertType::is_warn; - DOCTEST_PRINTF_COLORED(loc, Color::LightGrey); - DOCTEST_PRINTF_COLORED(msg, - passed ? Color::BrightGreen : isWarn ? Color::Yellow : Color::Red); - DOCTEST_PRINTF_COLORED(info1, Color::Cyan); - DOCTEST_PRINTF_COLORED(info2, Color::None); - DOCTEST_PRINTF_COLORED(info3, Color::Cyan); - String context = logContext(); - DOCTEST_PRINTF_COLORED(context.c_str(), Color::None); - DOCTEST_PRINTF_COLORED("\n", Color::None); - - printToDebugConsole(String(loc) + msg + info1 + info2 + info3 + context.c_str() + "\n"); - } - - void logAssertThrows(bool threw, const char* expr, assertType::Enum assert_type, - const char* file, int line) { - char loc[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - DOCTEST_SNPRINTF(loc, DOCTEST_COUNTOF(loc), "%s(%d)", fileForOutput(file), - lineForOutput(line)); - - char msg[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - DOCTEST_SNPRINTF(msg, DOCTEST_COUNTOF(msg), " %s!\n", - threw ? "PASSED" : getFailString(assert_type)); - - char info1[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - DOCTEST_SNPRINTF(info1, DOCTEST_COUNTOF(info1), " %s( %s )\n", - getAssertString(assert_type), expr); - - char info2[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - info2[0] = 0; - - if(!threw) - DOCTEST_SNPRINTF(info2, DOCTEST_COUNTOF(info2), "didn't throw at all\n"); - - bool isWarn = assert_type & assertType::is_warn; - DOCTEST_PRINTF_COLORED(loc, Color::LightGrey); - DOCTEST_PRINTF_COLORED(msg, - threw ? Color::BrightGreen : isWarn ? Color::Yellow : Color::Red); - DOCTEST_PRINTF_COLORED(info1, Color::Cyan); - DOCTEST_PRINTF_COLORED(info2, Color::None); - String context = logContext(); - DOCTEST_PRINTF_COLORED(context.c_str(), Color::None); - DOCTEST_PRINTF_COLORED("\n", Color::None); - - printToDebugConsole(String(loc) + msg + info1 + info2 + context.c_str() + "\n"); - } - - void logAssertThrowsAs(bool threw, bool threw_as, const char* as, const String& exception, - const char* expr, assertType::Enum assert_type, const char* file, - int line) { - char loc[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - DOCTEST_SNPRINTF(loc, DOCTEST_COUNTOF(loc), "%s(%d)", fileForOutput(file), - lineForOutput(line)); - - char msg[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - DOCTEST_SNPRINTF(msg, DOCTEST_COUNTOF(msg), " %s!\n", - threw_as ? "PASSED" : getFailString(assert_type)); - - char info1[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - DOCTEST_SNPRINTF(info1, DOCTEST_COUNTOF(info1), " %s( %s, %s )\n", - getAssertString(assert_type), expr, as); - - char info2[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - char info3[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - info2[0] = 0; - info3[0] = 0; - - if(!threw) { //!OCLINT inverted logic - DOCTEST_SNPRINTF(info2, DOCTEST_COUNTOF(info2), "didn't throw at all\n"); - } else if(!threw_as) { - DOCTEST_SNPRINTF(info2, DOCTEST_COUNTOF(info2), "threw a different exception:\n"); - DOCTEST_SNPRINTF(info3, DOCTEST_COUNTOF(info3), " %s\n", exception.c_str()); - } - - bool isWarn = assert_type & assertType::is_warn; - DOCTEST_PRINTF_COLORED(loc, Color::LightGrey); - DOCTEST_PRINTF_COLORED(msg, - threw_as ? Color::BrightGreen : isWarn ? Color::Yellow : Color::Red); - DOCTEST_PRINTF_COLORED(info1, Color::Cyan); - DOCTEST_PRINTF_COLORED(info2, Color::None); - DOCTEST_PRINTF_COLORED(info3, Color::Cyan); - String context = logContext(); - DOCTEST_PRINTF_COLORED(context.c_str(), Color::None); - DOCTEST_PRINTF_COLORED("\n", Color::None); - - printToDebugConsole(String(loc) + msg + info1 + info2 + info3 + context.c_str() + "\n"); - } - - void logAssertNothrow(bool threw, const String& exception, const char* expr, - assertType::Enum assert_type, const char* file, int line) { - char loc[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - DOCTEST_SNPRINTF(loc, DOCTEST_COUNTOF(loc), "%s(%d)", fileForOutput(file), - lineForOutput(line)); - - char msg[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - DOCTEST_SNPRINTF(msg, DOCTEST_COUNTOF(msg), " %s!\n", - threw ? getFailString(assert_type) : "PASSED"); - - char info1[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - DOCTEST_SNPRINTF(info1, DOCTEST_COUNTOF(info1), " %s( %s )\n", - getAssertString(assert_type), expr); - - char info2[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - char info3[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - info2[0] = 0; - info3[0] = 0; - if(threw) { - DOCTEST_SNPRINTF(info2, DOCTEST_COUNTOF(info2), "threw exception:\n"); - DOCTEST_SNPRINTF(info3, DOCTEST_COUNTOF(info3), " %s\n", exception.c_str()); - } - - bool isWarn = assert_type & assertType::is_warn; - DOCTEST_PRINTF_COLORED(loc, Color::LightGrey); - DOCTEST_PRINTF_COLORED(msg, - threw ? isWarn ? Color::Yellow : Color::Red : Color::BrightGreen); - DOCTEST_PRINTF_COLORED(info1, Color::Cyan); - DOCTEST_PRINTF_COLORED(info2, Color::None); - DOCTEST_PRINTF_COLORED(info3, Color::Cyan); - String context = logContext(); - DOCTEST_PRINTF_COLORED(context.c_str(), Color::None); - DOCTEST_PRINTF_COLORED("\n", Color::None); - - printToDebugConsole(String(loc) + msg + info1 + info2 + info3 + context.c_str() + "\n"); - } - - ResultBuilder::ResultBuilder(assertType::Enum assert_type, const char* file, int line, - const char* expr, const char* exception_type) - : m_assert_type(assert_type) - , m_file(file) - , m_line(line) - , m_expr(expr) - , m_exception_type(exception_type) - , m_threw(false) - , m_threw_as(false) - , m_failed(false) { -#ifdef _MSC_VER - if(m_expr[0] == ' ') // this happens when variadic macros are disabled under MSVC - ++m_expr; -#endif // _MSC_VER - } - - ResultBuilder::~ResultBuilder() {} - - void ResultBuilder::unexpectedExceptionOccurred() { - m_threw = true; - - m_exception = translateActiveException(); - } - - bool ResultBuilder::log() { - if((m_assert_type & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional - contextState->numAssertionsForCurrentTestcase++; - - if(m_assert_type & assertType::is_throws) { //!OCLINT bitwise operator in conditional - m_failed = !m_threw; - } else if(m_assert_type & //!OCLINT bitwise operator in conditional - assertType::is_throws_as) { - m_failed = !m_threw_as; - } else if(m_assert_type & //!OCLINT bitwise operator in conditional - assertType::is_nothrow) { - m_failed = m_threw; - } else { - m_failed = m_result; - } - - if(m_failed || contextState->success) { - DOCTEST_LOG_START(); - - if(m_assert_type & assertType::is_throws) { //!OCLINT bitwise operator in conditional - logAssertThrows(m_threw, m_expr, m_assert_type, m_file, m_line); - } else if(m_assert_type & //!OCLINT bitwise operator in conditional - assertType::is_throws_as) { - logAssertThrowsAs(m_threw, m_threw_as, m_exception_type, m_exception, m_expr, - m_assert_type, m_file, m_line); - } else if(m_assert_type & //!OCLINT bitwise operator in conditional - assertType::is_nothrow) { - logAssertNothrow(m_threw, m_exception, m_expr, m_assert_type, m_file, m_line); - } else { - logAssert(m_result.m_passed, m_result.m_decomposition.c_str(), m_threw, m_exception, - m_expr, m_assert_type, m_file, m_line); - } - } - - if(m_failed) - addFailedAssert(m_assert_type); - - return m_failed && isDebuggerActive() && !contextState->no_breaks; // break into debugger - } - - void ResultBuilder::react() const { - if(m_failed && checkIfShouldThrow(m_assert_type)) - throwException(); - } - - MessageBuilder::MessageBuilder(const char* file, int line, - doctest::detail::assertType::Enum severity) - : m_stream(createStream()) - , m_file(file) - , m_line(line) - , m_severity(severity) {} - - bool MessageBuilder::log() { - DOCTEST_LOG_START(); - - bool is_warn = m_severity & doctest::detail::assertType::is_warn; - - // warn is just a message in this context so we dont treat it as an assert - if(!is_warn) { - contextState->numAssertionsForCurrentTestcase++; - addFailedAssert(m_severity); - } - - char loc[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - DOCTEST_SNPRINTF(loc, DOCTEST_COUNTOF(loc), "%s(%d)", fileForOutput(m_file), - lineForOutput(m_line)); - char msg[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - DOCTEST_SNPRINTF(msg, DOCTEST_COUNTOF(msg), " %s!\n", - is_warn ? "MESSAGE" : getFailString(m_severity)); - - DOCTEST_PRINTF_COLORED(loc, Color::LightGrey); - DOCTEST_PRINTF_COLORED(msg, is_warn ? Color::Yellow : Color::Red); - - String info = getStreamResult(m_stream); - if(info.size()) { - DOCTEST_PRINTF_COLORED(" ", Color::None); - DOCTEST_PRINTF_COLORED(info.c_str(), Color::None); - DOCTEST_PRINTF_COLORED("\n", Color::None); - } - String context = logContext(); - DOCTEST_PRINTF_COLORED(context.c_str(), Color::None); - DOCTEST_PRINTF_COLORED("\n", Color::None); - - printToDebugConsole(String(loc) + msg + " " + info.c_str() + "\n" + context.c_str() + - "\n"); - - return isDebuggerActive() && !contextState->no_breaks && !is_warn; // break into debugger - } - - void MessageBuilder::react() { - if(m_severity & assertType::is_require) //!OCLINT bitwise operator in conditional - throwException(); - } - - MessageBuilder::~MessageBuilder() { freeStream(m_stream); } - - // the implementation of parseFlag() - bool parseFlagImpl(int argc, const char* const* argv, const char* pattern) { - for(int i = argc - 1; i >= 0; --i) { - const char* temp = std::strstr(argv[i], pattern); - if(temp && my_strlen(temp) == my_strlen(pattern)) { - // eliminate strings in which the chars before the option are not '-' - bool noBadCharsFound = true; //!OCLINT prefer early exits and continue - while(temp != argv[i]) { - if(*--temp != '-') { - noBadCharsFound = false; - break; - } - } - if(noBadCharsFound && argv[i][0] == '-') - return true; - } - } - return false; - } - - // locates a flag on the command line - bool parseFlag(int argc, const char* const* argv, const char* pattern) { -#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS - if(!parseFlagImpl(argc, argv, pattern)) - return parseFlagImpl(argc, argv, pattern + 3); // 3 for "dt-" - return true; -#else // DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS - return parseFlagImpl(argc, argv, pattern); -#endif // DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS - } - - // the implementation of parseOption() - bool parseOptionImpl(int argc, const char* const* argv, const char* pattern, String& res) { - for(int i = argc - 1; i >= 0; --i) { - const char* temp = std::strstr(argv[i], pattern); - if(temp) { //!OCLINT prefer early exits and continue - // eliminate matches in which the chars before the option are not '-' - bool noBadCharsFound = true; - const char* curr = argv[i]; - while(curr != temp) { - if(*curr++ != '-') { - noBadCharsFound = false; - break; - } - } - if(noBadCharsFound && argv[i][0] == '-') { - temp += my_strlen(pattern); - unsigned len = my_strlen(temp); - if(len) { - res = temp; - return true; - } - } - } - } - return false; - } - - // parses an option and returns the string after the '=' character - bool parseOption(int argc, const char* const* argv, const char* pattern, String& res, - const String& defaultVal = String()) { - res = defaultVal; -#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS - if(!parseOptionImpl(argc, argv, pattern, res)) - return parseOptionImpl(argc, argv, pattern + 3, res); // 3 for "dt-" - return true; -#else // DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS - return parseOptionImpl(argc, argv, pattern, res); -#endif // DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS - } - - // parses a comma separated list of words after a pattern in one of the arguments in argv - bool parseCommaSepArgs(int argc, const char* const* argv, const char* pattern, - std::vector& res) { - String filtersString; - if(parseOption(argc, argv, pattern, filtersString)) { - // tokenize with "," as a separator - // cppcheck-suppress strtokCalled - char* pch = std::strtok(filtersString.c_str(), ","); // modifies the string - while(pch != 0) { - if(my_strlen(pch)) - res.push_back(pch); - // uses the strtok() internal state to go to the next token - // cppcheck-suppress strtokCalled - pch = std::strtok(0, ","); - } - return true; - } - return false; - } - - enum optionType - { - option_bool, - option_int - }; - - // parses an int/bool option from the command line - bool parseIntOption(int argc, const char* const* argv, const char* pattern, optionType type, - int& res) { - String parsedValue; - if(!parseOption(argc, argv, pattern, parsedValue)) - return false; - - if(type == 0) { - // boolean - const char positive[][5] = {"1", "true", "on", "yes"}; // 5 - strlen("true") + 1 - const char negative[][6] = {"0", "false", "off", "no"}; // 6 - strlen("false") + 1 - - // if the value matches any of the positive/negative possibilities - for(unsigned i = 0; i < 4; i++) { - if(parsedValue.compare(positive[i], true) == 0) { - res = 1; //!OCLINT parameter reassignment - return true; - } - if(parsedValue.compare(negative[i], true) == 0) { - res = 0; //!OCLINT parameter reassignment - return true; - } - } - } else { - // integer - int theInt = std::atoi(parsedValue.c_str()); // NOLINT - if(theInt != 0) { - res = theInt; //!OCLINT parameter reassignment - return true; - } - } - return false; - } - - void printVersion() { - if(contextState->no_version == false) { - DOCTEST_PRINTF_COLORED("[doctest] ", Color::Cyan); - std::printf("doctest version is \"%s\"\n", DOCTEST_VERSION_STR); - } - } - - void printHelp() { - printVersion(); - // clang-format off - DOCTEST_PRINTF_COLORED("[doctest]\n", Color::Cyan); - DOCTEST_PRINTF_COLORED("[doctest] ", Color::Cyan); - std::printf("boolean values: \"1/on/yes/true\" or \"0/off/no/false\"\n"); - DOCTEST_PRINTF_COLORED("[doctest] ", Color::Cyan); - std::printf("filter values: \"str1,str2,str3\" (comma separated strings)\n"); - DOCTEST_PRINTF_COLORED("[doctest]\n", Color::Cyan); - DOCTEST_PRINTF_COLORED("[doctest] ", Color::Cyan); - std::printf("filters use wildcards for matching strings\n"); - DOCTEST_PRINTF_COLORED("[doctest] ", Color::Cyan); - std::printf("something passes a filter if any of the strings in a filter matches\n"); - DOCTEST_PRINTF_COLORED("[doctest]\n", Color::Cyan); - DOCTEST_PRINTF_COLORED("[doctest] ", Color::Cyan); - std::printf("ALL FLAGS, OPTIONS AND FILTERS ALSO AVAILABLE WITH A \"dt-\" PREFIX!!!\n"); - DOCTEST_PRINTF_COLORED("[doctest]\n", Color::Cyan); - DOCTEST_PRINTF_COLORED("[doctest] ", Color::Cyan); - std::printf("Query flags - the program quits after them. Available:\n\n"); - std::printf(" -?, --help, -h prints this message\n"); - std::printf(" -v, --version prints the version\n"); - std::printf(" -c, --count prints the number of matching tests\n"); - std::printf(" -ltc, --list-test-cases lists all matching tests by name\n"); - std::printf(" -lts, --list-test-suites lists all matching test suites\n\n"); - // ========================================================================================= << 79 - DOCTEST_PRINTF_COLORED("[doctest] ", Color::Cyan); - std::printf("The available / options/filters are:\n\n"); - std::printf(" -tc, --test-case= filters tests by their name\n"); - std::printf(" -tce, --test-case-exclude= filters OUT tests by their name\n"); - std::printf(" -sf, --source-file= filters tests by their file\n"); - std::printf(" -sfe, --source-file-exclude= filters OUT tests by their file\n"); - std::printf(" -ts, --test-suite= filters tests by their test suite\n"); - std::printf(" -tse, --test-suite-exclude= filters OUT tests by their test suite\n"); - std::printf(" -sc, --subcase= filters subcases by their name\n"); - std::printf(" -sce, --subcase-exclude= filters OUT subcases by their name\n"); - std::printf(" -ob, --order-by= how the tests should be ordered\n"); - std::printf(" - by [file/suite/name/rand]\n"); - std::printf(" -rs, --rand-seed= seed for random ordering\n"); - std::printf(" -f, --first= the first test passing the filters to\n"); - std::printf(" execute - for range-based execution\n"); - std::printf(" -l, --last= the last test passing the filters to\n"); - std::printf(" execute - for range-based execution\n"); - std::printf(" -aa, --abort-after= stop after failed assertions\n"); - std::printf(" -scfl,--subcase-filter-levels= apply filters for the first levels\n"); - DOCTEST_PRINTF_COLORED("\n[doctest] ", Color::Cyan); - std::printf("Bool options - can be used like flags and true is assumed. Available:\n\n"); - std::printf(" -s, --success= include successful assertions in output\n"); - std::printf(" -cs, --case-sensitive= filters being treated as case sensitive\n"); - std::printf(" -e, --exit= exits after the tests finish\n"); - std::printf(" -d, --duration= prints the time duration of each test\n"); - std::printf(" -nt, --no-throw= skips exceptions-related assert checks\n"); - std::printf(" -ne, --no-exitcode= returns (or exits) always with success\n"); - std::printf(" -nr, --no-run= skips all runtime doctest operations\n"); - std::printf(" -nv, --no-version= omit the framework version in the output\n"); - std::printf(" -nc, --no-colors= disables colors in output\n"); - std::printf(" -fc, --force-colors= use colors even when not in a tty\n"); - std::printf(" -nb, --no-breaks= disables breakpoints in debuggers\n"); - std::printf(" -ns, --no-skip= don't skip test cases marked as skip\n"); - std::printf(" -npf, --no-path-filenames= only filenames and no paths in output\n"); - std::printf(" -nln, --no-line-numbers= 0 instead of real line numbers in output\n"); - // ========================================================================================= << 79 - // clang-format on - - DOCTEST_PRINTF_COLORED("\n[doctest] ", Color::Cyan); - std::printf("for more information visit the project documentation\n\n"); - } - - void printSummary() { - const ContextState* p = contextState; - - DOCTEST_PRINTF_COLORED(getSeparator(), Color::Yellow); - if(p->count || p->list_test_cases) { - DOCTEST_PRINTF_COLORED("[doctest] ", Color::Cyan); - std::printf("unskipped test cases passing the current filters: %u\n", - p->numTestsPassingFilters); - } else if(p->list_test_suites) { - DOCTEST_PRINTF_COLORED("[doctest] ", Color::Cyan); - std::printf("unskipped test cases passing the current filters: %u\n", - p->numTestsPassingFilters); - DOCTEST_PRINTF_COLORED("[doctest] ", Color::Cyan); - std::printf("test suites with unskipped test cases passing the current filters: %u\n", - p->numTestSuitesPassingFilters); - } else { - bool anythingFailed = p->numFailed > 0 || p->numFailedAssertions > 0; - - char buff[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - - DOCTEST_PRINTF_COLORED("[doctest] ", Color::Cyan); - - DOCTEST_SNPRINTF(buff, DOCTEST_COUNTOF(buff), "test cases: %6u", - p->numTestsPassingFilters); - DOCTEST_PRINTF_COLORED(buff, Color::None); - DOCTEST_SNPRINTF(buff, DOCTEST_COUNTOF(buff), " | "); - DOCTEST_PRINTF_COLORED(buff, Color::None); - DOCTEST_SNPRINTF(buff, DOCTEST_COUNTOF(buff), "%6d passed", - p->numTestsPassingFilters - p->numFailed); - DOCTEST_PRINTF_COLORED(buff, - (p->numTestsPassingFilters == 0 || anythingFailed) ? - Color::None : - Color::Green); - DOCTEST_SNPRINTF(buff, DOCTEST_COUNTOF(buff), " | "); - DOCTEST_PRINTF_COLORED(buff, Color::None); - DOCTEST_SNPRINTF(buff, DOCTEST_COUNTOF(buff), "%6u failed", p->numFailed); - DOCTEST_PRINTF_COLORED(buff, p->numFailed > 0 ? Color::Red : Color::None); - - DOCTEST_SNPRINTF(buff, DOCTEST_COUNTOF(buff), " | "); - DOCTEST_PRINTF_COLORED(buff, Color::None); - if(p->no_skipped_summary == false) { - int numSkipped = static_cast(getRegisteredTests().size()) - - p->numTestsPassingFilters; - DOCTEST_SNPRINTF(buff, DOCTEST_COUNTOF(buff), "%6d skipped", numSkipped); - DOCTEST_PRINTF_COLORED(buff, numSkipped == 0 ? Color::None : Color::Yellow); - } - DOCTEST_PRINTF_COLORED("\n", Color::None); - - DOCTEST_PRINTF_COLORED("[doctest] ", Color::Cyan); - - DOCTEST_SNPRINTF(buff, DOCTEST_COUNTOF(buff), "assertions: %6d", p->numAssertions); - DOCTEST_PRINTF_COLORED(buff, Color::None); - DOCTEST_SNPRINTF(buff, DOCTEST_COUNTOF(buff), " | "); - DOCTEST_PRINTF_COLORED(buff, Color::None); - DOCTEST_SNPRINTF(buff, DOCTEST_COUNTOF(buff), "%6d passed", - p->numAssertions - p->numFailedAssertions); - DOCTEST_PRINTF_COLORED( - buff, (p->numAssertions == 0 || anythingFailed) ? Color::None : Color::Green); - DOCTEST_SNPRINTF(buff, DOCTEST_COUNTOF(buff), " | "); - DOCTEST_PRINTF_COLORED(buff, Color::None); - DOCTEST_SNPRINTF(buff, DOCTEST_COUNTOF(buff), "%6d failed", p->numFailedAssertions); - DOCTEST_PRINTF_COLORED(buff, p->numFailedAssertions > 0 ? Color::Red : Color::None); - - DOCTEST_SNPRINTF(buff, DOCTEST_COUNTOF(buff), " |\n"); - DOCTEST_PRINTF_COLORED(buff, Color::None); - - DOCTEST_PRINTF_COLORED("[doctest] ", Color::Cyan); - DOCTEST_PRINTF_COLORED("Status: ", Color::None); - const char* result = (p->numFailed > 0) ? "FAILURE!\n" : "SUCCESS!\n"; - DOCTEST_PRINTF_COLORED(result, p->numFailed > 0 ? Color::Red : Color::Green); - } - - // remove any coloring - DOCTEST_PRINTF_COLORED("", Color::None); - } -} // namespace detail - -bool isRunningInTest() { return detail::contextState != 0; } - -Context::Context(int argc, const char* const* argv) - : p(new detail::ContextState) { - parseArgs(argc, argv, true); -} - -Context::~Context() { delete p; } - -void Context::applyCommandLine(int argc, const char* const* argv) { parseArgs(argc, argv); } - -// parses args -void Context::parseArgs(int argc, const char* const* argv, bool withDefaults) { - using namespace detail; - - // clang-format off - parseCommaSepArgs(argc, argv, "dt-source-file=", p->filters[0]); - parseCommaSepArgs(argc, argv, "dt-sf=", p->filters[0]); - parseCommaSepArgs(argc, argv, "dt-source-file-exclude=",p->filters[1]); - parseCommaSepArgs(argc, argv, "dt-sfe=", p->filters[1]); - parseCommaSepArgs(argc, argv, "dt-test-suite=", p->filters[2]); - parseCommaSepArgs(argc, argv, "dt-ts=", p->filters[2]); - parseCommaSepArgs(argc, argv, "dt-test-suite-exclude=", p->filters[3]); - parseCommaSepArgs(argc, argv, "dt-tse=", p->filters[3]); - parseCommaSepArgs(argc, argv, "dt-test-case=", p->filters[4]); - parseCommaSepArgs(argc, argv, "dt-tc=", p->filters[4]); - parseCommaSepArgs(argc, argv, "dt-test-case-exclude=", p->filters[5]); - parseCommaSepArgs(argc, argv, "dt-tce=", p->filters[5]); - parseCommaSepArgs(argc, argv, "dt-subcase=", p->filters[6]); - parseCommaSepArgs(argc, argv, "dt-sc=", p->filters[6]); - parseCommaSepArgs(argc, argv, "dt-subcase-exclude=", p->filters[7]); - parseCommaSepArgs(argc, argv, "dt-sce=", p->filters[7]); - // clang-format on - - int intRes = 0; - String strRes; - -#define DOCTEST_PARSE_AS_BOOL_OR_FLAG(name, sname, var, default) \ - if(parseIntOption(argc, argv, DOCTEST_STR_CONCAT_TOSTR(name, =), option_bool, intRes) || \ - parseIntOption(argc, argv, DOCTEST_STR_CONCAT_TOSTR(sname, =), option_bool, intRes)) \ - p->var = !!intRes; \ - else if(parseFlag(argc, argv, #name) || parseFlag(argc, argv, #sname)) \ - p->var = true; \ - else if(withDefaults) \ - p->var = default - -#define DOCTEST_PARSE_INT_OPTION(name, sname, var, default) \ - if(parseIntOption(argc, argv, DOCTEST_STR_CONCAT_TOSTR(name, =), option_int, intRes) || \ - parseIntOption(argc, argv, DOCTEST_STR_CONCAT_TOSTR(sname, =), option_int, intRes)) \ - p->var = intRes; \ - else if(withDefaults) \ - p->var = default - -#define DOCTEST_PARSE_STR_OPTION(name, sname, var, default) \ - if(parseOption(argc, argv, DOCTEST_STR_CONCAT_TOSTR(name, =), strRes, default) || \ - parseOption(argc, argv, DOCTEST_STR_CONCAT_TOSTR(sname, =), strRes, default) || \ - withDefaults) \ - p->var = strRes - - // clang-format off - DOCTEST_PARSE_STR_OPTION(dt-order-by, dt-ob, order_by, "file"); - DOCTEST_PARSE_INT_OPTION(dt-rand-seed, dt-rs, rand_seed, 0); - - DOCTEST_PARSE_INT_OPTION(dt-first, dt-f, first, 1); - DOCTEST_PARSE_INT_OPTION(dt-last, dt-l, last, 0); - - DOCTEST_PARSE_INT_OPTION(dt-abort-after, dt-aa, abort_after, 0); - DOCTEST_PARSE_INT_OPTION(dt-subcase-filter-levels, dt-scfl, subcase_filter_levels, 2000000000); - - DOCTEST_PARSE_AS_BOOL_OR_FLAG(dt-success, dt-s, success, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG(dt-case-sensitive, dt-cs, case_sensitive, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG(dt-exit, dt-e, exit, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG(dt-duration, dt-d, duration, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG(dt-no-throw, dt-nt, no_throw, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG(dt-no-exitcode, dt-ne, no_exitcode, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG(dt-no-run, dt-nr, no_run, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG(dt-no-version, dt-nv, no_version, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG(dt-no-colors, dt-nc, no_colors, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG(dt-force-colors, dt-fc, force_colors, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG(dt-no-breaks, dt-nb, no_breaks, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG(dt-no-skip, dt-ns, no_skip, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG(dt-no-path-filenames, dt-npf, no_path_in_filenames, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG(dt-no-line-numbers, dt-nln, no_line_numbers, false); - DOCTEST_PARSE_AS_BOOL_OR_FLAG(dt-no-skipped-summary, dt-nss, no_skipped_summary, false); -// clang-format on - -#undef DOCTEST_PARSE_STR_OPTION -#undef DOCTEST_PARSE_INT_OPTION -#undef DOCTEST_PARSE_AS_BOOL_OR_FLAG - - if(withDefaults) { - p->help = false; - p->version = false; - p->count = false; - p->list_test_cases = false; - p->list_test_suites = false; - } - if(parseFlag(argc, argv, "dt-help") || parseFlag(argc, argv, "dt-h") || - parseFlag(argc, argv, "dt-?")) { - p->help = true; - p->exit = true; - } - if(parseFlag(argc, argv, "dt-version") || parseFlag(argc, argv, "dt-v")) { - p->version = true; - p->exit = true; - } - if(parseFlag(argc, argv, "dt-count") || parseFlag(argc, argv, "dt-c")) { - p->count = true; - p->exit = true; - } - if(parseFlag(argc, argv, "dt-list-test-cases") || parseFlag(argc, argv, "dt-ltc")) { - p->list_test_cases = true; - p->exit = true; - } - if(parseFlag(argc, argv, "dt-list-test-suites") || parseFlag(argc, argv, "dt-lts")) { - p->list_test_suites = true; - p->exit = true; - } -} - -// allows the user to add procedurally to the filters from the command line -void Context::addFilter(const char* filter, const char* value) { setOption(filter, value); } - -// allows the user to clear all filters from the command line -void Context::clearFilters() { - for(unsigned i = 0; i < p->filters.size(); ++i) - p->filters[i].clear(); -} - -// allows the user to override procedurally the int/bool options from the command line -void Context::setOption(const char* option, int value) { - setOption(option, toString(value).c_str()); -} - -// allows the user to override procedurally the string options from the command line -void Context::setOption(const char* option, const char* value) { - String argv = String("-") + option + "=" + value; - const char* lvalue = argv.c_str(); - parseArgs(1, &lvalue); -} - -// users should query this in their main() and exit the program if true -bool Context::shouldExit() { return p->exit; } - -// the main function that does all the filtering and test running -int Context::run() { - using namespace detail; - - Color::init(); - - contextState = p; - p->resetRunData(); - - // handle version, help and no_run - if(p->no_run || p->version || p->help) { - if(p->version) - printVersion(); - if(p->help) - printHelp(); - - contextState = 0; - - return EXIT_SUCCESS; - } - - printVersion(); - DOCTEST_PRINTF_COLORED("[doctest] ", Color::Cyan); - std::printf("run with \"--help\" for options\n"); - - unsigned i = 0; // counter used for loops - here for VC6 - - std::set& registeredTests = getRegisteredTests(); - - std::vector testArray; - for(std::set::iterator it = registeredTests.begin(); it != registeredTests.end(); - ++it) - testArray.push_back(&(*it)); - - // sort the collected records - if(!testArray.empty()) { - if(p->order_by.compare("file", true) == 0) { - std::qsort(&testArray[0], testArray.size(), sizeof(TestCase*), fileOrderComparator); - } else if(p->order_by.compare("suite", true) == 0) { - std::qsort(&testArray[0], testArray.size(), sizeof(TestCase*), suiteOrderComparator); - } else if(p->order_by.compare("name", true) == 0) { - std::qsort(&testArray[0], testArray.size(), sizeof(TestCase*), nameOrderComparator); - } else if(p->order_by.compare("rand", true) == 0) { - std::srand(p->rand_seed); - - // random_shuffle implementation - const TestCase** first = &testArray[0]; - for(i = testArray.size() - 1; i > 0; --i) { - int idxToSwap = std::rand() % (i + 1); // NOLINT - - const TestCase* temp = first[i]; - - first[i] = first[idxToSwap]; - first[idxToSwap] = temp; - } - } - } - - if(p->list_test_cases) { - DOCTEST_PRINTF_COLORED("[doctest] ", Color::Cyan); - std::printf("listing all test case names\n"); - DOCTEST_PRINTF_COLORED(getSeparator(), Color::Yellow); - } - - std::set testSuitesPassingFilters; - if(p->list_test_suites) { - DOCTEST_PRINTF_COLORED("[doctest] ", Color::Cyan); - std::printf("listing all test suites\n"); - DOCTEST_PRINTF_COLORED(getSeparator(), Color::Yellow); - } - - // invoke the registered functions if they match the filter criteria (or just count them) - for(i = 0; i < testArray.size(); i++) { - const TestCase& data = *testArray[i]; - - if(data.m_skip && !p->no_skip) - continue; - - if(!matchesAny(data.m_file, p->filters[0], 1, p->case_sensitive)) - continue; - if(matchesAny(data.m_file, p->filters[1], 0, p->case_sensitive)) - continue; - if(!matchesAny(data.m_test_suite, p->filters[2], 1, p->case_sensitive)) - continue; - if(matchesAny(data.m_test_suite, p->filters[3], 0, p->case_sensitive)) - continue; - if(!matchesAny(data.m_name, p->filters[4], 1, p->case_sensitive)) - continue; - if(matchesAny(data.m_name, p->filters[5], 0, p->case_sensitive)) - continue; - - p->numTestsPassingFilters++; - - // do not execute the test if we are to only count the number of filter passing tests - if(p->count) - continue; - - // print the name of the test and don't execute it - if(p->list_test_cases) { - std::printf("%s\n", data.m_name); - continue; - } - - // print the name of the test suite if not done already and don't execute it - if(p->list_test_suites) { - if((testSuitesPassingFilters.count(data.m_test_suite) == 0) && - data.m_test_suite[0] != '\0') { - std::printf("%s\n", data.m_test_suite); - testSuitesPassingFilters.insert(data.m_test_suite); - p->numTestSuitesPassingFilters++; - } - continue; - } - - // skip the test if it is not in the execution range - if((p->last < p->numTestsPassingFilters && p->first <= p->last) || - (p->first > p->numTestsPassingFilters)) - continue; - - // execute the test if it passes all the filtering - { - p->currentTest = &data; - - bool failed = false; - p->hasLoggedCurrentTestStart = false; - p->numFailedAssertionsForCurrentTestcase = 0; - p->subcasesPassed.clear(); - double duration = 0; - Timer timer; - timer.start(); - do { - // if the start has been logged from a previous iteration of this loop - if(p->hasLoggedCurrentTestStart) - logTestEnd(); - p->hasLoggedCurrentTestStart = false; - - // if logging successful tests - force the start log - if(p->success) - DOCTEST_LOG_START(); - - // reset the assertion state - p->numAssertionsForCurrentTestcase = 0; - p->hasCurrentTestFailed = false; - - // reset some of the fields for subcases (except for the set of fully passed ones) - p->subcasesHasSkipped = false; - p->subcasesCurrentLevel = 0; - p->subcasesEnteredLevels.clear(); - - // reset stuff for logging with INFO() - p->exceptionalContexts.clear(); - -// execute the test -#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS - try { -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS - FatalConditionHandler fatalConditionHandler; // Handle signals - data.m_test(); - fatalConditionHandler.reset(); - if(contextState->hasCurrentTestFailed) - failed = true; -#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS - } catch(const TestFailureException&) { failed = true; } catch(...) { - DOCTEST_LOG_START(); - logTestException(translateActiveException()); - failed = true; - } -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS - - p->numAssertions += p->numAssertionsForCurrentTestcase; - - // exit this loop if enough assertions have failed - if(p->abort_after > 0 && p->numFailedAssertions >= p->abort_after) { - p->subcasesHasSkipped = false; - DOCTEST_PRINTF_COLORED("Aborting - too many failed asserts!\n", Color::Red); - } - - } while(p->subcasesHasSkipped == true); - - duration = timer.getElapsedSeconds(); - - if(Approx(p->currentTest->m_timeout).epsilon(DBL_EPSILON) != 0 && - Approx(duration).epsilon(DBL_EPSILON) > p->currentTest->m_timeout) { - failed = true; - DOCTEST_LOG_START(); - char msg[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - DOCTEST_SNPRINTF(msg, DOCTEST_COUNTOF(msg), - "Test case exceeded time limit of %.6f!\n", - p->currentTest->m_timeout); - DOCTEST_PRINTF_COLORED(msg, Color::Red); - } - - if(p->duration) { - char msg[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - DOCTEST_SNPRINTF(msg, DOCTEST_COUNTOF(msg), "%.6f s: %s\n", duration, - p->currentTest->m_name); - DOCTEST_PRINTF_COLORED(msg, Color::None); - } - - if(data.m_should_fail) { - DOCTEST_LOG_START(); - if(failed) { - failed = false; - DOCTEST_PRINTF_COLORED("Failed as expected so marking it as not failed\n", - Color::Yellow); - } else { - failed = true; - DOCTEST_PRINTF_COLORED("Should have failed but didn't! Marking it as failed!\n", - Color::Red); - } - } else if(failed && data.m_may_fail) { - DOCTEST_LOG_START(); - failed = false; - DOCTEST_PRINTF_COLORED("Allowed to fail so marking it as not failed\n", - Color::Yellow); - } else if(data.m_expected_failures > 0) { - DOCTEST_LOG_START(); - char msg[DOCTEST_SNPRINTF_BUFFER_LENGTH]; - if(p->numFailedAssertionsForCurrentTestcase == data.m_expected_failures) { - failed = false; - DOCTEST_SNPRINTF( - msg, DOCTEST_COUNTOF(msg), - "Failed exactly %d times as expected so marking it as not failed!\n", - data.m_expected_failures); - DOCTEST_PRINTF_COLORED(msg, Color::Yellow); - } else { - failed = true; - DOCTEST_SNPRINTF(msg, DOCTEST_COUNTOF(msg), - "Didn't fail exactly %d times so marking it as failed!\n", - data.m_expected_failures); - DOCTEST_PRINTF_COLORED(msg, Color::Red); - } - } - - if(p->hasLoggedCurrentTestStart) - logTestEnd(); - - if(failed) // if any subcase has failed - the whole test case has failed - p->numFailed++; - - // stop executing tests if enough assertions have failed - if(p->abort_after > 0 && p->numFailedAssertions >= p->abort_after) - break; - } - } - - printSummary(); - - contextState = 0; - - if(p->numFailed && !p->no_exitcode) - return EXIT_FAILURE; - return EXIT_SUCCESS; -} -} // namespace doctest - -#endif // DOCTEST_CONFIG_DISABLE - -#ifdef DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN -int main(int argc, char** argv) { return doctest::Context(argc, argv).run(); } -#endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN - -#endif // DOCTEST_LIBRARY_IMPLEMENTATION -#endif // DOCTEST_CONFIG_IMPLEMENT - -#if defined(__clang__) -#pragma clang diagnostic pop -#endif // __clang__ - -#if defined(__GNUC__) && !defined(__clang__) -#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 6) -#pragma GCC diagnostic pop -#endif // > gcc 4.6 -#endif // __GNUC__ - -#ifdef _MSC_VER -#pragma warning(pop) -#endif // _MSC_VER From 615ed316352c544d6731a9d4cbef73ccba962b94 Mon Sep 17 00:00:00 2001 From: Pino Toscano Date: Sun, 30 Jul 2023 07:49:35 +0200 Subject: [PATCH 087/104] nix: disable tests & examples - the tests are not run or even built - the examples are not installed so it is better to simply disable them; this also avoids requiring packages needed only for them. --- default.nix | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/default.nix b/default.nix index c0ff997b..b2ab20d0 100644 --- a/default.nix +++ b/default.nix @@ -24,6 +24,10 @@ stdenv.mkDerivation rec { nativeBuildInputs = [ cmake ]; dontBuild = true; dontUseCmakeBuildDir = true; + cmakeFlags = [ + "-Dimmer_BUILD_TESTS=OFF" + "-Dimmer_BUILD_EXAMPLES=OFF" + ]; meta = { homepage = "https://github.com/arximboldi/immer"; description = "Postmodern immutable data structures for C++"; From ac49b71e4213b7f4d4c7e3427b7e3d3fd7c03a4d Mon Sep 17 00:00:00 2001 From: Pino Toscano Date: Sat, 29 Jul 2023 08:55:24 +0200 Subject: [PATCH 088/104] Switch to system Catch2 In case the tests are enabled, require a system installed version of Catch2: - find it using its CMake config file - use the Catch2::Catch2 CMake target - switch the includes to the canonical form Also adapt the Nix configuration for this. --- CMakeLists.txt | 2 ++ shell.nix | 1 + test/CMakeLists.txt | 2 +- test/algorithm.cpp | 2 +- test/atom/generic.ipp | 2 +- test/box/generic.ipp | 2 +- test/box/recursive.cpp | 2 +- test/box/vector-of-boxes-transient.cpp | 2 +- test/detail/type_traits.cpp | 2 +- test/experimental/dvektor.cpp | 2 +- test/flex_vector/fuzzed-0.cpp | 2 +- test/flex_vector/fuzzed-1.cpp | 2 +- test/flex_vector/fuzzed-2.cpp | 2 +- test/flex_vector/fuzzed-3.cpp | 2 +- test/flex_vector/fuzzed-4.cpp | 2 +- test/flex_vector/generic.ipp | 2 +- test/flex_vector/issue-45.cpp | 2 +- test/flex_vector_transient/generic.ipp | 2 +- test/map/generic.ipp | 2 +- test/map/issue-56.cpp | 2 +- test/map_transient/generic.ipp | 2 +- test/memory/heaps.cpp | 2 +- test/memory/refcounts.cpp | 2 +- test/oss-fuzz/array-0.cpp | 2 +- test/oss-fuzz/array-gc-0.cpp | 2 +- test/oss-fuzz/flex-vector-0.cpp | 2 +- test/oss-fuzz/flex-vector-bo-0.cpp | 2 +- test/oss-fuzz/flex-vector-gc-0.cpp | 2 +- test/oss-fuzz/map-gc-0.cpp | 2 +- test/oss-fuzz/map-st-0.cpp | 2 +- test/oss-fuzz/map-st-1.cpp | 2 +- test/oss-fuzz/map-st-2.cpp | 2 +- test/oss-fuzz/map-st-str-0.cpp | 2 +- test/oss-fuzz/set-gc-0.cpp | 2 +- test/oss-fuzz/set-gc-1.cpp | 2 +- test/oss-fuzz/set-st-0.cpp | 2 +- test/oss-fuzz/set-st-str-0.cpp | 2 +- test/set/generic.ipp | 2 +- test/set_transient/generic.ipp | 2 +- test/table/generic.ipp | 2 +- test/table_transient/generic.ipp | 2 +- test/vector/generic.ipp | 2 +- test/vector/issue-177.cpp | 2 +- test/vector/issue-46.cpp | 2 +- test/vector_transient/generic.ipp | 2 +- 45 files changed, 46 insertions(+), 43 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ab73bac..90ec3d93 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -139,6 +139,8 @@ endif() if (immer_BUILD_TESTS) enable_testing() + find_package(Catch2 REQUIRED) + add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} diff --git a/shell.nix b/shell.nix index 2879431b..1dfbe1eb 100644 --- a/shell.nix +++ b/shell.nix @@ -49,6 +49,7 @@ tc.stdenv.mkDerivation rec { buildInputs = [ tc.cc git + catch2 cmake pkgconfig ninja diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 60584c4f..7fec6adb 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -17,6 +17,6 @@ foreach(_file IN LISTS immer_unit_tests) target_compile_definitions(${_target} PUBLIC -DIMMER_OSS_FUZZ_DATA_PATH="${CMAKE_CURRENT_SOURCE_DIR}/oss-fuzz/data" CATCH_CONFIG_MAIN) - target_link_libraries(${_target} PUBLIC immer-dev) + target_link_libraries(${_target} PUBLIC immer-dev Catch2::Catch2) add_test("test/${_output}" ${_output}) endforeach() diff --git a/test/algorithm.cpp b/test/algorithm.cpp index de925209..6a785dbd 100644 --- a/test/algorithm.cpp +++ b/test/algorithm.cpp @@ -7,7 +7,7 @@ #include -#include +#include struct thing { diff --git a/test/atom/generic.ipp b/test/atom/generic.ipp index 21ff74d7..648da315 100644 --- a/test/atom/generic.ipp +++ b/test/atom/generic.ipp @@ -10,7 +10,7 @@ #error "define the box template to use in ATOM_T" #endif -#include +#include template using BOX_T = typename ATOM_T::box_type; diff --git a/test/box/generic.ipp b/test/box/generic.ipp index e221cdc8..72ab9c8b 100644 --- a/test/box/generic.ipp +++ b/test/box/generic.ipp @@ -10,7 +10,7 @@ #error "define the box template to use in BOX_T" #endif -#include +#include TEST_CASE("construction and copy") { diff --git a/test/box/recursive.cpp b/test/box/recursive.cpp index 4c8fce07..46d12043 100644 --- a/test/box/recursive.cpp +++ b/test/box/recursive.cpp @@ -13,7 +13,7 @@ #include #include -#include +#include struct rec_vec { diff --git a/test/box/vector-of-boxes-transient.cpp b/test/box/vector-of-boxes-transient.cpp index 623c8ba3..399be547 100644 --- a/test/box/vector-of-boxes-transient.cpp +++ b/test/box/vector-of-boxes-transient.cpp @@ -10,7 +10,7 @@ #include #include -#include +#include TEST_CASE("issue-33") { diff --git a/test/detail/type_traits.cpp b/test/detail/type_traits.cpp index 8809418c..632a25c9 100644 --- a/test/detail/type_traits.cpp +++ b/test/detail/type_traits.cpp @@ -6,7 +6,7 @@ // See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt // -#include +#include #include #include #include diff --git a/test/experimental/dvektor.cpp b/test/experimental/dvektor.cpp index f3b0a153..ffdd2e0f 100644 --- a/test/experimental/dvektor.cpp +++ b/test/experimental/dvektor.cpp @@ -15,7 +15,7 @@ #include #include -#include +#include using namespace immer; diff --git a/test/flex_vector/fuzzed-0.cpp b/test/flex_vector/fuzzed-0.cpp index 165ac1b6..ed0c5a66 100644 --- a/test/flex_vector/fuzzed-0.cpp +++ b/test/flex_vector/fuzzed-0.cpp @@ -8,7 +8,7 @@ #include "extra/fuzzer/fuzzer_input.hpp" #include -#include +#include #include #include diff --git a/test/flex_vector/fuzzed-1.cpp b/test/flex_vector/fuzzed-1.cpp index 8bd47a19..162c78eb 100644 --- a/test/flex_vector/fuzzed-1.cpp +++ b/test/flex_vector/fuzzed-1.cpp @@ -8,7 +8,7 @@ #include "extra/fuzzer/fuzzer_input.hpp" #include -#include +#include #include #include diff --git a/test/flex_vector/fuzzed-2.cpp b/test/flex_vector/fuzzed-2.cpp index bdfd1203..a058ad61 100644 --- a/test/flex_vector/fuzzed-2.cpp +++ b/test/flex_vector/fuzzed-2.cpp @@ -8,7 +8,7 @@ #include "extra/fuzzer/fuzzer_input.hpp" #include -#include +#include #include #include diff --git a/test/flex_vector/fuzzed-3.cpp b/test/flex_vector/fuzzed-3.cpp index 6fce537a..422497d3 100644 --- a/test/flex_vector/fuzzed-3.cpp +++ b/test/flex_vector/fuzzed-3.cpp @@ -8,7 +8,7 @@ #include "extra/fuzzer/fuzzer_input.hpp" #include -#include +#include #include #include diff --git a/test/flex_vector/fuzzed-4.cpp b/test/flex_vector/fuzzed-4.cpp index 28e2cf04..0a726bef 100644 --- a/test/flex_vector/fuzzed-4.cpp +++ b/test/flex_vector/fuzzed-4.cpp @@ -8,7 +8,7 @@ #include "extra/fuzzer/fuzzer_input.hpp" #include -#include +#include #include #include #include diff --git a/test/flex_vector/generic.ipp b/test/flex_vector/generic.ipp index d6955dc8..f01f48ca 100644 --- a/test/flex_vector/generic.ipp +++ b/test/flex_vector/generic.ipp @@ -14,7 +14,7 @@ #include #include -#include +#include #include #include diff --git a/test/flex_vector/issue-45.cpp b/test/flex_vector/issue-45.cpp index 76bd77b2..3ff670e7 100644 --- a/test/flex_vector/issue-45.cpp +++ b/test/flex_vector/issue-45.cpp @@ -13,7 +13,7 @@ #include #include -#include +#include #if IMMER_CXX_STANDARD >= 17 diff --git a/test/flex_vector_transient/generic.ipp b/test/flex_vector_transient/generic.ipp index bcaaa3a0..200e7d0a 100644 --- a/test/flex_vector_transient/generic.ipp +++ b/test/flex_vector_transient/generic.ipp @@ -14,7 +14,7 @@ #include #include -#include +#include #include #include diff --git a/test/map/generic.ipp b/test/map/generic.ipp index a049fefc..e9b6cf58 100644 --- a/test/map/generic.ipp +++ b/test/map/generic.ipp @@ -18,7 +18,7 @@ #include "test/dada.hpp" #include "test/util.hpp" -#include +#include #include #include diff --git a/test/map/issue-56.cpp b/test/map/issue-56.cpp index 447c623d..8658121c 100644 --- a/test/map/issue-56.cpp +++ b/test/map/issue-56.cpp @@ -13,7 +13,7 @@ #include #include -#include +#include TEST_CASE("const map") { diff --git a/test/map_transient/generic.ipp b/test/map_transient/generic.ipp index 097eaddc..5d0a1540 100644 --- a/test/map_transient/generic.ipp +++ b/test/map_transient/generic.ipp @@ -8,7 +8,7 @@ #include "test/util.hpp" -#include +#include #ifndef MAP_T #error "define the map template to use in MAP_T" diff --git a/test/memory/heaps.cpp b/test/memory/heaps.cpp index 94af2be2..a43c99b7 100644 --- a/test/memory/heaps.cpp +++ b/test/memory/heaps.cpp @@ -12,7 +12,7 @@ #include #include -#include +#include #include void do_stuff_to(void* buf, std::size_t size) diff --git a/test/memory/refcounts.cpp b/test/memory/refcounts.cpp index 3b05da02..f8a82e82 100644 --- a/test/memory/refcounts.cpp +++ b/test/memory/refcounts.cpp @@ -10,7 +10,7 @@ #include #include -#include +#include TEST_CASE("no refcount has no data") { diff --git a/test/oss-fuzz/array-0.cpp b/test/oss-fuzz/array-0.cpp index d0c2962a..162b0154 100644 --- a/test/oss-fuzz/array-0.cpp +++ b/test/oss-fuzz/array-0.cpp @@ -10,7 +10,7 @@ #include -#include +#include namespace { diff --git a/test/oss-fuzz/array-gc-0.cpp b/test/oss-fuzz/array-gc-0.cpp index 40a29331..74806653 100644 --- a/test/oss-fuzz/array-gc-0.cpp +++ b/test/oss-fuzz/array-gc-0.cpp @@ -15,7 +15,7 @@ #include #include -#include +#include using gc_memory = immer::memory_policy, immer::no_refcount_policy, diff --git a/test/oss-fuzz/flex-vector-0.cpp b/test/oss-fuzz/flex-vector-0.cpp index c5a8d18b..2ce52917 100644 --- a/test/oss-fuzz/flex-vector-0.cpp +++ b/test/oss-fuzz/flex-vector-0.cpp @@ -12,7 +12,7 @@ #include #include -#include +#include #define IMMER_FUZZED_TRACE_ENABLE 0 diff --git a/test/oss-fuzz/flex-vector-bo-0.cpp b/test/oss-fuzz/flex-vector-bo-0.cpp index 6dc7be44..f9526cac 100644 --- a/test/oss-fuzz/flex-vector-bo-0.cpp +++ b/test/oss-fuzz/flex-vector-bo-0.cpp @@ -12,7 +12,7 @@ #include #include -#include +#include #define IMMER_FUZZED_TRACE_ENABLE 1 diff --git a/test/oss-fuzz/flex-vector-gc-0.cpp b/test/oss-fuzz/flex-vector-gc-0.cpp index 1a669c16..0b9e6d6a 100644 --- a/test/oss-fuzz/flex-vector-gc-0.cpp +++ b/test/oss-fuzz/flex-vector-gc-0.cpp @@ -13,7 +13,7 @@ #include #include -#include +#include #define IMMER_FUZZED_TRACE_ENABLE 0 diff --git a/test/oss-fuzz/map-gc-0.cpp b/test/oss-fuzz/map-gc-0.cpp index a97baf7d..fd0540da 100644 --- a/test/oss-fuzz/map-gc-0.cpp +++ b/test/oss-fuzz/map-gc-0.cpp @@ -14,7 +14,7 @@ #include #include -#include +#include using gc_memory = immer::memory_policy, immer::no_refcount_policy, diff --git a/test/oss-fuzz/map-st-0.cpp b/test/oss-fuzz/map-st-0.cpp index 9ae292f4..59aa0c6f 100644 --- a/test/oss-fuzz/map-st-0.cpp +++ b/test/oss-fuzz/map-st-0.cpp @@ -13,7 +13,7 @@ #include #include -#include +#include using st_memory = immer::memory_policy, immer::unsafe_refcount_policy, diff --git a/test/oss-fuzz/map-st-1.cpp b/test/oss-fuzz/map-st-1.cpp index a692dac5..4041ea96 100644 --- a/test/oss-fuzz/map-st-1.cpp +++ b/test/oss-fuzz/map-st-1.cpp @@ -14,7 +14,7 @@ #include #include -#include +#include using st_memory = immer::memory_policy, immer::unsafe_refcount_policy, diff --git a/test/oss-fuzz/map-st-2.cpp b/test/oss-fuzz/map-st-2.cpp index af022e76..782e2af4 100644 --- a/test/oss-fuzz/map-st-2.cpp +++ b/test/oss-fuzz/map-st-2.cpp @@ -14,7 +14,7 @@ #include #include -#include +#include #define IMMER_FUZZED_TRACE_ENABLE 0 diff --git a/test/oss-fuzz/map-st-str-0.cpp b/test/oss-fuzz/map-st-str-0.cpp index 3e85308a..fdf5204c 100644 --- a/test/oss-fuzz/map-st-str-0.cpp +++ b/test/oss-fuzz/map-st-str-0.cpp @@ -16,7 +16,7 @@ #include -#include +#include #define IMMER_FUZZED_TRACE_ENABLE 1 diff --git a/test/oss-fuzz/set-gc-0.cpp b/test/oss-fuzz/set-gc-0.cpp index 7e88b0bb..c6abebcc 100644 --- a/test/oss-fuzz/set-gc-0.cpp +++ b/test/oss-fuzz/set-gc-0.cpp @@ -14,7 +14,7 @@ #include #include -#include +#include using gc_memory = immer::memory_policy, immer::no_refcount_policy, diff --git a/test/oss-fuzz/set-gc-1.cpp b/test/oss-fuzz/set-gc-1.cpp index daee36b5..d07901a2 100644 --- a/test/oss-fuzz/set-gc-1.cpp +++ b/test/oss-fuzz/set-gc-1.cpp @@ -16,7 +16,7 @@ #include -#include +#include #define IMMER_FUZZED_TRACE_ENABLE 0 diff --git a/test/oss-fuzz/set-st-0.cpp b/test/oss-fuzz/set-st-0.cpp index 87bd2aeb..4e56f211 100644 --- a/test/oss-fuzz/set-st-0.cpp +++ b/test/oss-fuzz/set-st-0.cpp @@ -16,7 +16,7 @@ #include -#include +#include using st_memory = immer::memory_policy, immer::unsafe_refcount_policy, diff --git a/test/oss-fuzz/set-st-str-0.cpp b/test/oss-fuzz/set-st-str-0.cpp index b4060990..538abf70 100644 --- a/test/oss-fuzz/set-st-str-0.cpp +++ b/test/oss-fuzz/set-st-str-0.cpp @@ -16,7 +16,7 @@ #include -#include +#include namespace { diff --git a/test/set/generic.ipp b/test/set/generic.ipp index 2a5e6f35..d8bde725 100644 --- a/test/set/generic.ipp +++ b/test/set/generic.ipp @@ -18,7 +18,7 @@ #include #include -#include +#include #include #include diff --git a/test/set_transient/generic.ipp b/test/set_transient/generic.ipp index 6fa0ecfa..29f7b0b9 100644 --- a/test/set_transient/generic.ipp +++ b/test/set_transient/generic.ipp @@ -8,7 +8,7 @@ #include "test/util.hpp" -#include +#include #ifndef SET_T #error "define the set template to use in SET_T" diff --git a/test/table/generic.ipp b/test/table/generic.ipp index 144da7ae..f943e51f 100644 --- a/test/table/generic.ipp +++ b/test/table/generic.ipp @@ -17,7 +17,7 @@ #include "test/dada.hpp" #include "test/util.hpp" -#include +#include #include #include diff --git a/test/table_transient/generic.ipp b/test/table_transient/generic.ipp index fd518ce2..73725be5 100644 --- a/test/table_transient/generic.ipp +++ b/test/table_transient/generic.ipp @@ -8,7 +8,7 @@ #include "test/util.hpp" -#include +#include #ifndef SETUP_T #error "define the table types via SETUP_T macro" diff --git a/test/vector/generic.ipp b/test/vector/generic.ipp index 98cea994..ccce07ae 100644 --- a/test/vector/generic.ipp +++ b/test/vector/generic.ipp @@ -12,7 +12,7 @@ #include #include -#include +#include #include #include diff --git a/test/vector/issue-177.cpp b/test/vector/issue-177.cpp index 09dff262..ac953d86 100644 --- a/test/vector/issue-177.cpp +++ b/test/vector/issue-177.cpp @@ -13,7 +13,7 @@ #include #include -#include +#include struct object; diff --git a/test/vector/issue-46.cpp b/test/vector/issue-46.cpp index 6475be21..0032b71f 100644 --- a/test/vector/issue-46.cpp +++ b/test/vector/issue-46.cpp @@ -13,7 +13,7 @@ #include #include -#include +#include TEST_CASE("operator==() may return bad result") { diff --git a/test/vector_transient/generic.ipp b/test/vector_transient/generic.ipp index a889380d..6b3e6dad 100644 --- a/test/vector_transient/generic.ipp +++ b/test/vector_transient/generic.ipp @@ -10,7 +10,7 @@ #include "test/transient_tester.hpp" #include "test/util.hpp" -#include +#include #ifndef VECTOR_T #error "define the vector template to use in VECTOR_T" From eec85078f21ef43616914efd8e74cae3f5386074 Mon Sep 17 00:00:00 2001 From: Pino Toscano Date: Sat, 29 Jul 2023 18:37:41 +0200 Subject: [PATCH 089/104] Drop unused copy of Catch2 Now that the system version of Catch2 is used, the embedded copy is no more needed. --- tools/include/catch.hpp | 17959 -------------------------------------- 1 file changed, 17959 deletions(-) delete mode 100644 tools/include/catch.hpp diff --git a/tools/include/catch.hpp b/tools/include/catch.hpp deleted file mode 100644 index 7e706f94..00000000 --- a/tools/include/catch.hpp +++ /dev/null @@ -1,17959 +0,0 @@ -/* - * Catch v2.13.7 - * Generated: 2021-07-28 20:29:27.753164 - * ---------------------------------------------------------- - * This file has been merged from multiple headers. Please don't edit it directly - * Copyright (c) 2021 Two Blue Cubes Ltd. All rights reserved. - * - * Distributed under the Boost Software License, Version 1.0. (See accompanying - * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) - */ -#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED -#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED -// start catch.hpp - - -#define CATCH_VERSION_MAJOR 2 -#define CATCH_VERSION_MINOR 13 -#define CATCH_VERSION_PATCH 7 - -#ifdef __clang__ -# pragma clang system_header -#elif defined __GNUC__ -# pragma GCC system_header -#endif - -// start catch_suppress_warnings.h - -#ifdef __clang__ -# ifdef __ICC // icpc defines the __clang__ macro -# pragma warning(push) -# pragma warning(disable: 161 1682) -# else // __ICC -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wpadded" -# pragma clang diagnostic ignored "-Wswitch-enum" -# pragma clang diagnostic ignored "-Wcovered-switch-default" -# endif -#elif defined __GNUC__ - // Because REQUIREs trigger GCC's -Wparentheses, and because still - // supported version of g++ have only buggy support for _Pragmas, - // Wparentheses have to be suppressed globally. -# pragma GCC diagnostic ignored "-Wparentheses" // See #674 for details - -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wunused-variable" -# pragma GCC diagnostic ignored "-Wpadded" -#endif -// end catch_suppress_warnings.h -#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) -# define CATCH_IMPL -# define CATCH_CONFIG_ALL_PARTS -#endif - -// In the impl file, we want to have access to all parts of the headers -// Can also be used to sanely support PCHs -#if defined(CATCH_CONFIG_ALL_PARTS) -# define CATCH_CONFIG_EXTERNAL_INTERFACES -# if defined(CATCH_CONFIG_DISABLE_MATCHERS) -# undef CATCH_CONFIG_DISABLE_MATCHERS -# endif -# if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) -# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER -# endif -#endif - -#if !defined(CATCH_CONFIG_IMPL_ONLY) -// start catch_platform.h - -// See e.g.: -// https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/TargetConditionals.h.auto.html -#ifdef __APPLE__ -# include -# if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || \ - (defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1) -# define CATCH_PLATFORM_MAC -# elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1) -# define CATCH_PLATFORM_IPHONE -# endif - -#elif defined(linux) || defined(__linux) || defined(__linux__) -# define CATCH_PLATFORM_LINUX - -#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) -# define CATCH_PLATFORM_WINDOWS -#endif - -// end catch_platform.h - -#ifdef CATCH_IMPL -# ifndef CLARA_CONFIG_MAIN -# define CLARA_CONFIG_MAIN_NOT_DEFINED -# define CLARA_CONFIG_MAIN -# endif -#endif - -// start catch_user_interfaces.h - -namespace Catch { - unsigned int rngSeed(); -} - -// end catch_user_interfaces.h -// start catch_tag_alias_autoregistrar.h - -// start catch_common.h - -// start catch_compiler_capabilities.h - -// Detect a number of compiler features - by compiler -// The following features are defined: -// -// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? -// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? -// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? -// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? -// **************** -// Note to maintainers: if new toggles are added please document them -// in configuration.md, too -// **************** - -// In general each macro has a _NO_ form -// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. -// Many features, at point of detection, define an _INTERNAL_ macro, so they -// can be combined, en-mass, with the _NO_ forms later. - -#ifdef __cplusplus - -# if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) -# define CATCH_CPP14_OR_GREATER -# endif - -# if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) -# define CATCH_CPP17_OR_GREATER -# endif - -#endif - -// Only GCC compiler should be used in this block, so other compilers trying to -// mask themselves as GCC should be ignored. -#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__) -# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) -# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) - -# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) - -#endif - -#if defined(__clang__) - -# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" ) -# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic pop" ) - -// As of this writing, IBM XL's implementation of __builtin_constant_p has a bug -// which results in calls to destructors being emitted for each temporary, -// without a matching initialization. In practice, this can result in something -// like `std::string::~string` being called on an uninitialized value. -// -// For example, this code will likely segfault under IBM XL: -// ``` -// REQUIRE(std::string("12") + "34" == "1234") -// ``` -// -// Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented. -# if !defined(__ibmxl__) && !defined(__CUDACC__) -# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg, hicpp-vararg) */ -# endif - -# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ - _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") - -# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) - -# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) - -# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) - -# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wunused-template\"" ) - -#endif // __clang__ - -//////////////////////////////////////////////////////////////////////////////// -// Assume that non-Windows platforms support posix signals by default -#if !defined(CATCH_PLATFORM_WINDOWS) - #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS -#endif - -//////////////////////////////////////////////////////////////////////////////// -// We know some environments not to support full POSIX signals -#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__) - #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS -#endif - -#ifdef __OS400__ -# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS -# define CATCH_CONFIG_COLOUR_NONE -#endif - -//////////////////////////////////////////////////////////////////////////////// -// Android somehow still does not support std::to_string -#if defined(__ANDROID__) -# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING -# define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE -#endif - -//////////////////////////////////////////////////////////////////////////////// -// Not all Windows environments support SEH properly -#if defined(__MINGW32__) -# define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH -#endif - -//////////////////////////////////////////////////////////////////////////////// -// PS4 -#if defined(__ORBIS__) -# define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE -#endif - -//////////////////////////////////////////////////////////////////////////////// -// Cygwin -#ifdef __CYGWIN__ - -// Required for some versions of Cygwin to declare gettimeofday -// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin -# define _BSD_SOURCE -// some versions of cygwin (most) do not support std::to_string. Use the libstd check. -// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 -# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ - && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) - -# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING - -# endif -#endif // __CYGWIN__ - -//////////////////////////////////////////////////////////////////////////////// -// Visual C++ -#if defined(_MSC_VER) - -# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) ) -# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION __pragma( warning(pop) ) - -// Universal Windows platform does not support SEH -// Or console colours (or console at all...) -# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) -# define CATCH_CONFIG_COLOUR_NONE -# else -# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH -# endif - -// MSVC traditional preprocessor needs some workaround for __VA_ARGS__ -// _MSVC_TRADITIONAL == 0 means new conformant preprocessor -// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor -# if !defined(__clang__) // Handle Clang masquerading for msvc -# if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) -# define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -# endif // MSVC_TRADITIONAL -# endif // __clang__ - -#endif // _MSC_VER - -#if defined(_REENTRANT) || defined(_MSC_VER) -// Enable async processing, as -pthread is specified or no additional linking is required -# define CATCH_INTERNAL_CONFIG_USE_ASYNC -#endif // _MSC_VER - -//////////////////////////////////////////////////////////////////////////////// -// Check if we are compiled with -fno-exceptions or equivalent -#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) -# define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED -#endif - -//////////////////////////////////////////////////////////////////////////////// -// DJGPP -#ifdef __DJGPP__ -# define CATCH_INTERNAL_CONFIG_NO_WCHAR -#endif // __DJGPP__ - -//////////////////////////////////////////////////////////////////////////////// -// Embarcadero C++Build -#if defined(__BORLANDC__) - #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN -#endif - -//////////////////////////////////////////////////////////////////////////////// - -// Use of __COUNTER__ is suppressed during code analysis in -// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly -// handled by it. -// Otherwise all supported compilers support COUNTER macro, -// but user still might want to turn it off -#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) - #define CATCH_INTERNAL_CONFIG_COUNTER -#endif - -//////////////////////////////////////////////////////////////////////////////// - -// RTX is a special version of Windows that is real time. -// This means that it is detected as Windows, but does not provide -// the same set of capabilities as real Windows does. -#if defined(UNDER_RTSS) || defined(RTX64_BUILD) - #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH - #define CATCH_INTERNAL_CONFIG_NO_ASYNC - #define CATCH_CONFIG_COLOUR_NONE -#endif - -#if !defined(_GLIBCXX_USE_C99_MATH_TR1) -#define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER -#endif - -// Various stdlib support checks that require __has_include -#if defined(__has_include) - // Check if string_view is available and usable - #if __has_include() && defined(CATCH_CPP17_OR_GREATER) - # define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW - #endif - - // Check if optional is available and usable - # if __has_include() && defined(CATCH_CPP17_OR_GREATER) - # define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL - # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) - - // Check if byte is available and usable - # if __has_include() && defined(CATCH_CPP17_OR_GREATER) - # include - # if defined(__cpp_lib_byte) && (__cpp_lib_byte > 0) - # define CATCH_INTERNAL_CONFIG_CPP17_BYTE - # endif - # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) - - // Check if variant is available and usable - # if __has_include() && defined(CATCH_CPP17_OR_GREATER) - # if defined(__clang__) && (__clang_major__ < 8) - // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 - // fix should be in clang 8, workaround in libstdc++ 8.2 - # include - # if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) - # define CATCH_CONFIG_NO_CPP17_VARIANT - # else - # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT - # endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) - # else - # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT - # endif // defined(__clang__) && (__clang_major__ < 8) - # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) -#endif // defined(__has_include) - -#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) -# define CATCH_CONFIG_COUNTER -#endif -#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) -# define CATCH_CONFIG_WINDOWS_SEH -#endif -// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. -#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) -# define CATCH_CONFIG_POSIX_SIGNALS -#endif -// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions. -#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) -# define CATCH_CONFIG_WCHAR -#endif - -#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) -# define CATCH_CONFIG_CPP11_TO_STRING -#endif - -#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) -# define CATCH_CONFIG_CPP17_OPTIONAL -#endif - -#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) -# define CATCH_CONFIG_CPP17_STRING_VIEW -#endif - -#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) -# define CATCH_CONFIG_CPP17_VARIANT -#endif - -#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) -# define CATCH_CONFIG_CPP17_BYTE -#endif - -#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) -# define CATCH_INTERNAL_CONFIG_NEW_CAPTURE -#endif - -#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) -# define CATCH_CONFIG_NEW_CAPTURE -#endif - -#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) -# define CATCH_CONFIG_DISABLE_EXCEPTIONS -#endif - -#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) -# define CATCH_CONFIG_POLYFILL_ISNAN -#endif - -#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) -# define CATCH_CONFIG_USE_ASYNC -#endif - -#if defined(CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_NO_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_ANDROID_LOGWRITE) -# define CATCH_CONFIG_ANDROID_LOGWRITE -#endif - -#if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) -# define CATCH_CONFIG_GLOBAL_NEXTAFTER -#endif - -// Even if we do not think the compiler has that warning, we still have -// to provide a macro that can be used by the code. -#if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION) -# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION -#endif -#if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION) -# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION -#endif -#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS -#endif -#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS -#endif -#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS -#endif -#if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS -#endif - -// The goal of this macro is to avoid evaluation of the arguments, but -// still have the compiler warn on problems inside... -#if !defined(CATCH_INTERNAL_IGNORE_BUT_WARN) -# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) -#endif - -#if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10) -# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS -#elif defined(__clang__) && (__clang_major__ < 5) -# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS -#endif - -#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS -#endif - -#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) -#define CATCH_TRY if ((true)) -#define CATCH_CATCH_ALL if ((false)) -#define CATCH_CATCH_ANON(type) if ((false)) -#else -#define CATCH_TRY try -#define CATCH_CATCH_ALL catch (...) -#define CATCH_CATCH_ANON(type) catch (type) -#endif - -#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) -#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#endif - -// end catch_compiler_capabilities.h -#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line -#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) -#ifdef CATCH_CONFIG_COUNTER -# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) -#else -# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) -#endif - -#include -#include -#include - -// We need a dummy global operator<< so we can bring it into Catch namespace later -struct Catch_global_namespace_dummy {}; -std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); - -namespace Catch { - - struct CaseSensitive { enum Choice { - Yes, - No - }; }; - - class NonCopyable { - NonCopyable( NonCopyable const& ) = delete; - NonCopyable( NonCopyable && ) = delete; - NonCopyable& operator = ( NonCopyable const& ) = delete; - NonCopyable& operator = ( NonCopyable && ) = delete; - - protected: - NonCopyable(); - virtual ~NonCopyable(); - }; - - struct SourceLineInfo { - - SourceLineInfo() = delete; - SourceLineInfo( char const* _file, std::size_t _line ) noexcept - : file( _file ), - line( _line ) - {} - - SourceLineInfo( SourceLineInfo const& other ) = default; - SourceLineInfo& operator = ( SourceLineInfo const& ) = default; - SourceLineInfo( SourceLineInfo&& ) noexcept = default; - SourceLineInfo& operator = ( SourceLineInfo&& ) noexcept = default; - - bool empty() const noexcept { return file[0] == '\0'; } - bool operator == ( SourceLineInfo const& other ) const noexcept; - bool operator < ( SourceLineInfo const& other ) const noexcept; - - char const* file; - std::size_t line; - }; - - std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); - - // Bring in operator<< from global namespace into Catch namespace - // This is necessary because the overload of operator<< above makes - // lookup stop at namespace Catch - using ::operator<<; - - // Use this in variadic streaming macros to allow - // >> +StreamEndStop - // as well as - // >> stuff +StreamEndStop - struct StreamEndStop { - std::string operator+() const; - }; - template - T const& operator + ( T const& value, StreamEndStop ) { - return value; - } -} - -#define CATCH_INTERNAL_LINEINFO \ - ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) - -// end catch_common.h -namespace Catch { - - struct RegistrarForTagAliases { - RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); - }; - -} // end namespace Catch - -#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION - -// end catch_tag_alias_autoregistrar.h -// start catch_test_registry.h - -// start catch_interfaces_testcase.h - -#include - -namespace Catch { - - class TestSpec; - - struct ITestInvoker { - virtual void invoke () const = 0; - virtual ~ITestInvoker(); - }; - - class TestCase; - struct IConfig; - - struct ITestCaseRegistry { - virtual ~ITestCaseRegistry(); - virtual std::vector const& getAllTests() const = 0; - virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; - }; - - bool isThrowSafe( TestCase const& testCase, IConfig const& config ); - bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); - std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); - std::vector const& getAllTestCasesSorted( IConfig const& config ); - -} - -// end catch_interfaces_testcase.h -// start catch_stringref.h - -#include -#include -#include -#include - -namespace Catch { - - /// A non-owning string class (similar to the forthcoming std::string_view) - /// Note that, because a StringRef may be a substring of another string, - /// it may not be null terminated. - class StringRef { - public: - using size_type = std::size_t; - using const_iterator = const char*; - - private: - static constexpr char const* const s_empty = ""; - - char const* m_start = s_empty; - size_type m_size = 0; - - public: // construction - constexpr StringRef() noexcept = default; - - StringRef( char const* rawChars ) noexcept; - - constexpr StringRef( char const* rawChars, size_type size ) noexcept - : m_start( rawChars ), - m_size( size ) - {} - - StringRef( std::string const& stdString ) noexcept - : m_start( stdString.c_str() ), - m_size( stdString.size() ) - {} - - explicit operator std::string() const { - return std::string(m_start, m_size); - } - - public: // operators - auto operator == ( StringRef const& other ) const noexcept -> bool; - auto operator != (StringRef const& other) const noexcept -> bool { - return !(*this == other); - } - - auto operator[] ( size_type index ) const noexcept -> char { - assert(index < m_size); - return m_start[index]; - } - - public: // named queries - constexpr auto empty() const noexcept -> bool { - return m_size == 0; - } - constexpr auto size() const noexcept -> size_type { - return m_size; - } - - // Returns the current start pointer. If the StringRef is not - // null-terminated, throws std::domain_exception - auto c_str() const -> char const*; - - public: // substrings and searches - // Returns a substring of [start, start + length). - // If start + length > size(), then the substring is [start, size()). - // If start > size(), then the substring is empty. - auto substr( size_type start, size_type length ) const noexcept -> StringRef; - - // Returns the current start pointer. May not be null-terminated. - auto data() const noexcept -> char const*; - - constexpr auto isNullTerminated() const noexcept -> bool { - return m_start[m_size] == '\0'; - } - - public: // iterators - constexpr const_iterator begin() const { return m_start; } - constexpr const_iterator end() const { return m_start + m_size; } - }; - - auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; - auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; - - constexpr auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { - return StringRef( rawChars, size ); - } -} // namespace Catch - -constexpr auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { - return Catch::StringRef( rawChars, size ); -} - -// end catch_stringref.h -// start catch_preprocessor.hpp - - -#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ -#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) -#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) -#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) -#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) -#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) - -#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ -// MSVC needs more evaluations -#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) -#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) -#else -#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) -#endif - -#define CATCH_REC_END(...) -#define CATCH_REC_OUT - -#define CATCH_EMPTY() -#define CATCH_DEFER(id) id CATCH_EMPTY() - -#define CATCH_REC_GET_END2() 0, CATCH_REC_END -#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 -#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 -#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT -#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) -#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) - -#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) -#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) -#define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) - -#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) -#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) -#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) - -// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, -// and passes userdata as the first parameter to each invocation, -// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) -#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) - -#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) - -#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) -#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ -#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ -#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF -#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) -#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ -#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) -#else -// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF -#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) -#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ -#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) -#endif - -#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ -#define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) - -#define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__) - -#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper()) -#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) -#else -#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper())) -#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) -#endif - -#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\ - CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__) - -#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) -#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) -#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) -#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) -#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) -#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) -#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _3, _4, _5, _6) -#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) -#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) -#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) -#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) - -#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N - -#define INTERNAL_CATCH_TYPE_GEN\ - template struct TypeList {};\ - template\ - constexpr auto get_wrapper() noexcept -> TypeList { return {}; }\ - template class...> struct TemplateTypeList{};\ - template class...Cs>\ - constexpr auto get_wrapper() noexcept -> TemplateTypeList { return {}; }\ - template\ - struct append;\ - template\ - struct rewrap;\ - template class, typename...>\ - struct create;\ - template class, typename>\ - struct convert;\ - \ - template \ - struct append { using type = T; };\ - template< template class L1, typename...E1, template class L2, typename...E2, typename...Rest>\ - struct append, L2, Rest...> { using type = typename append, Rest...>::type; };\ - template< template class L1, typename...E1, typename...Rest>\ - struct append, TypeList, Rest...> { using type = L1; };\ - \ - template< template class Container, template class List, typename...elems>\ - struct rewrap, List> { using type = TypeList>; };\ - template< template class Container, template class List, class...Elems, typename...Elements>\ - struct rewrap, List, Elements...> { using type = typename append>, typename rewrap, Elements...>::type>::type; };\ - \ - template