)]}'
{
  "commit": "d5c2f53dfc56c6d0d65a93a5e303c16a046e5fe6",
  "tree": "41de3e5eb7ab1a236a787c229579323a8a1d41fc",
  "parents": [
    "c7836d2ff5abab2dbb8220b629cac64c949ee607"
  ],
  "author": {
    "name": "Fengxiaoxx",
    "email": "43958531+Fengxiaoxx@users.noreply.github.com",
    "time": "Wed Sep 30 14:23:52 2026 -0700"
  },
  "committer": {
    "name": "Copybara-Service",
    "email": "copybara-worker@google.com",
    "time": "Wed Sep 30 14:24:41 2026 -0700"
  },
  "message": "PR #2175: Fix use-after-free in linked_hash_set and linked_hash_map move assignment\n\nImported from GitHub PR https://github.com/abseil/abseil-cpp/pull/2175\n\n## Issue\n\nFixes #2174.\n\n`linked_hash_set` and `linked_hash_map` move assignment with an unequal,\nnon-propagating allocator leaves the internal `flat_hash_set` of `std::list`\niterators pointing at the source\u0027s nodes, which the source then frees\n(`heap-use-after-free`; SIGSEGV without a sanitizer). For\n`linked_hash_map` the same configuration does not compile at all.\n\n## Root cause\n\n`operator\u003d(linked_hash_set\u0026\u0026)` (and the map equivalent) unconditionally did\n\n```cpp\nset_ \u003d std::move(other.set_);\nlist_ \u003d std::move(other.list_);\nother.set_.clear();\nother.list_.clear();\n```\n\nWhen `propagate_on_container_move_assignment` is false and the allocators\ncompare unequal, `std::list` cannot take over the source\u0027s nodes and\nre-creates them with the destination\u0027s allocator, so the iterators taken\nover with `set_` dangle as soon as `other.list_.clear()` runs. `std::list`\u0027s\nmove assignment additionally element-assigns in that case, which does not\ncompile for the map\u0027s `std::pair\u003cconst K, V\u003e` value type.\n\n## Fix\n\nDispatch on `propagate_on_container_move_assignment`:\n\n* **true**: propagate the allocator and use the component moves as before;\n* **false, equal allocators**: take over the nodes with `clear()` +\n  `splice()` (splice relinks the nodes instead of element-assigning, which is\n  also what makes this compile for the map);\n* **false, unequal allocators**: move each element into a new node allocated\n  with the destination\u0027s allocator and rebuild the index over those nodes\n  with the source\u0027s hash and equality state, mirroring\n  `raw_hash_set::move_assign`.\n\nThe operator is no longer unconditionally `noexcept`; it now uses the same\nspecification as `raw_hash_set::operator\u003d(raw_hash_set\u0026\u0026)`:\n`is_always_equal \u0026\u0026 is_nothrow_move_assignable\u003chasher\u003e \u0026\u0026\nis_nothrow_move_assignable\u003ckey_equal\u003e`. This is an API change for callers\nwhose allocator is not always equal: the operator can now allocate and\nthrow. It can be split out of this change if preferred.\n\n## Tests\n\nThree tests in `linked_hash_set_test.cc` and three in\n`linked_hash_map_test.cc`, in the style of\n`ExtractAndEmplaceUseSameStatefulAllocator`. Unequal allocators come from\n`CountingAllocator` instances with different state pointers, and\n`MoveAssignPropagatingCountingAlloc` provides a propagating allocator:\n\n* `MoveAssignWithUnequalNonPropagatingAllocator`: empty and non-empty\n  destinations; checks `contains`, `find`, `erase`, iteration order, that the\n  destination\u0027s old elements are gone, and that the source is empty;\n* `MoveAssignWithEqualAllocatorTakesOverNodes`: the nodes and the index are\n  taken over without allocating;\n* `MoveAssignWithPropagatingAllocatorPropagates`: the allocator is\n  propagated even when the allocators are unequal.\n\n## Verification\n\n* New tests, unpatched: the set test aborts under ASan with\n  `heap-use-after-free` (`Wrapped::operator()` at `linked_hash_set.h:98`,\n  reached from the test at `linked_hash_set_test.cc:878`) and SIGSEGVs\n  without a sanitizer; the map test does not compile (`std::list` element\n  assignment of `std::pair\u003cconst int, int\u003e`).\n* New tests, patched: pass, plain and ASan.\n* Full suites, patched: `linked_hash_set_test` 219/219 (plain) and 218\n  passed + 1 skipped (ASan); `linked_hash_map_test` 341/341 (plain) and 340\n  passed + 1 skipped (ASan); `raw_hash_set_test` and\n  `raw_hash_set_allocator_test` pass under ASan (no fallout).\n* The control/trigger PoCs from the issue pass with the patch under both\n  libstdc++ (clang 14) and libc++ (clang 22).\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nMerge e0a08bbe766042f88cebc12d4b0f6491699c73c3 into e7d5000d632dc1c3f32477b027bf0384b1747325\n\nMerging this change closes #2175\n\nPiperOrigin-RevId: 991220704\nChange-Id: I501a52d9ae51da76934e8c9f38176d5b227f015c\n",
  "tree_diff": [
    {
      "type": "modify",
      "old_id": "6d0e320cafc7da61a57b5b97308ad5197bc4f963",
      "old_mode": 33188,
      "old_path": "absl/container/linked_hash_map.h",
      "new_id": "d04807bf617018d1541b1bb32c6e837bcc71bd6d",
      "new_mode": 33188,
      "new_path": "absl/container/linked_hash_map.h"
    },
    {
      "type": "modify",
      "old_id": "a905e920c4cdc4545beb844a4225652f0bd44e30",
      "old_mode": 33188,
      "old_path": "absl/container/linked_hash_map_test.cc",
      "new_id": "ef03e13884cb27444dec942e09942a8571b9ec6d",
      "new_mode": 33188,
      "new_path": "absl/container/linked_hash_map_test.cc"
    },
    {
      "type": "modify",
      "old_id": "7fe0adc09d68dea6c51ff1ff53326b77b5e2bc5d",
      "old_mode": 33188,
      "old_path": "absl/container/linked_hash_set.h",
      "new_id": "10ba28976cfb71fada53970f192d10a3ef94994b",
      "new_mode": 33188,
      "new_path": "absl/container/linked_hash_set.h"
    },
    {
      "type": "modify",
      "old_id": "340d6e3f5930b19e1ca9c32c194d508eb17fdc1c",
      "old_mode": 33188,
      "old_path": "absl/container/linked_hash_set_test.cc",
      "new_id": "5e4c176a5bc2058643e4203d262852996b376119",
      "new_mode": 33188,
      "new_path": "absl/container/linked_hash_set_test.cc"
    }
  ]
}
