From c7fc73ee459045edabb99816658f14f32d23bf92 Mon Sep 17 00:00:00 2001 From: Jonathan Wakely Date: Thu, 25 Mar 2021 13:51:08 +0000 Subject: [PATCH] libstdc++: Allow seeding random engines in testsuite The testsuite utilities that use random numbers use a default-constructed mersenne_twister_engine, meaning the values are reproducable. This adds support for seeding them, controlledby an environment variable. Defining GLIBCXX_SEED_TEST_RNG=val in the environment will cause the engines to be seeded with atoi(val) if that is non-zero, or with a value read from std::random_device otherwise. Running with different seeds revealed some bugs in the tests, where a randomly selected iterator was past-the-end (which can't be erased), or where the randomly populated container was empty, and then we tried to remove elements from it unconditionally. libstdc++-v3/ChangeLog: * testsuite/util/exception/safety.h (setup_base::generate): Support seeding random engine. (erase_point, erase_range): Adjust range of random numbers to ensure dereferenceable iterators are used where required. (generation_prohibited::run): Do not try to erase from empty containers. * testsuite/util/testsuite_containergen.h (test_containers): Support seeding random engine. --- .../testsuite/util/exception/safety.h | 87 ++++++++++++------- .../testsuite/util/testsuite_containergen.h | 14 +++ 2 files changed, 72 insertions(+), 29 deletions(-) diff --git a/libstdc++-v3/testsuite/util/exception/safety.h b/libstdc++-v3/testsuite/util/exception/safety.h index 6c91e740e0d..2e5d8acae00 100644 --- a/libstdc++-v3/testsuite/util/exception/safety.h +++ b/libstdc++-v3/testsuite/util/exception/safety.h @@ -22,6 +22,8 @@ #include #include +#include // getenv, atoi +#include // printf, fflush // Container requirement testing. namespace __gnu_test @@ -33,27 +35,34 @@ namespace __gnu_test typedef std::uniform_int_distribution distribution_type; typedef std::mt19937 engine_type; + static engine_type + get_engine() + { + engine_type engine; + if (const char* v = std::getenv("GLIBCXX_SEED_TEST_RNG")) + { + // A single seed value is much smaller than the mt19937 state size, + // but we're not trying to be cryptographically secure here. + int s = std::atoi(v); + if (s == 0) + s = (int)std::random_device{}(); + std::printf("Using random seed %d\n", s); + std::fflush(stdout); + engine.seed((unsigned)s); + } + return engine; + } + // Return randomly generated integer on range [0, __max_size]. static size_type generate(size_type __max_size) { - // Make the generator static... - const engine_type engine; - const distribution_type distribution; - static auto generator = std::bind(distribution, engine, - std::placeholders::_1); + using param_type = typename distribution_type::param_type; - // ... but set the range for this particular invocation here. - const typename distribution_type::param_type p(0, __max_size); - size_type random = generator(p); - if (random < distribution.min() || random > distribution.max()) - std::__throw_out_of_range_fmt(__N("setup_base::generate\n" - "random number generated is: %zu " - "out of range [%zu, %zu]\n"), - (size_t)random, - (size_t)distribution.min(), - (size_t)distribution.max()); - return random; + // Make the engine and distribution static... + static engine_type engine = get_engine(); + static distribution_type distribution; + return distribution(engine, param_type{0, __max_size}); } // Given an instantiating type, return a unique value. @@ -309,10 +318,13 @@ namespace __gnu_test // computed with begin() and end(). const size_type sz = std::distance(__container.begin(), __container.end()); + // Container::erase(pos) requires dereferenceable pos. + if (sz == 0) + throw std::logic_error("erase_point: empty container"); // NB: Lowest common denominator: use forward iterator operations. auto i = __container.begin(); - std::advance(i, generate(sz)); + std::advance(i, generate(sz - 1)); // Makes it easier to think of this as __container.erase(i) (__container.*_F_erase_point)(i); @@ -337,12 +349,15 @@ namespace __gnu_test // computed with begin() and end(). const size_type sz = std::distance(__container.begin(), __container.end()); + // forward_list::erase_after(pos) requires dereferenceable pos. + if (sz == 0) + throw std::logic_error("erase_point: empty container"); // NB: Lowest common denominator: use forward iterator operations. auto i = __container.before_begin(); - std::advance(i, generate(sz)); + std::advance(i, generate(sz - 1)); - // Makes it easier to think of this as __container.erase(i) + // Makes it easier to think of this as __container.erase_after(i) (__container.*_F_erase_point)(i); } catch(const __gnu_cxx::forced_error&) @@ -405,14 +420,19 @@ namespace __gnu_test { const size_type sz = std::distance(__container.begin(), __container.end()); - size_type s1 = generate(sz); - size_type s2 = generate(sz); + // forward_list::erase_after(pos, last) requires a pos != last + if (sz == 0) + return; // Caller doesn't check for this, not a logic error. + + size_type s1 = generate(sz - 1); + size_type s2 = generate(sz - 1); auto i1 = __container.before_begin(); auto i2 = __container.before_begin(); std::advance(i1, std::min(s1, s2)); - std::advance(i2, std::max(s1, s2)); + std::advance(i2, std::max(s1, s2) + 1); - // Makes it easier to think of this as __container.erase(i1, i2). + // Makes it easier to think of this as + // __container.erase_after(i1, i2). (__container.*_F_erase_range)(i1, i2); } catch(const __gnu_cxx::forced_error&) @@ -1454,16 +1474,25 @@ namespace __gnu_test // constructor or assignment operator of value_type throws. if (!traits::has_throwing_erase::value) { - typename base_type::erase_point erasep; - erasep(container); + if (!container.empty()) + { + typename base_type::erase_point erasep; + erasep(container); + } typename base_type::erase_range eraser; eraser(container); } - typename base_type::pop_front popf; - popf(container); - typename base_type::pop_back popb; - popb(container); + if (!container.empty()) + { + typename base_type::pop_front popf; + popf(container); + } + if (!container.empty()) + { + typename base_type::pop_back popb; + popb(container); + } typename base_type::iterator_ops iops; iops(container); diff --git a/libstdc++-v3/testsuite/util/testsuite_containergen.h b/libstdc++-v3/testsuite/util/testsuite_containergen.h index a2156733ec6..c468c7f4415 100644 --- a/libstdc++-v3/testsuite/util/testsuite_containergen.h +++ b/libstdc++-v3/testsuite/util/testsuite_containergen.h @@ -20,6 +20,8 @@ #include #include +#include // getenv, atoi +#include // printf, fflush namespace __gnu_test { @@ -63,6 +65,18 @@ namespace __gnu_test { std::mt19937_64 random_gen; + if (const char* v = std::getenv("GLIBCXX_SEED_TEST_RNG")) + { + // A single seed value is much smaller than the mt19937 state size, + // but we're not trying to be cryptographically secure here. + int s = std::atoi(v); + if (s == 0) + s = (int)std::random_device{}(); + std::printf("Using random seed %d\n", s); + std::fflush(stdout); + random_gen.seed((unsigned)s); + } + #ifdef SIMULATOR_TEST int loops = 10; #else