From f512088f2bbc000ac18081e220e1913f2907cb48 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Sun, 30 Aug 2026 22:47:09 +0200 Subject: [PATCH 1/2] Fix uninitialised read in lut_map on networks with dangling nodes `cut() = default` leaves `_length`, `_cend` and `_end` indeterminate. A default-constructed cut is reachable: `cut_set` and `lut_cut_set` hold an array of them and `best()` returns `*_pcuts[0]` whether or not any cut has been inserted. `lut_map_impl::compute_share_mapping_init` iterates over every node index and calls `best()`, and a node unreachable from the outputs is never visited by the cut enumerator, so its cut set is empty and the following `for ( auto leaf : cut )` in `compute_cut_data` walks a garbage end pointer and indexes `cuts[leaf]` with whatever it finds. Networks with unreachable nodes are not exotic: ABC's `&dch -f; &put` leaves the choice-class members in as ordinary AND nodes, so every AIG written by the standard `strash; &get; &dch -f; &put; write_aiger` front end has them (cavlc: 1271 ANDs written, 647 reachable). Give the default constructor a defined empty state, and add a lut_mapper test that maps a six-gate AIG whose last four gates drive no output. --- include/mockturtle/utils/cuts.hpp | 14 ++++++++++++- test/algorithms/lut_mapper.cpp | 33 +++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/include/mockturtle/utils/cuts.hpp b/include/mockturtle/utils/cuts.hpp index 5a9c4404f..fc35990e1 100644 --- a/include/mockturtle/utils/cuts.hpp +++ b/include/mockturtle/utils/cuts.hpp @@ -87,8 +87,20 @@ class cut { public: /*! \brief Default constructor. + * + * Constructs an empty cut. The end iterators must be initialized here: + * `cut_set` and its variants hold an array of default-constructed cuts and + * hand one of them out from `best()` even when the set is still empty, so a + * defaulted constructor leaves `end()` indeterminate for a reachable object. */ - cut() = default; + cut() + : _length( 0 ), + _signature( 0 ), + _cend( _leaves.begin() ), + _end( _leaves.begin() ), + _data() + { + } /*! \brief Copy constructor. * diff --git a/test/algorithms/lut_mapper.cpp b/test/algorithms/lut_mapper.cpp index 343697385..6d38b7ec1 100644 --- a/test/algorithms/lut_mapper.cpp +++ b/test/algorithms/lut_mapper.cpp @@ -334,3 +334,36 @@ TEST_CASE( "LUT map remapping LUT network", "[lut_mapper]" ) CHECK( mapped_ntk.num_cells() == 1 ); CHECK( *equivalence_checking( miter_ntk ) == true ); } + +TEST_CASE( "LUT map of an AIG with nodes unreachable from the outputs", "[lut_mapper]" ) +{ + /* `compute_share_mapping_init` calls `best()` on every node index, including + nodes the cut enumerator never visited because no output depends on them. + Their cut set is empty and `best()` hands out a default-constructed cut, so + that cut has to be a well-defined empty one. */ + aig_network aig; + + auto const a = aig.create_pi(); + auto const b = aig.create_pi(); + auto const c = aig.create_pi(); + + auto const f1 = aig.create_and( a, b ); + auto const f2 = aig.create_and( f1, c ); + aig.create_po( f2 ); + + /* dangling: driven by primary inputs, driving no output */ + auto const d1 = aig.create_and( a, !b ); + auto const d2 = aig.create_and( d1, !c ); + auto const d3 = aig.create_and( d2, a ); + aig.create_and( d3, !b ); + + CHECK( aig.num_gates() == 6 ); + + lut_map_params ps; + ps.cut_enumeration_ps.cut_size = 4; + ps.area_oriented_mapping = true; + + const klut_network klut = lut_map( aig, ps ); + + CHECK( klut.num_gates() == 1 ); +} From 3586273dec2f6f2de9a589abfcd430cdbef74c5b Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Mon, 28 Sep 2026 18:53:03 +0200 Subject: [PATCH 2/2] Initialize empty cut sets in clear instead of every cut --- include/mockturtle/algorithms/lut_mapper.hpp | 5 ++- include/mockturtle/utils/cuts.hpp | 17 +++------- test/algorithms/lut_mapper.cpp | 34 ++++++++++++++++++-- test/utils/cuts.cpp | 25 ++++++++++++++ 4 files changed, 65 insertions(+), 16 deletions(-) diff --git a/include/mockturtle/algorithms/lut_mapper.hpp b/include/mockturtle/algorithms/lut_mapper.hpp index 57659041d..5c613d19b 100644 --- a/include/mockturtle/algorithms/lut_mapper.hpp +++ b/include/mockturtle/algorithms/lut_mapper.hpp @@ -231,6 +231,9 @@ class lut_cut_set { *pit++ = &c; } + /* best() exposes the first cut even when the set is empty. */ + _cuts[0].set_leaves( _cuts[0].begin(), _cuts[0].begin() ); + _cuts[0].data() = {}; } /*! \brief Adds a cut to the end of the set. @@ -3012,4 +3015,4 @@ void lut_map_inplace( Ntk& ntk, lut_map_params const& ps = {}, lut_map_stats* ps } } -} /* namespace mockturtle */ \ No newline at end of file +} /* namespace mockturtle */ diff --git a/include/mockturtle/utils/cuts.hpp b/include/mockturtle/utils/cuts.hpp index fc35990e1..d15a86237 100644 --- a/include/mockturtle/utils/cuts.hpp +++ b/include/mockturtle/utils/cuts.hpp @@ -87,20 +87,8 @@ class cut { public: /*! \brief Default constructor. - * - * Constructs an empty cut. The end iterators must be initialized here: - * `cut_set` and its variants hold an array of default-constructed cuts and - * hand one of them out from `best()` even when the set is still empty, so a - * defaulted constructor leaves `end()` indeterminate for a reachable object. */ - cut() - : _length( 0 ), - _signature( 0 ), - _cend( _leaves.begin() ), - _end( _leaves.begin() ), - _data() - { - } + cut() = default; /*! \brief Copy constructor. * @@ -514,6 +502,9 @@ void cut_set::clear() { *pit++ = &c; } + /* best() exposes the first cut even when the set is empty. */ + _cuts[0].set_leaves( _cuts[0].begin(), _cuts[0].begin() ); + _cuts[0].data() = {}; } template diff --git a/test/algorithms/lut_mapper.cpp b/test/algorithms/lut_mapper.cpp index 6d38b7ec1..74cdc666d 100644 --- a/test/algorithms/lut_mapper.cpp +++ b/test/algorithms/lut_mapper.cpp @@ -339,8 +339,8 @@ TEST_CASE( "LUT map of an AIG with nodes unreachable from the outputs", "[lut_ma { /* `compute_share_mapping_init` calls `best()` on every node index, including nodes the cut enumerator never visited because no output depends on them. - Their cut set is empty and `best()` hands out a default-constructed cut, so - that cut has to be a well-defined empty one. */ + Their cut set is empty, so `clear()` must initialize the first cut + exposed by `best()`. */ aig_network aig; auto const a = aig.create_pi(); @@ -367,3 +367,33 @@ TEST_CASE( "LUT map of an AIG with nodes unreachable from the outputs", "[lut_ma CHECK( klut.num_gates() == 1 ); } + +TEST_CASE( "empty LUT cut set has an initialized best cut", "[lut_mapper]" ) +{ + using cut_type = cut<16, cut_data>; + detail::lut_cut_set set; + + SECTION( "newly constructed" ) + { + } + + SECTION( "after clearing populated and reordered storage" ) + { + std::vector leaves{ 1, 2, 3 }; + auto& first = set.add_cut( leaves.begin(), leaves.end() ); + first->func_id = 42; + first->data.lut_area = 23; + set.add_cut( leaves.begin(), leaves.begin() + 1 ); + set.update_best( 1 ); + set.clear(); + } + + CHECK( set.size() == 0 ); + CHECK( set.best().size() == 0 ); + CHECK( set.best().signature() == 0 ); + CHECK( set.best().begin() == set.best().end() ); + auto const& best = set.best(); + CHECK( best.begin() == best.end() ); + CHECK( best->func_id == 0 ); + CHECK( best->data.lut_area == 0 ); +} diff --git a/test/utils/cuts.cpp b/test/utils/cuts.cpp index b0c82cf75..f39f64ead 100644 --- a/test/utils/cuts.cpp +++ b/test/utils/cuts.cpp @@ -188,3 +188,28 @@ TEST_CASE( "merge three cuts", "[cuts]" ) ct.merge( c3, cr, 10 ); CHECK( std::vector( cr.begin(), cr.end() ) == std::vector{ 1u, 2u, 3u, 4u, 5u, 6u, 7u, 9u } ); } + +TEST_CASE( "empty cut set has an initialized best cut", "[cuts]" ) +{ + cut_set, 25> set; + + SECTION( "newly constructed" ) + { + } + + SECTION( "after clearing populated and reordered storage" ) + { + std::vector leaves{ 1, 2, 3 }; + set.add_cut( leaves.begin(), leaves.end() ).data() = 42; + set.add_cut( leaves.begin(), leaves.begin() + 1 ).data() = 23; + set.update_best( 1 ); + set.clear(); + } + + CHECK( set.size() == 0 ); + CHECK( set.best().size() == 0 ); + CHECK( set.best().signature() == 0 ); + CHECK( set.best().begin() == set.best().end() ); + CHECK( ( *set.begin() )->begin() == ( *set.begin() )->end() ); + CHECK( set.best().data() == 0 ); +}