-
Notifications
You must be signed in to change notification settings - Fork 12.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
BTreeMap::drain_filter should not touch the root during iteration #74762
BTreeMap::drain_filter should not touch the root during iteration #74762
Conversation
(rust_highfive has picked a reviewer for you, use r? to override) |
☔ The latest upstream changes (presumably #73265) made this pull request unmergeable. Please resolve the merge conflicts. |
059e9ca
to
b51be5f
Compare
b51be5f
to
99398dd
Compare
I am still not certain that the UB is there, but it seems like the related code cleanups in this PR are good regardless. @bors r+ |
📌 Commit 99398dd has been approved by |
You're probably right. I assumed that Note that the earlier commit simplified code a little more, but recent benchmarking said consistently this costs 5 to 10%, so I passed the |
Rollup of 10 pull requests Successful merges: - rust-lang#74686 (BTreeMap: remove into_slices and its unsafe block) - rust-lang#74762 (BTreeMap::drain_filter should not touch the root during iteration) - rust-lang#74781 (Clean up E0733 explanation) - rust-lang#74874 (BTreeMap: define forget_type only when relevant) - rust-lang#74974 (Make tests faster in Miri) - rust-lang#75010 (Update elasticlunr-rs and ammonia transitive deps) - rust-lang#75041 (Replaced log with tracing crate) - rust-lang#75044 (Clean up E0744 explanation) - rust-lang#75054 (Rename rustc_middle::cstore::DepKind to CrateDepKind) - rust-lang#75057 (Avoid dumping rustc invocations to stdout) Failed merges: - rust-lang#74827 (Move bulk of BTreeMap::insert method down to new method on handle) r? @ghost
…ulacrum BTreeMap: better way to postpone root access in DrainFilter A slightly more elegant (in my opinion) adaptation of rust-lang#74762. Benchmarks seem irrationally pleased to: ``` benchcmp old new --threshold 5 name old ns/iter new ns/iter diff ns/iter diff % speedup btree::map::clone_fat_100_and_remove_all 215,182 185,052 -30,130 -14.00% x 1.16 btree::map::clone_fat_100_and_remove_half 139,667 127,945 -11,722 -8.39% x 1.09 btree::map::clone_fat_val_100_and_remove_all 96,755 81,279 -15,476 -16.00% x 1.19 btree::map::clone_fat_val_100_and_remove_half 64,678 56,911 -7,767 -12.01% x 1.14 btree::map::find_rand_100 18 17 -1 -5.56% x 1.06 btree::map::first_and_last_0 33 35 2 6.06% x 0.94 btree::map::first_and_last_100 40 54 14 35.00% x 0.74 btree::map::insert_rand_100 45 42 -3 -6.67% x 1.07 btree::map::insert_rand_10_000 45 41 -4 -8.89% x 1.10 btree::map::iter_0 2,010 1,759 -251 -12.49% x 1.14 btree::map::iter_100 3,514 2,764 -750 -21.34% x 1.27 btree::map::iter_10k 4,018 3,768 -250 -6.22% x 1.07 btree::map::range_unbounded_unbounded 37,269 28,929 -8,340 -22.38% x 1.29 btree::map::range_unbounded_vs_iter 31,518 28,814 -2,704 -8.58% x 1.09 ``` r? @Mark-Simulacrum
Illustration of the alleged undefined behaviour under stacked borrows rules:Say we have a 2-level map with 11 keys -5..=5. The root has key 0, a left child with keys -5..=-1, a right child with keys 1..=5. You won't get this 2nd level inserting 11 elements from scratch, but you can get there removing an element from a larger map. We start
or virtually:
That's a raw pointer access (unless there's something like #73987 going on). If we turn it into a "real" |
…mulacrum Revert the fundamental changes in rust-lang#74762 and rust-lang#75257 Before possibly going over to rust-lang#75487. Also contains some added and fixed comments. r? @Mark-Simulacrum
Although Miri doesn't point it out, I believe there is undefined behaviour using
drain_filter
when draining the 11th-last element from a tree that was larger. When this happens, the last remaining child nodes are merged, the root becomes empty and is popped from the tree. That last step establishes a mutable reference to the node elected root and writes a pointer innode::Root
, while iteration continues to visit the same node.This is mostly code from #74437, slightly adapted.