From 9109f21b68c19832c8d333ff31262c89698f7306 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 22 Oct 2026 22:54:14 +0200 Subject: [PATCH 1/7] Replacing std::multimap::find by lower_bound as suggested in the ticket --- src/db/db/dbEdgesUtils.cc | 8 ++++---- src/db/db/dbLayout.cc | 14 +++++++------- src/db/db/dbLayoutLayers.cc | 6 +++--- src/db/db/dbLibraryManager.cc | 2 +- src/db/db/dbNetlistCompare.cc | 6 +++--- src/db/db/dbPLC.cc | 2 +- src/db/db/dbRegionCheckUtils.cc | 2 +- src/lay/lay/layGSIHelpProvider.cc | 2 +- src/layui/layui/layNetlistCrossReferenceModel.cc | 2 +- src/layui/layui/rdbMarkerBrowserPage.cc | 2 +- src/lym/lym/lymMacroCollection.cc | 8 ++++---- 11 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/db/db/dbEdgesUtils.cc b/src/db/db/dbEdgesUtils.cc index 4f27750d2..81867bcb3 100644 --- a/src/db/db/dbEdgesUtils.cc +++ b/src/db/db/dbEdgesUtils.cc @@ -55,11 +55,11 @@ JoinEdgesCluster::finish () std::multimap::iterator j0 = objects_by_p1.begin (); std::multimap::iterator j = j0; do { - std::multimap::iterator jj = objects_by_p2.find (j->first); + std::multimap::iterator jj = objects_by_p2.lower_bound (j->first); if (jj == objects_by_p2.end ()) { break; } else { - j = objects_by_p1.find (jj->second->first->p1 ()); + j = objects_by_p1.lower_bound (jj->second->first->p1 ()); tl_assert (j != objects_by_p1.end ()); } } while (j != j0); @@ -78,7 +78,7 @@ JoinEdgesCluster::finish () // remove the edge as it's taken std::multimap::iterator jj; - for (jj = objects_by_p2.find (i->first->p2 ()); jj != objects_by_p2.end () && jj->first == i->first->p2 (); ++jj) { + for (jj = objects_by_p2.lower_bound (i->first->p2 ()); jj != objects_by_p2.end () && jj->first == i->first->p2 (); ++jj) { if (jj->second == i) { break; } @@ -89,7 +89,7 @@ JoinEdgesCluster::finish () // process along the edge to the next one // TODO: this chooses any solution in case of forks. Choose a specific one? - j = objects_by_p1.find (i->first->p2 ()); + j = objects_by_p1.lower_bound (i->first->p2 ()); if (j != objects_by_p1.end ()) { i = j->second; } else { diff --git a/src/db/db/dbLayout.cc b/src/db/db/dbLayout.cc index 56d650b1e..a4235aabc 100644 --- a/src/db/db/dbLayout.cc +++ b/src/db/db/dbLayout.cc @@ -3349,7 +3349,7 @@ Layout::register_lib_proxy (db::LibraryProxy *lib_proxy) { auto key = std::make_pair (lib_proxy->lib_id (), lib_proxy->library_cell_index ()); - auto l = m_lib_proxy_map.find (key); + auto l = m_lib_proxy_map.lower_bound (key); while (l != m_lib_proxy_map.end () && l->first == key) { if (l->second == lib_proxy->Cell::cell_index ()) { return; @@ -3365,7 +3365,7 @@ Layout::unregister_lib_proxy (db::LibraryProxy *lib_proxy) { auto key = std::make_pair (lib_proxy->lib_id (), lib_proxy->library_cell_index ()); - auto l = m_lib_proxy_map.find (key); + auto l = m_lib_proxy_map.lower_bound (key); while (l != m_lib_proxy_map.end () && l->first == key) { if (l->second == lib_proxy->Cell::cell_index ()) { m_lib_proxy_map.erase (l); @@ -3394,7 +3394,7 @@ Layout::get_lib_proxy (Library *lib, cell_index_type cell_index) { auto key = std::make_pair (lib->get_id (), cell_index); - lib_proxy_map::const_iterator lp = m_lib_proxy_map.find (key); + lib_proxy_map::const_iterator lp = m_lib_proxy_map.lower_bound (key); if (lp != m_lib_proxy_map.end () && lp->first == key) { return lp->second; @@ -3432,7 +3432,7 @@ Layout::get_lib_proxy (Library *lib, cell_index_type cell_index) void Layout::register_cold_proxy (db::ColdProxy *cold_proxy) { - auto l = m_cold_proxy_map.find (cold_proxy->context_info ()); + auto l = m_cold_proxy_map.lower_bound (cold_proxy->context_info ()); while (l != m_cold_proxy_map.end () && l->first == cold_proxy->context_info ()) { if (l->second == cold_proxy->Cell::cell_index ()) { return; @@ -3446,7 +3446,7 @@ Layout::register_cold_proxy (db::ColdProxy *cold_proxy) void Layout::unregister_cold_proxy (db::ColdProxy *cold_proxy) { - auto l = m_cold_proxy_map.find (cold_proxy->context_info ()); + auto l = m_cold_proxy_map.lower_bound (cold_proxy->context_info ()); while (l != m_cold_proxy_map.end () && l->first == cold_proxy->context_info ()) { if (l->second == cold_proxy->Cell::cell_index ()) { m_cold_proxy_map.erase (l); @@ -3459,7 +3459,7 @@ Layout::unregister_cold_proxy (db::ColdProxy *cold_proxy) std::pair Layout::find_cold_proxy (const db::LayoutOrCellContextInfo &info) { - cold_proxy_map::const_iterator lp = m_cold_proxy_map.find (info); + cold_proxy_map::const_iterator lp = m_cold_proxy_map.lower_bound (info); if (lp != m_cold_proxy_map.end () && lp->first == info) { return std::make_pair (true, lp->second); } else { @@ -3470,7 +3470,7 @@ Layout::find_cold_proxy (const db::LayoutOrCellContextInfo &info) cell_index_type Layout::create_cold_proxy (const db::LayoutOrCellContextInfo &info) { - cold_proxy_map::const_iterator lp = m_cold_proxy_map.find (info); + cold_proxy_map::const_iterator lp = m_cold_proxy_map.lower_bound (info); if (lp != m_cold_proxy_map.end () && lp->first == info) { return lp->second; diff --git a/src/db/db/dbLayoutLayers.cc b/src/db/db/dbLayoutLayers.cc index 4e6d30ed4..91ea5f831 100644 --- a/src/db/db/dbLayoutLayers.cc +++ b/src/db/db/dbLayoutLayers.cc @@ -126,7 +126,7 @@ LayoutLayers::delete_layer (unsigned int n) { const db::LayerProperties &lp = m_layer_props [n]; if (! lp.is_null ()) { - for (auto i = m_layers_by_props.find (lp); i != m_layers_by_props.end () && i->first.log_equal (lp); ++i) { + for (auto i = m_layers_by_props.lower_bound (lp); i != m_layers_by_props.end () && i->first.log_equal (lp); ++i) { if (i->second == n) { m_layers_by_props.erase (i); break; @@ -163,7 +163,7 @@ LayoutLayers::get_layer_maybe (const db::LayerProperties &lp) const if (lp.is_null ()) { return -1; } else { - auto i = m_layers_by_props.find (lp); + auto i = m_layers_by_props.lower_bound (lp); if (i != m_layers_by_props.end () && i->first.log_equal (lp)) { return int (i->second); } else { @@ -290,7 +290,7 @@ LayoutLayers::set_properties (unsigned int n, const LayerProperties &props) const db::LayerProperties &lp = m_layer_props [n]; if (! lp.is_null ()) { - for (auto i = m_layers_by_props.find (lp); i != m_layers_by_props.end () && i->first.log_equal (lp); ++i) { + for (auto i = m_layers_by_props.lower_bound (lp); i != m_layers_by_props.end () && i->first.log_equal (lp); ++i) { if (i->second == n) { m_layers_by_props.erase (i); break; diff --git a/src/db/db/dbLibraryManager.cc b/src/db/db/dbLibraryManager.cc index 8181694b4..5948440f6 100644 --- a/src/db/db/dbLibraryManager.cc +++ b/src/db/db/dbLibraryManager.cc @@ -207,7 +207,7 @@ LibraryManager::register_lib (Library *library) // if the new library replaces the old one, remap existing library proxies before deleting the library // (replacement is done only when all technologies are substituted) - lib_name_map::iterator l = m_lib_by_name.find (library->get_name ()); + lib_name_map::iterator l = m_lib_by_name.lower_bound (library->get_name ()); bool found = false; while (l != m_lib_by_name.end () && l->first == library->get_name ()) { if (m_libs [l->second] && m_libs [l->second]->get_technologies () == library->get_technologies ()) { diff --git a/src/db/db/dbNetlistCompare.cc b/src/db/db/dbNetlistCompare.cc index 97ccdbd63..20baab567 100644 --- a/src/db/db/dbNetlistCompare.cc +++ b/src/db/db/dbNetlistCompare.cc @@ -1391,7 +1391,7 @@ NetlistComparer::do_pin_assignment (const db::Circuit *c1, const db::NetGraph &g continue; } - std::multimap::iterator np = net2pin2.find (n.other_net_index ()); + std::multimap::iterator np = net2pin2.lower_bound (n.other_net_index ()); for (db::Net::const_pin_iterator pi = net->begin_pins (); pi != net->end_pins (); ++pi) { if (np != net2pin2.end () && np->first == n.other_net_index ()) { @@ -1533,7 +1533,7 @@ NetlistComparer::do_device_assignment (const db::Circuit *c1, const db::NetGraph bool mapped = true; std::vector > k = compute_device_key_for_other (*d, g2, device_categorizer.is_strict_device_category (device_cat), mapped); - auto dm = device_map.find (k); + auto dm = device_map.lower_bound (k); if (! mapped || dm == device_map.end () || dm->first != k) { @@ -1761,7 +1761,7 @@ NetlistComparer::do_subcircuit_assignment (const db::Circuit *c1, const db::NetG bool mapped = true, valid = true; std::vector > k = compute_subcircuit_key_for_other (*sc, g2, &c22_circuit_and_pin_mapping, &circuit_pin_mapper, mapped, valid); - std::multimap >, std::pair >::iterator scm = subcircuit_map.find (k); + std::multimap >, std::pair >::iterator scm = subcircuit_map.lower_bound (k); if (! mapped || scm == subcircuit_map.end () || scm->first != k) { diff --git a/src/db/db/dbPLC.cc b/src/db/db/dbPLC.cc index 3c2bb6da7..ae79f48fc 100644 --- a/src/db/db/dbPLC.cc +++ b/src/db/db/dbPLC.cc @@ -468,7 +468,7 @@ Polygon::init () mp_v.push_back (v); - auto i = v2e.find (v); + auto i = v2e.lower_bound (v); tl_assert (i != v2e.end () && i->first == v && i->second != mp_e.back ()); mp_e.push_back (i->second); v = i->second->other (v); diff --git a/src/db/db/dbRegionCheckUtils.cc b/src/db/db/dbRegionCheckUtils.cc index 5c37ee272..523b99280 100644 --- a/src/db/db/dbRegionCheckUtils.cc +++ b/src/db/db/dbRegionCheckUtils.cc @@ -113,7 +113,7 @@ Edge2EdgeCheckBase::finish (const Edge *o, size_t p) if (m_has_negative_edge_output && m_pass == 1 && m_pseudo_edges.find (std::make_pair (*o, p)) == m_pseudo_edges.end ()) { std::pair k (*o, p); - std::multimap, size_t>::const_iterator i0 = m_e2ep.find (k); + std::multimap, size_t>::const_iterator i0 = m_e2ep.lower_bound (k); bool fully_removed = false; bool any = false; diff --git a/src/lay/lay/layGSIHelpProvider.cc b/src/lay/lay/layGSIHelpProvider.cc index 0ea3efd7e..f79b6f0c2 100644 --- a/src/lay/lay/layGSIHelpProvider.cc +++ b/src/lay/lay/layGSIHelpProvider.cc @@ -1139,7 +1139,7 @@ GSIHelpProvider::produce_class_doc (const std::string &cls) const for (gsi::ClassBase::method_iterator m = classes [n]->begin_methods (); m != classes [n]->end_methods (); ++m) { for (gsi::MethodBase::synonym_iterator syn = (*m)->begin_synonyms (); syn != (*m)->end_synonyms (); ++syn) { std::multimap >::iterator im; - while ((im = mm.find (full_name (*syn))) != mm.end ()) { + while ((im = mm.lower_bound (full_name (*syn))) != mm.end ()) { mm.erase (im); } } diff --git a/src/layui/layui/layNetlistCrossReferenceModel.cc b/src/layui/layui/layNetlistCrossReferenceModel.cc index b254cea34..f512671a1 100644 --- a/src/layui/layui/layNetlistCrossReferenceModel.cc +++ b/src/layui/layui/layNetlistCrossReferenceModel.cc @@ -460,7 +460,7 @@ void NetlistCrossReferenceModel::ensure_subcircuit_data_built () const const db::NetSubcircuitPinRef *n1 = sc_pair.first->netref_for_pin (i); if (n1) { const db::NetSubcircuitPinRef *n2 = 0; - std::multimap::iterator m = first_net_to_other_netref.find (n1->net ()); + std::multimap::iterator m = first_net_to_other_netref.lower_bound (n1->net ()); if (m != first_net_to_other_netref.end () && m->first == n1->net ()) { n2 = m->second; first_net_to_other_netref.erase (m); diff --git a/src/layui/layui/rdbMarkerBrowserPage.cc b/src/layui/layui/rdbMarkerBrowserPage.cc index 419d61cf6..f9e724cc8 100644 --- a/src/layui/layui/rdbMarkerBrowserPage.cc +++ b/src/layui/layui/rdbMarkerBrowserPage.cc @@ -786,7 +786,7 @@ class MarkerBrowserTreeViewModel void waive_or_unwaive (rdb::id_type cell_id, rdb::id_type cat_id, bool waived) { auto k = std::make_pair (cell_id, cat_id); - auto c = m_cache_by_ids.find (k); + auto c = m_cache_by_ids.lower_bound (k); while (c != m_cache_by_ids.end () && c->first == k) { c->second->waive_or_unwaive (waived); ++c; diff --git a/src/lym/lym/lymMacroCollection.cc b/src/lym/lym/lymMacroCollection.cc index 5d08133aa..18575cb2e 100644 --- a/src/lym/lym/lymMacroCollection.cc +++ b/src/lym/lym/lymMacroCollection.cc @@ -224,7 +224,7 @@ void MacroCollection::collect_used_nodes (std::set ¯os, std::set < Macro *MacroCollection::macro_by_name (const std::string &name, Macro::Format format) { - std::multimap ::iterator i = m_macros.find (name); + std::multimap ::iterator i = m_macros.lower_bound (name); while (i != m_macros.end () && i->first == name) { if (format == Macro::NoFormat || i->second->format () == format) { return i->second; @@ -236,7 +236,7 @@ Macro *MacroCollection::macro_by_name (const std::string &name, Macro::Format fo const Macro *MacroCollection::macro_by_name (const std::string &name, Macro::Format format) const { - std::multimap ::const_iterator i = m_macros.find (name); + std::multimap ::const_iterator i = m_macros.lower_bound (name); while (i != m_macros.end () && i->first == name) { if (format == Macro::NoFormat || i->second->format () == format) { return i->second; @@ -528,7 +528,7 @@ MacroCollection::create_entry (const std::string &path) if (Macro::format_from_suffix (path, interpreter, dsl_name, autorun, format)) { - iterator mm = m_macros.find (n); + iterator mm = m_macros.lower_bound (n); bool found = false; while (mm != m_macros.end () && mm->first == n && ! found) { if ((interpreter == Macro::None || mm->second->interpreter () == interpreter) && @@ -794,7 +794,7 @@ bool MacroCollection::del () void MacroCollection::rename_macro (Macro *macro, const std::string &new_name) { - iterator m = m_macros.find (macro->name ()); + iterator m = m_macros.lower_bound (macro->name ()); while (m != m_macros.end () && m->first == macro->name ()) { if (m->second == macro) { m_macros.erase (m); From 06be1945047291851056c5064da88fdd43c477f8 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 23 Oct 2026 00:17:44 +0200 Subject: [PATCH 2/7] 'lower_bound' is not an exact replacement. --- src/db/db/dbEdgesUtils.cc | 6 +++--- src/lay/lay/layGSIHelpProvider.cc | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/db/db/dbEdgesUtils.cc b/src/db/db/dbEdgesUtils.cc index 81867bcb3..7b44a1eed 100644 --- a/src/db/db/dbEdgesUtils.cc +++ b/src/db/db/dbEdgesUtils.cc @@ -56,11 +56,11 @@ JoinEdgesCluster::finish () std::multimap::iterator j = j0; do { std::multimap::iterator jj = objects_by_p2.lower_bound (j->first); - if (jj == objects_by_p2.end ()) { + if (jj == objects_by_p2.end () || jj->first != j->first) { break; } else { j = objects_by_p1.lower_bound (jj->second->first->p1 ()); - tl_assert (j != objects_by_p1.end ()); + tl_assert (j != objects_by_p1.end () && j->first == jj->second->first->p1 ()); } } while (j != j0); @@ -90,7 +90,7 @@ JoinEdgesCluster::finish () // process along the edge to the next one // TODO: this chooses any solution in case of forks. Choose a specific one? j = objects_by_p1.lower_bound (i->first->p2 ()); - if (j != objects_by_p1.end ()) { + if (j != objects_by_p1.end () && j->first == i->first->p2 ()) { i = j->second; } else { break; diff --git a/src/lay/lay/layGSIHelpProvider.cc b/src/lay/lay/layGSIHelpProvider.cc index f79b6f0c2..28752976e 100644 --- a/src/lay/lay/layGSIHelpProvider.cc +++ b/src/lay/lay/layGSIHelpProvider.cc @@ -1139,7 +1139,8 @@ GSIHelpProvider::produce_class_doc (const std::string &cls) const for (gsi::ClassBase::method_iterator m = classes [n]->begin_methods (); m != classes [n]->end_methods (); ++m) { for (gsi::MethodBase::synonym_iterator syn = (*m)->begin_synonyms (); syn != (*m)->end_synonyms (); ++syn) { std::multimap >::iterator im; - while ((im = mm.lower_bound (full_name (*syn))) != mm.end ()) { + std::string fn = full_name (*syn); + while ((im = mm.lower_bound (fn)) != mm.end () && im->first == fn) { mm.erase (im); } } From f70ac18070fa0dcad594f2e28b54da74a1841534 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 27 Oct 2026 21:10:39 +0100 Subject: [PATCH 3/7] Trying a fix for the gsiExpressionTests problem --- src/gsi/gsi/gsiClass.h | 8 ++ src/gsi/unit_tests/gsiExpressionTests.cc | 142 +++++++++++++++++++++++ src/tl/tl/tlObject.cc | 11 ++ src/tl/tl/tlObject.h | 5 + src/tl/tl/tlVariant.cc | 22 +++- src/tl/tl/tlVariant.h | 1 + src/tl/tl/tlVariantUserClasses.h | 5 + src/tl/unit_tests/tlExpressionTests.cc | 2 + 8 files changed, 191 insertions(+), 5 deletions(-) diff --git a/src/gsi/gsi/gsiClass.h b/src/gsi/gsi/gsiClass.h index fd546758a..fa8ecb176 100644 --- a/src/gsi/gsi/gsiClass.h +++ b/src/gsi/gsi/gsiClass.h @@ -194,6 +194,14 @@ class GSI_PUBLIC_TEMPLATE VariantUserClass } } + void detach_proxy (tl::Object *proxy) const + { + Proxy *p = dynamic_cast (proxy); + if (p) { + p->detach (); + } + } + virtual bool equal (const void *a, const void *b) const { return gsi::_var_user_equal_impl::value>::call ((const T *) a, (const T *) b, this); diff --git a/src/gsi/unit_tests/gsiExpressionTests.cc b/src/gsi/unit_tests/gsiExpressionTests.cc index 046980515..a81d89d1d 100644 --- a/src/gsi/unit_tests/gsiExpressionTests.cc +++ b/src/gsi/unit_tests/gsiExpressionTests.cc @@ -25,6 +25,7 @@ #include "gsiDecl.h" #include "tlUnitTest.h" +#include "tlVariantUserClasses.h" #include #include @@ -914,3 +915,144 @@ TEST(18) v = e.parse ("var y=Y.new(); y.is_a(X)").execute (); EXPECT_EQ (v.to_string (), std::string ("true")); } + +namespace +{ + static size_t s_xcount = 0; + + class TestClassX : public tl::Object + { + public: + TestClassX () { ++s_xcount; } + ~TestClassX () { --s_xcount; } + }; + + static size_t s_ycount = 0; + + class TestClassY : public gsi::ObjectBase + { + public: + TestClassY () { ++s_ycount; } + ~TestClassY () { --s_ycount; } + }; + + static tl::VariantUserClassImpl x_class_instance; + static gsi::Class y_class_instance ("TestClassY", "", gsi::Methods ()); +} + +// detaching from objects as references by user_take() +TEST(19) +{ + s_xcount = 0; + + tl::Variant v1 = tl::Variant (new TestClassX (), &x_class_instance, true); + EXPECT_EQ (s_xcount, size_t (1)); + TestClassX *tl_obj = (TestClassX *) v1.to_object (); + EXPECT_EQ (tl_obj->ref_count (), size_t (1)); + tl::Variant v2 = tl::Variant (tl_obj, &x_class_instance, false); + EXPECT_EQ (v2.to_object () == tl_obj, true); + + EXPECT_EQ (tl_obj->ref_count (), size_t (2)); + const auto *cls = v1.user_cls (); + void *obj = v1.user_take (); + tl_assert (obj == tl_obj); + EXPECT_EQ (v1.is_nil (), true); + + // v2 is still holding a reference + EXPECT_EQ (tl_obj->ref_count (), size_t (1)); + EXPECT_EQ (v2.is_nil (), false); + + cls->destroy (obj); + + // as tl::Object does not provide a callback to the owner, we only see that the weak reference has been + // turned to null: + EXPECT_EQ (v2.is_nil (), false); + EXPECT_EQ (v2.to_object () == 0, true); + + EXPECT_EQ (s_xcount, size_t (0)); +} + +// detaching from objects as references +TEST(20) +{ + s_xcount = 0; + + tl::Variant v1 = tl::Variant (new TestClassX (), &x_class_instance, true); + EXPECT_EQ (s_xcount, size_t (1)); + TestClassX *tl_obj = (TestClassX *) v1.to_object (); + EXPECT_EQ (tl_obj->ref_count (), size_t (1)); + tl::Variant v2 = tl::Variant (tl_obj, &x_class_instance, false); + EXPECT_EQ (v2.to_object () == tl_obj, true); + + EXPECT_EQ (tl_obj->ref_count (), size_t (2)); + v1.user_destroy (); + + EXPECT_EQ (v1.is_nil (), true); + + // as tl::Object does not provide a callback to the owner, we only see that the weak reference has been + // turned to null: + EXPECT_EQ (v2.is_nil (), false); + EXPECT_EQ (v2.to_object () == 0, true); + + EXPECT_EQ (s_xcount, size_t (0)); +} + +// detaching from gsi objects as references +TEST(21) +{ + s_ycount = 0; + + gsi::Proxy *proxy = new gsi::Proxy (&y_class_instance); + proxy->set (new TestClassY (), true, false, true); + EXPECT_EQ (s_ycount, size_t (1)); + + tl::Variant v1 = tl::Variant (proxy, y_class_instance.var_cls (false), true); + gsi::Proxy *tl_obj = dynamic_cast (v1.to_object ()); + EXPECT_EQ (tl_obj->ref_count (), size_t (2)); // one refcount more because of event binding + + TestClassY *y_obj = (TestClassY *) (v1.to_user ()); + + // adds another reference3 + tl::Variant v2 = v1; + EXPECT_EQ (v2.to_user () == y_obj, true); + + EXPECT_EQ (tl_obj->ref_count (), size_t (3)); + const auto *cls = v1.user_cls (); + void *obj = v1.user_take (); + EXPECT_EQ (tl_obj->ref_count (), size_t (1)); // one refcount left because of event binding + + EXPECT_EQ (v1.is_nil (), true); + tl_assert (dynamic_cast (v2.to_object ()) != 0); + EXPECT_EQ (dynamic_cast (v2.to_object ())->destroyed (), true); + + cls->destroy (obj); + + EXPECT_EQ (s_ycount, size_t (0)); +} + +// detaching from gsi objects as references +TEST(22) +{ + s_ycount = 0; + + gsi::Proxy *proxy = new gsi::Proxy (&y_class_instance); + proxy->set (new TestClassY (), true, false, true); + EXPECT_EQ (s_ycount, size_t (1)); + + tl::Variant v1 = tl::Variant (proxy, y_class_instance.var_cls (false), true); + gsi::Proxy *tl_obj = dynamic_cast (v1.to_object ()); + EXPECT_EQ (tl_obj->ref_count (), size_t (2)); + + TestClassY *y_obj = (TestClassY *) (v1.to_user ()); + tl::Variant v2 = v1; + EXPECT_EQ (v2.to_user () == y_obj, true); + + EXPECT_EQ (tl_obj->ref_count (), size_t (3)); + v1.user_destroy (); + + EXPECT_EQ (v1.is_nil (), true); + tl_assert (dynamic_cast (v2.to_object ()) != 0); + EXPECT_EQ (dynamic_cast (v2.to_object ())->destroyed (), true); + + EXPECT_EQ (s_ycount, size_t (0)); +} diff --git a/src/tl/tl/tlObject.cc b/src/tl/tl/tlObject.cc index 4cca01035..a9caf2122 100644 --- a/src/tl/tl/tlObject.cc +++ b/src/tl/tl/tlObject.cc @@ -145,6 +145,17 @@ void Object::release_object () } } +size_t Object::ref_count () const +{ + WeakOrSharedPtr *ptrs = (WeakOrSharedPtr *)(size_t (mp_ptrs) & ~size_t (1)); + + size_t n = 0; + for (WeakOrSharedPtr *p = ptrs; p; p = p->mp_next) { + ++n; + } + return n; +} + // --------------------------------------------------------------------- // WeakOrSharedPtr implementation diff --git a/src/tl/tl/tlObject.h b/src/tl/tl/tlObject.h index 1991a13cb..ce87c4b5e 100644 --- a/src/tl/tl/tlObject.h +++ b/src/tl/tl/tlObject.h @@ -115,6 +115,11 @@ class TL_PUBLIC Object */ void release_object (); + /** + * @brief Diagnostic: gets the number of references + */ + size_t ref_count () const; + protected: /** * @brief Detach this object from all events it was registered as listener diff --git a/src/tl/tl/tlVariant.cc b/src/tl/tl/tlVariant.cc index 246688d3b..86786a76f 100644 --- a/src/tl/tl/tlVariant.cc +++ b/src/tl/tl/tlVariant.cc @@ -2920,21 +2920,33 @@ bool Variant::user_is_ref () const void Variant::user_destroy () { - tl_assert (is_user ()); - void *obj = to_user (); + const auto *cls = user_cls (); + void *obj = user_take (); if (obj) { - user_cls ()->destroy (obj); - m_type = t_nil; + tl_assert (cls); + cls->destroy (obj); } } void *Variant::user_take () { tl_assert (is_user ()); + void *obj = to_user (); if (obj) { - m_type = t_nil; + + // Resets the reference so we really own the tl::Object that is returned + if (m_type == t_user_ref) { + tl::WeakOrSharedPtr *wptr = const_cast (reinterpret_cast (m_var.mp_user_ref.ptr)); + tl::Object *obj = wptr->get (); + wptr->unshare (); + *wptr = tl::WeakOrSharedPtr (); + user_cls ()->detach_proxy (obj); + } + } + + m_type = t_nil; return obj; } diff --git a/src/tl/tl/tlVariant.h b/src/tl/tl/tlVariant.h index c8bb137cc..3a5cef97e 100644 --- a/src/tl/tl/tlVariant.h +++ b/src/tl/tl/tlVariant.h @@ -89,6 +89,7 @@ class TL_PUBLIC VariantUserClassBase virtual const gsi::ClassBase *gsi_cls () const = 0; virtual const tl::EvalClass *eval_cls () const = 0; virtual void *deref_proxy (tl::Object *proxy) const = 0; + virtual void detach_proxy (tl::Object *proxy) const = 0; virtual const tl::VariantUserClassBase *change_constness (bool constness) const = 0; const void *deref_proxy_const (const tl::Object *proxy) const diff --git a/src/tl/tl/tlVariantUserClasses.h b/src/tl/tl/tlVariantUserClasses.h index af0e181dc..c19aaed14 100644 --- a/src/tl/tl/tlVariantUserClasses.h +++ b/src/tl/tl/tlVariantUserClasses.h @@ -254,6 +254,11 @@ class VariantUserClassImpl return obj; } + virtual void detach_proxy (tl::Object * /*obj*/) const + { + // No proxy, no detach + } + virtual const gsi::ClassBase *gsi_cls () const { return 0; diff --git a/src/tl/unit_tests/tlExpressionTests.cc b/src/tl/unit_tests/tlExpressionTests.cc index 98e64a69c..4908ce43e 100644 --- a/src/tl/unit_tests/tlExpressionTests.cc +++ b/src/tl/unit_tests/tlExpressionTests.cc @@ -464,6 +464,7 @@ class BoxClassClass : public tl::VariantUserClassBase, private tl::EvalClass virtual bool is_const () const { return false; } virtual bool is_ref () const { return false; } virtual void *deref_proxy (tl::Object *) const { return 0; } + virtual void detach_proxy (tl::Object *) const { } virtual const gsi::ClassBase*gsi_cls() const { return 0; } virtual const tl::VariantUserClassBase *change_constness (bool) const { return this; } static BoxClassClass instance; @@ -533,6 +534,7 @@ class EdgeClassClass : public tl::VariantUserClassBase, private tl::EvalClass virtual bool is_const () const { return false; } virtual bool is_ref () const { return false; } virtual void *deref_proxy (tl::Object *) const { return 0; } + virtual void detach_proxy (tl::Object *) const { } virtual const gsi::ClassBase*gsi_cls() const { return 0; } virtual const tl::VariantUserClassBase *change_constness (bool) const { return this; } static EdgeClassClass instance; From 24896c9aba2d78e0519092aad763780edbf9a25f Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 27 Oct 2026 23:35:42 +0100 Subject: [PATCH 4/7] Fixing a problem with clang 21.1.8 and dynamic_cast by replacing it by static_cast. --- src/db/db/dbArray.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/db/db/dbArray.h b/src/db/db/dbArray.h index 930acba6f..c3765e200 100644 --- a/src/db/db/dbArray.h +++ b/src/db/db/dbArray.h @@ -275,7 +275,11 @@ class DB_PUBLIC ArrayRepository basic_repository::iterator f = r->find ((ArrayBase *) &base); if (f != r->end ()) { - return dynamic_cast *> (*f); + // Issues with clang 21.1.8: + // return dynamic_cast *> (*f); + // Returns a null pointer + tl_assert ((*f)->type () == base.type ()); + return static_cast *> (*f); } else { basic_array *bb = base.clone (); bb->in_repository = true; From 8a3b9ca302f5cb746407b3e3477b51d18d2a40dc Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 28 Oct 2026 22:41:05 +0100 Subject: [PATCH 5/7] Fixing another std::multimap problem --- src/db/db/dbArray.h | 3 --- src/db/db/dbRegionCheckUtils.cc | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/db/db/dbArray.h b/src/db/db/dbArray.h index c3765e200..982d0a891 100644 --- a/src/db/db/dbArray.h +++ b/src/db/db/dbArray.h @@ -275,9 +275,6 @@ class DB_PUBLIC ArrayRepository basic_repository::iterator f = r->find ((ArrayBase *) &base); if (f != r->end ()) { - // Issues with clang 21.1.8: - // return dynamic_cast *> (*f); - // Returns a null pointer tl_assert ((*f)->type () == base.type ()); return static_cast *> (*f); } else { diff --git a/src/db/db/dbRegionCheckUtils.cc b/src/db/db/dbRegionCheckUtils.cc index 523b99280..db8090fe2 100644 --- a/src/db/db/dbRegionCheckUtils.cc +++ b/src/db/db/dbRegionCheckUtils.cc @@ -270,7 +270,7 @@ Edge2EdgeCheckBase::add (const db::Edge *o1, size_t p1, const db::Edge *o2, size for (unsigned int p = 0; p < 2; ++p) { std::pair k (*o1, p1); - for (std::multimap, size_t>::const_iterator i = m_e2ep.find (k); i != m_e2ep.end () && i->first == k; ++i) { + for (std::multimap, size_t>::const_iterator i = m_e2ep.lower_bound (k); i != m_e2ep.end () && i->first == k; ++i) { size_t n = i->second / 2; if (n < m_first_pseudo && ! m_ep_discarded [n]) { n1.push_back (n); From 09a8bd44ecc1f3fb6b8faceb6fd40c1e98aad7d7 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 29 Oct 2026 00:09:43 +0100 Subject: [PATCH 6/7] Fixing another std::multimap issue --- src/db/db/dbLibraryManager.cc | 12 +++++++----- src/db/db/dbPLC.cc | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/db/db/dbLibraryManager.cc b/src/db/db/dbLibraryManager.cc index 5948440f6..51d72477a 100644 --- a/src/db/db/dbLibraryManager.cc +++ b/src/db/db/dbLibraryManager.cc @@ -82,14 +82,16 @@ LibraryManager::rename (lib_id_type lib_id, const std::string &name) std::string org_name = lib->get_name (); - for (auto it = m_lib_by_name.find (org_name);it != m_lib_by_name.end () && it->first == org_name; ++it) { + for (auto it = m_lib_by_name.lower_bound (org_name); it != m_lib_by_name.end () && it->first == org_name; ++it) { if (it->second == lib_id) { m_lib_by_name.erase (it); break; } } - m_lib_by_name.insert (std::make_pair (name, lib_id)); + // insert new lib as first of this name + auto l = m_lib_by_name.lower_bound (name); + m_lib_by_name.insert (l, std::make_pair (name, lib_id)); lib->set_name (name); } @@ -109,7 +111,7 @@ LibraryManager::lib_by_name (const std::string &name, const std::setfirst == name) { const db::Library *lptr = lib_internal (l->second); bool found = lptr->for_technologies (); @@ -127,7 +129,7 @@ LibraryManager::lib_by_name (const std::string &name, const std::setfirst == name) { if (! lib_internal (l->second)->for_technologies ()) { return std::make_pair (true, l->second); @@ -224,7 +226,7 @@ LibraryManager::register_lib (Library *library) } // insert new lib as first of this name - l = m_lib_by_name.find (library->get_name ()); + l = m_lib_by_name.lower_bound (library->get_name ()); m_lib_by_name.insert (l, std::make_pair (library->get_name (), id)); } diff --git a/src/db/db/dbPLC.cc b/src/db/db/dbPLC.cc index ae79f48fc..cb8dff114 100644 --- a/src/db/db/dbPLC.cc +++ b/src/db/db/dbPLC.cc @@ -475,7 +475,7 @@ Polygon::init () v2e.erase (i); - i = v2e.find (v); + i = v2e.lower_bound (v); while (i != v2e.end () && i->first == v) { if (i->second == mp_e.back ()) { v2e.erase (i); From 4a17a3675dff0e0163c089cbc4b9965d1a1ad902 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 29 Oct 2026 00:42:26 +0100 Subject: [PATCH 7/7] Fixing another std::multimap issue --- src/db/db/dbPropertiesRepository.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/db/db/dbPropertiesRepository.h b/src/db/db/dbPropertiesRepository.h index f1d3c7945..026af3b00 100644 --- a/src/db/db/dbPropertiesRepository.h +++ b/src/db/db/dbPropertiesRepository.h @@ -292,7 +292,12 @@ class DB_PUBLIC PropertiesSet */ iterator find (db::property_names_id_type name_id) const { - return m_map.find (name_id); + auto i = m_map.lower_bound (name_id); + if (i != m_map.end () && i->first != name_id) { + return m_map.end (); + } else { + return i; + } } /**