From patchwork Wed Sep 2 17:41:13 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nathan Myers X-Patchwork-Id: 142748 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id F13E34BA2E29 for ; Wed, 2 Sep 2026 17:51:03 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org F13E34BA2E29 X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from us-smtp-delivery-44.mimecast.com (us-smtp-delivery-44.mimecast.com [207.211.30.44]) by sourceware.org (Postfix) with ESMTP id 7431B4BA2E0B for ; Wed, 2 Sep 2026 17:50:14 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 7431B4BA2E0B Authentication-Results: sourceware.org; dmarc=none (p=none dis=none) header.from=cantrip.org Authentication-Results: sourceware.org; spf=fail smtp.mailfrom=cantrip.org ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 7431B4BA2E0B Authentication-Results: sourceware.org; arc=none smtp.remote-ip=207.211.30.44 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1788371414; cv=none; b=a1l7dLNMz5rquo/Qu8rE7CnIzBFAptgNvlODYfFwJ1sPwBdLZQBA3cPelpAyvuSFQtLgADtHe9uYbS8WYQ7eWSpPAZ+RxzwMDabgfL4oyg148uHCaXIxWhdjOUQhL7X7HeDZqPwP2v71JqxiDiZHFcztRIEPvycdi29DvPe2vmU= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1788371414; c=relaxed/simple; bh=3oHHslI8EySjtXMzmOXN91Nds/a9OOFhIEdjJ1mbrok=; h=From:To:Subject:Date:Message-ID:MIME-Version; b=C4f6s2eZzxU0c0TcQRmlKmh5yHjBi0D9xiWRNblpDrTSjI6TCdcO0vof82uM6XOWFu+JJ1pgnxJODiwWULQpbMDPb7zC/s9pxIVtA/9KMdmtSulsK9nkBv3uscJg23VGMCocdUnljnKhBxj7Lcw5XCdkCBefjqO/7qIlFUqSThE= ARC-Authentication-Results: i=1; sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 7431B4BA2E0B Received: from mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-357-IniWcl5CMu-hT8-izIdl4w-1; Wed, 02 Sep 2026 13:50:07 -0400 X-MC-Unique: IniWcl5CMu-hT8-izIdl4w-1 X-Mimecast-MFC-AGG-ID: IniWcl5CMu-hT8-izIdl4w_1788371407 Received: from mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.12]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id F130C1953996; Wed, 2 Sep 2026 17:50:06 +0000 (UTC) Received: from redhat (headnet03.pony-001.prod.iad2.dc.redhat.com [10.2.32.114]) by mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 551C5195608A; Wed, 2 Sep 2026 17:50:05 +0000 (UTC) From: Nathan Myers To: gcc-patches@gcc.gnu.org, libstdc++@gcc.gnu.org Subject: [PATCH] libstdc++: fix unordered_map heterogeneous insertion copying [PR126800] Date: Wed, 2 Sep 2026 13:41:13 -0400 Message-ID: <20260902175004.59863-1-ncm@cantrip.org> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.12 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: UcBgyZJsr1lfNXvgzPfvL0p26emmnNFgLpD8CqFW9mQ_1788371407 X-Mimecast-Originator: cantrip.org content-type: text/plain; charset=WINDOWS-1252; x-default=true X-Spam-Status: No, score=-8.3 required=5.0 tests=BAYES_00, GIT_PATCH_0, KAM_DMARC_STATUS, LOCAL_AUTHENTICATION_FAIL_SPF, PROLO_LEO1, RCVD_IN_DNSWL_LOW, RCVD_IN_SBL_CSS, SPF_FAIL, SPF_HELO_PASS, TXREP shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gcc-patches-bounces~patchwork=sourceware.org@gcc.gnu.org PR126800 reports that heterogeneous insertions fail to avoid copying the key when no insertion occurs. They call an existing member try_emplace that had invoked the wrong helper for their new usage case, and tests failed to detect the error. This patch calls the correct helper, and amends tests for heterogeneous insert operations op[], at(), try_emplace(), and insert_or_replace(). libstdc++-v3/Changelog: PR libstdc++/126800 * include/bits/hashtable.h: (try_emplace): Call correct helper. * testsuite/23_containers/unordered_map/modifiers/hetero/insert.cc: Check whether key arguments have been copied. --- libstdc++-v3/include/bits/hashtable.h | 2 +- .../unordered_map/modifiers/hetero/insert.cc | 125 ++++++++++++++---- 2 files changed, 99 insertions(+), 28 deletions(-) diff --git a/libstdc++-v3/include/bits/hashtable.h b/libstdc++-v3/include/bits/hashtable.h index eff6c31d827..5b98b1193ed 100644 --- a/libstdc++-v3/include/bits/hashtable.h +++ b/libstdc++-v3/include/bits/hashtable.h @@ -1107,7 +1107,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION // Note we ignore the hint argument. __hash_code __code; size_type __bkt; - if (auto __loc = _M_locate(__k)) + if (auto __loc = _M_locate_tr(__k)) return { iterator(__loc), false }; else { diff --git a/libstdc++-v3/testsuite/23_containers/unordered_map/modifiers/hetero/insert.cc b/libstdc++-v3/testsuite/23_containers/unordered_map/modifiers/hetero/insert.cc index c1d86b8eea3..87f997d3a6c 100644 --- a/libstdc++-v3/testsuite/23_containers/unordered_map/modifiers/hetero/insert.cc +++ b/libstdc++-v3/testsuite/23_containers/unordered_map/modifiers/hetero/insert.cc @@ -6,6 +6,7 @@ #include #include #include +#include #include struct Y; @@ -20,11 +21,12 @@ struct X { struct Y { std::string s; + mutable bool copied_from = false; // [PR126800] Y() = default; Y(Y&& y) : s(std::move(y.s)) { y.s.clear(); } - Y(const Y& y) = default; + Y(const Y& y) : s(y.s) { y.copied_from = true; } Y& operator=(Y&& y) { s = std::move(y.s); y.s.clear(); return *this; } - Y& operator=(const Y& y) = default; + Y& operator=(const Y& y) { s = y.s; y.copied_from = true; return *this; } Y(std::string_view sv) : s(sv) {} Y(int n) : s(std::string('a', n)) {} Y(const Y& a, const Y& b) : s(a.s + "1" + b.s) { } @@ -36,7 +38,7 @@ struct Y { }; X::X(Y&& y) : s(std::move(y.s)) { y.s.clear(); } -X::X(const Y& y) : s(y.s) {} +X::X(const Y& y) : s(y.s) { y.copied_from = true; } struct Hash { using is_transparent = void; @@ -52,22 +54,33 @@ void test_op_bracket() std::unordered_map amap; amap.insert({{X{"abc"}, 1}, {X{"def"}, 2}, {X{"ghi"}, 3}}); - Y x{"dei"}, y{"deh"}, z{"deg"}; - amap[z] = 4; + Y z{"deg"}; + amap[z] = Y{4}; VERIFY(amap.size() == 4); VERIFY(z.s.size() == 3); // not moved from. + VERIFY(z.copied_from); + VERIFY(amap[Y{"deg"}] == Y{4}); - amap[std::move(z)] = 5; + Y zz{"deg"}; + amap[std::move(zz)] = Y{5}; VERIFY(amap.size() == 4); - VERIFY(z.s.size() == 3); // not moved from. + VERIFY(zz.s.size() == 3); // not moved from. + VERIFY(!zz.copied_from); + VERIFY(amap[Y{"deg"}] == Y{5}); - VERIFY(amap[std::move(y)] == Y{}); + Y y{"deh"}; + amap[y] = Y{6}; VERIFY(amap.size() == 5); - VERIFY(y.s.empty()); // moved from. + VERIFY(y.s.size() == 3); // not moved from. + VERIFY(y.copied_from); + VERIFY(amap[Y{"deh"}] == Y{6}); - amap[std::move(x)] = 7; + Y x{"dei"}; + amap[std::move(x)] = Y{7}; VERIFY(amap.size() == 6); VERIFY(x.s.empty()); // moved from + VERIFY(!x.copied_from); + VERIFY(amap[Y{"dei"}] == Y{7}); } void test_at() @@ -81,21 +94,24 @@ void test_at() VERIFY(2 == amap.at(x)); VERIFY(amap.size() == 3); VERIFY(x.s.size() == 3); // not moved from + VERIFY(!x.copied_from); VERIFY(4 == (amap.at(x) = 4)); VERIFY(amap.size() == 3); VERIFY(x.s.size() == 3); // not moved from + VERIFY(!x.copied_from); } catch(...) { VERIFY(false); } Y z{"deg"}; try { - amap.at(z) = 4; + amap.at(z) = Y{1}; VERIFY(false); // Should have thrown. } catch (std::out_of_range&) { VERIFY(amap.size() == 3); } catch (...) { VERIFY(false); } // Wrong exception. VERIFY(z.s.size() == 3); // not moved from + VERIFY(!z.copied_from); Y y{"deh"}; auto const& amapr{amap}; @@ -108,6 +124,7 @@ void test_at() catch (...) { VERIFY(false); } // Wrong exception. VERIFY(amapr.size() == 3); VERIFY(y.s.size() == 3); // not moved from + VERIFY(!y.copied_from); } void test_try_emplace() @@ -117,10 +134,15 @@ void test_try_emplace() { // Fail, already there auto a = amap; - auto [it, res] = a.try_emplace(Y{"def"}, Y{"xyz"}); + Y y{"def"}, z{"xyz"}; + auto [it, res] = a.try_emplace(std::move(y), z); VERIFY(!res); VERIFY(a.size() == 3); VERIFY(a.at(Y{"def"}) == Y{2}); + VERIFY(y.s.size() == 3); // not moved from + VERIFY(!y.copied_from); + VERIFY(z.s.size() == 3); // not moved from + VERIFY(!z.copied_from); } { // Fail, already there, move auto a = amap; @@ -130,13 +152,16 @@ void test_try_emplace() VERIFY(a.size() == 3); VERIFY(a.at(Y{"def"}) == Y{2}); VERIFY(y.s.size() == 3); // not moved from + VERIFY(!y.copied_from); VERIFY(z.s.size() == 3); // not moved from + VERIFY(!z.copied_from); } { // Succeed, construct auto a = amap; - Y m("m"), n("n"), o("o"), p("p"), dek("dek"); + Y m("m"), n("n"), o("o"), p("p"); { - auto [it, res] = a.try_emplace(Y{"deg"}, m, n); + Y y{"deg"}; + auto [it, res] = a.try_emplace(std::move(y), m, n); VERIFY(res); VERIFY(a.size() == 4); VERIFY(it->first == X{"deg"}); @@ -145,39 +170,47 @@ void test_try_emplace() VERIFY(n.s.size() == 1); } { - auto [it, res] = a.try_emplace(Y{"deh"}, m, std::move(n)); + Y y{"deh"}; + auto [it, res] = a.try_emplace(std::move(y), m, std::move(n)); VERIFY(res); VERIFY(a.size() == 5); VERIFY(it->first == X{"deh"}); VERIFY(it->second == Y{"m2n"}); VERIFY(m.s.size() == 1); VERIFY(n.s.empty()); + VERIFY(!y.copied_from); } { - auto [it, res] = a.try_emplace(Y{"dei"}, std::move(m), o); + Y y{"dei"}; + auto [it, res] = a.try_emplace(std::move(y), std::move(m), o); VERIFY(res); VERIFY(a.size() == 6); VERIFY(it->first == X{"dei"}); VERIFY(it->second == Y{"m3o"}); VERIFY(m.s.empty()); VERIFY(o.s.size() == 1); + VERIFY(!y.copied_from); } { - auto [it, res] = a.try_emplace(Y{"dej"}, std::move(o), std::move(p)); + Y y{"dej"}; + auto [it, res] = a.try_emplace(std::move(y), std::move(o), std::move(p)); VERIFY(res); VERIFY(a.size() == 7); VERIFY(it->first == X{"dej"}); VERIFY(it->second == Y{"o4p"}); VERIFY(o.s.empty()); VERIFY(p.s.empty()); + VERIFY(!y.copied_from); } { - auto [it, res] = a.try_emplace(std::move(dek), Y("q"), Y("r")); + Y y{"dek"}; + auto [it, res] = a.try_emplace(std::move(y), Y("q"), Y("r")); VERIFY(res); VERIFY(a.size() == 8); - VERIFY(dek.s.empty()); + VERIFY(y.s.empty()); VERIFY(it->first == X{"dek"}); VERIFY(it->second == Y{"q4r"}); + VERIFY(!y.copied_from); } } { // Succeed, move @@ -190,16 +223,20 @@ void test_try_emplace() VERIFY(a.size() == 4); VERIFY(y.s.empty()); // moved from VERIFY(z.s.empty()); // moved from + VERIFY(!y.copied_from); + VERIFY(!z.copied_from); } { // Hinted, fail auto a = amap; Y y{"def"}, z{"xyz"}; - auto it = a.try_emplace(a.begin(), std::move(y), std::move(z)); + auto it = a.try_emplace(a.begin(), std::move(y), z); VERIFY(a.size() == 3); VERIFY(it->first == X{"def"}); VERIFY(it->second == Y{2}); VERIFY(y.s.size() == 3); // not moved from VERIFY(z.s.size() == 3); // not moved from + VERIFY(!y.copied_from); + VERIFY(!z.copied_from); } { // Hinted, fail, move auto a = amap; @@ -210,48 +247,66 @@ void test_try_emplace() VERIFY(it->second == Y{2}); VERIFY(y.s.size() == 3); // not moved from VERIFY(z.s.size() == 3); // not moved from + VERIFY(!y.copied_from); + VERIFY(!z.copied_from); } { // Hinted, succeed, construct auto a = amap; - Y m("m"), n("n"), o("o"), p("p"), dek("dek"); + Y m("m"), n("n"), o("o"), p("p"); { - auto it = a.try_emplace(a.begin(), Y{"deg"}, m, n); + Y y{"deg"}; + auto it = a.try_emplace(a.begin(), y, m, n); VERIFY(a.size() == 4); VERIFY(it->first == X{"deg"}); VERIFY(it->second == Y{"m1n"}); VERIFY(m.s.size() == 1); VERIFY(n.s.size() == 1); + VERIFY(y.s.size() == 3); + VERIFY(y.copied_from); } { - auto it = a.try_emplace(a.begin(), Y{"deh"}, m, std::move(n)); + Y y{"deh"}; + auto it = a.try_emplace(a.begin(), y, m, std::move(n)); VERIFY(a.size() == 5); VERIFY(it->first == X{"deh"}); VERIFY(it->second == Y{"m2n"}); VERIFY(m.s.size() == 1); VERIFY(n.s.empty()); + VERIFY(y.s.size() == 3); + VERIFY(y.copied_from); } { - auto it = a.try_emplace(a.begin(), Y{"dei"}, std::move(m), o); + Y y{"dei"}; + auto it = a.try_emplace(a.begin(), y, std::move(m), o); VERIFY(a.size() == 6); VERIFY(it->first == X{"dei"}); VERIFY(it->second == Y{"m3o"}); VERIFY(m.s.empty()); VERIFY(o.s.size() == 1); + VERIFY(y.s.size() == 3); + VERIFY(y.copied_from); } { - auto it = a.try_emplace(a.begin(), Y{"dej"}, std::move(o), std::move(p)); + Y y{"dej"}; + auto it = a.try_emplace(a.begin(), y, std::move(o), std::move(p)); VERIFY(a.size() == 7); VERIFY(it->first == X{"dej"}); VERIFY(it->second == Y{"o4p"}); VERIFY(o.s.empty()); VERIFY(p.s.empty()); + VERIFY(y.s.size() == 3); + VERIFY(y.copied_from); } { - auto it = a.try_emplace(a.begin(), std::move(dek), Y("q"), Y("r")); + Y y{"dek"}; + auto it = a.try_emplace(a.begin(), std::move(y), Y("q"), Y("r")); VERIFY(a.size() == 8); - VERIFY(dek.s.empty()); + VERIFY(y.s.empty()); VERIFY(it->first == X{"dek"}); VERIFY(it->second == Y{"q4r"}); + VERIFY(it->second == Y{"q4r"}); + VERIFY(y.s.empty()); // moved from + VERIFY(!y.copied_from); } } { // Hinted, succeed, move @@ -262,7 +317,9 @@ void test_try_emplace() VERIFY(it->second == Y{"xyz"}); VERIFY(a.size() == 4); VERIFY(y.s.empty()); // moved from + VERIFY(!y.copied_from); VERIFY(z.s.empty()); // moved from + VERIFY(!z.copied_from); } } @@ -279,7 +336,9 @@ void test_insert_or_assign() VERIFY(a.size() == 3); VERIFY(a.at(Y{"def"}) == Y{"xyz"}); VERIFY(y.s.size() == 3); // not moved from + VERIFY(!y.copied_from); VERIFY(z.s.size() == 3); // not moved from + VERIFY(z.copied_from); } { // Already there, move auto a = amap; @@ -289,7 +348,9 @@ void test_insert_or_assign() VERIFY(a.size() == 3); VERIFY(a.at(Y{"def"}) == Y{"xyz"}); VERIFY(y.s.size() == 3); // not moved from + VERIFY(!y.copied_from); VERIFY(z.s.empty()); // moved from + VERIFY(!z.copied_from); } { // Succeed, move auto a = amap; @@ -300,7 +361,9 @@ void test_insert_or_assign() VERIFY(it->second == Y{"xyz"}); VERIFY(a.size() == 4); VERIFY(y.s.empty()); // moved from + VERIFY(!y.copied_from); VERIFY(z.s.empty()); // moved from + VERIFY(!z.copied_from); } { // Hinted, already there, replace auto a = amap; @@ -310,7 +373,9 @@ void test_insert_or_assign() VERIFY(it->first == X{"def"}); VERIFY(it->second == Y{"xyz"}); VERIFY(y.s.size() == 3); // not moved from + VERIFY(!y.copied_from); VERIFY(z.s.size() == 3); // not moved from + VERIFY(z.copied_from); } { // Hinted, already there, move auto a = amap; @@ -320,7 +385,9 @@ void test_insert_or_assign() VERIFY(it->first == X{"def"}); VERIFY(it->second == Y{"xyz"}); VERIFY(y.s.size() == 3); // not moved from + VERIFY(!y.copied_from); VERIFY(z.s.empty()); // moved from + VERIFY(!z.copied_from); } { // Hinted, succeed auto a = amap; @@ -330,7 +397,9 @@ void test_insert_or_assign() VERIFY(it->second == Y{"xyz"}); VERIFY(a.size() == 4); VERIFY(y.s.size() == 3); // not moved from + VERIFY(y.copied_from); VERIFY(z.s.size() == 3); // not moved from + VERIFY(z.copied_from); } { // Hinted, succeed, move auto a = amap; @@ -340,7 +409,9 @@ void test_insert_or_assign() VERIFY(it->second == Y{"xyz"}); VERIFY(a.size() == 4); VERIFY(y.s.empty()); // moved from + VERIFY(!y.copied_from); VERIFY(z.s.empty()); // moved from + VERIFY(!z.copied_from); } }