-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Fix drop-tracking ICE when a struct containing a field with a significant drop is used across an await #98754
Conversation
let scope = if self.drop_ranges.is_borrowed_temporary(expr) | ||
|| ty.map_or(true, |ty| ty.has_significant_drop(self.fcx.tcx, self.fcx.param_env)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm. this is probably more coarse than necessary - it will also trigger for things like let x = String::new(); drop(x)
. I don't know a better way to do this - I guess this is what you were talking about in DMs?
eholk: But I think maybe the conservative thing to do is to count the field access (.nickname) as a borrow of the Config temporary, so we should add it to maybe_borrowed_temporaries. Then I think that should make the whole Config live for the whole temporary scope (until the semicolon). It's more conservative than it needs to be, since .nickname has been dropped but this would still consider it live since it's part of the type of Config, but it should be sound.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh, it's still going to use the temporary scope though - I guess the proper test case is something like
f(String::new()).await
where the future doesn't actually use the string
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh lol while debugging this I found that there's another existing bug where we're too coarse: for non-Send types with trivial drop, we still consider them held across the generator:
async fn trivial_drop() {
// uncomment to make this program compile
// {
let x: *const usize = &0;
// }
async {}.await;
}
fn assert_send<T: Send>(_: T) {}
fn main() {
assert_send(trivial_drop());
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tried to fix this but got an ICE :( I think I will put this off until later
diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs
index 946bfa52d37..5caa1b2ded7 100644
--- a/compiler/rustc_typeck/src/check/generator_interior.rs
+++ b/compiler/rustc_typeck/src/check/generator_interior.rs
@@ -387,7 +387,10 @@ fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
// temporary on the stack that is live for the current temporary scope and then return a
// reference to it. That value may be live across the entire temporary scope.
let scope = if self.drop_ranges.is_borrowed_temporary(expr)
- || ty.map_or(true, |ty| ty.has_significant_drop(self.fcx.tcx, self.fcx.param_env))
+ || ty.map_or(true, |ty| {
+ !ty.needs_drop(self.fcx.tcx, self.fcx.param_env)
+ || ty.has_significant_drop(self.fcx.tcx, self.fcx.param_env)
+ })
{
self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id)
} else {
thread 'rustc' panicked at 'index out of bounds: the len is 0 but the index is 13', /home/jnelson/.local/lib/cargo/registry/src/github.com-1ecc6299db9ec823/ena-0.14.0/src/snapshot_vec.rs:199:10
21: 0x7f12f71e004b - ena::unify::UnificationTable<S>::value::h616d0c0c6ae4057b
at /home/jnelson/.local/lib/cargo/registry/src/github.com-1ecc6299db9ec823/ena-0.14.0/
src/unify/mod.rs:347:10
22: 0x7f12f71e004b - ena::unify::UnificationTable<S>::inlined_get_root_key::hea2bf8b4576b5472
at /home/jnelson/.local/lib/cargo/registry/src/github.com-1ecc6299db9ec823/ena-0.14.0/
src/unify/mod.rs:362:19
23: 0x7f12f71e004b - ena::unify::UnificationTable<S>::inlined_probe_value::hc1dc88e31da4bcc9
at /home/jnelson/.local/lib/cargo/registry/src/github.com-1ecc6299db9ec823/ena-0.14.0/
src/unify/mod.rs:566:18
24: 0x7f12f71e004b - rustc_infer::infer::type_variable::TypeVariableTable::inlined_probe::h29b4e108ffacbc08
at /home/jnelson/rust-lang/rust/compiler/rustc_infer/src/infer/type_variable.rs:312:9
25: 0x7f12f71e004b - rustc_infer::infer::type_variable::TypeVariableTable::probe::h91a8d70bb6dc3e56
at /home/jnelson/rust-lang/rust/compiler/rustc_infer/src/infer/type_variable.rs:306:9
26: 0x7f12f717fe34 - rustc_infer::infer::InferCtxt::probe_ty_var::hefc9501f755a1a65
at /home/jnelson/rust-lang/rust/compiler/rustc_infer/src/infer/mod.rs:1388:15
27: 0x7f12f71d7089 - <rustc_infer::infer::canonical::canonicalizer::Canonicalizer as rustc_middle::ty::fold::Ty
peFolder>::fold_ty::h0ebb9c6ed6bc4918
at /home/jnelson/rust-lang/rust/compiler/rustc_infer/src/infer/canonical/canonicalizer
.rs:394:23
...
query stack during panic:
#0 [is_copy_raw] computing whether `[static generator@src/test/ui/async-await/non-trivial-drop-unused.rs:11:11: 11:13
]` is `Copy`
#1 [needs_drop_raw] computing whether `[static generator@src/test/ui/async-await/non-trivial-drop-unused.rs:11:11: 11
:13]` needs drop
#2 [typeck] type-checking `trivial_drop`
#3 [mir_built] building MIR for `trivial_drop`
#4 [unsafety_check_result] unsafety-checking `trivial_drop`
#5 [mir_const] processing MIR for `trivial_drop`
#6 [mir_promoted] processing `trivial_drop`
#7 [mir_borrowck] borrow-checking `trivial_drop`
#8 [type_of] computing type of `trivial_drop::{opaque#0}`
#9 [check_mod_item_types] checking item types in top-level module
#10 [analysis] running analysis passes on this crate
end of query stack
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess the proper test case is something like
looks like this already errors today:
use std::rc::Rc;
async fn ignore_param(x: Rc<i32>) {
drop(x);
async {}.await;
}
fn assert_send<T: Send>(_: T) {}
fn main() {
assert_send(ignore_param(Rc::new(0)));
}
$ rustc +nightly src/test/ui/async-await/non-trivial-drop-unused.rs --edition 2018 -Zdrop-tracking
error: future cannot be sent between threads safely
--> src/test/ui/async-await/non-trivial-drop-unused.rs:19:17
|
19 | assert_send(ignore_param(Rc::new(0)));
| ^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `ignore_param` is not `Send`
|
= help: within `impl Future<Output = ()>`, the trait `Send` is not implemented for `Rc<i32>`
note: captured value is not `Send`
--> src/test/ui/async-await/non-trivial-drop-unused.rs:6:23
|
6 | async fn ignore_param(x: Rc<i32>) {
| ^ has type `Rc<i32>` which is not `Send`
note: required by a bound in `assert_send`
--> src/test/ui/async-await/non-trivial-drop-unused.rs:16:19
|
16 | fn assert_send<T: Send>(_: T) {}
| ^^^^ required by this bound in `assert_send`
that also seems wrong, for some reason it considers the param held for the whole async function even if you drop it earlier this seems correct actually, the inner async block is Send but the outer one generated by the function desugaring is not. I tested with a non-async function and it compiles ok after this PR:
fn ignore_param(x: Rc<i32>) -> impl Future + Send {
drop(x);
async {}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's because we still need to normalize projections -- also, reminder that i said type variables, not type parameters. type variables don't have anything to do with param envs.
note a similar difference between Infcx::type_is_copy_modulo_regions and Ty::is_copy_modulo_regions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type (inference) variables are associated to a given InferCtxt, so unless the function is associated with one of those, it's not typically safe to pass a Ty to it unless you know that we're past the type-checking phase or we're outside of a function body.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks, using !self.fcx.inh.infcx.resolve_vars_if_possible(ty).needs_drop(self.fcx.tcx, self.fcx.param_env)
fixed the ICE (but didn't allow the code to compile for reasons I don't understand - going to stop harping on it in this PR since it's not related to the current fix)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: fcx derefs to infcx so you should just be able to do fcx.resolve_vars_if_possible
also that'll still ice if you have any region variables, so you might need to pass it through erase_regions for types that have lifetimes and also significant drops
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, figured this out: #99105
... apparently this fixes #98477 too ?? I'll add a test. |
This comment has been minimized.
This comment has been minimized.
This is awesome! I love it when it's just a small thing that was overlooked that fixes a bunch of different bugs. (I don't know if this will work, but I'm going to try anyway...) |
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
hm, I'm not confident that this is the right solution...? Though I am only barely familiar with it.
@jyn514, what happened to this convo that you mentioned above? |
but anyways if eholk is ok w/ the change then that's fine @bors delegate=eholk |
✌️ @eholk can now approve this pull request |
That was a conversation we had as a DM on Zulip (I guess in retrospect we probably should have discussed it in #wg-async). This change seems reasonable to me. This is related to #94309, where we changed the scope we considered values live for to be smaller in some circumstances. Basically there the change was to use the parent expression as the scope, rather the temporary scope, unless the value was borrowed at some point (since the MIR counterpart to generator_interior is really conservative around borrows). It turns out that was a little too aggressive because if the value has a non-trivial drop, we need to consider it live until the destructor runs at the end of the temporary scope. Let me know if that explanation makes sense. Some of the finer points of this gets pretty tricky and making sure I can explain it well helps me feel more confident in it. |
I think I understand. I guess the one part I am somewhat suspicious of is the usage of
|
Ah, good question. Let me read up a little more on that and figure out which one is more appropriate. (@jyn514 - I noticed at one point you used both? Do you know what the difference is?) |
I think we should be using If either of y'all need me to investigate how to get |
☔ The latest upstream changes (presumably #98482) made this pull request unmergeable. Please resolve the merge conflicts. |
I am not convinced we should be using
"non-copy" is not the right criteria here; we want this code (from #94309) to compile, even if fn status(_client_status: &Client) -> i16 {
200
}
fn main() {
let client = Client;
let g = move || match status(&client) {
_status => yield,
};
assert_send(g);
}
figured this out: I was thinking about |
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
Ok, I pushed a test which shows why we need to use |
This comment was marked as resolved.
This comment was marked as resolved.
Ok, done. This also means the diagnostics for #97332 haven't been improved unfortunately. @rustbot ready |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
r=me with tiny nit applied and if @eholk is happy with these changes too
…impl is used across an await Previously, drop-tracking would incorrectly assume the struct would be dropped immediately, which was not true: when the field had a type with a manual `Drop` impl, the drop becomes observable and has to be dropped after the await instead. For reasons I don't understand, this also fixes another error crater popped up related to type parameters. rust-lang#98476
Looks good to me! Trying to be precise but not more precise than MIR has been a struggle with this feature :) @bors r=compiler-errors |
…-errors Fix drop-tracking ICE when a struct containing a field with a significant drop is used across an await Previously, drop-tracking would incorrectly assume the struct would be dropped immediately, which was not true. Fixes rust-lang#98476. Also fixes rust-lang#98477, I think because the parent HIR node for type variables is the whole function instead of the expression where the variable is used. r? `@eholk`
@bors rollup=never |
I don't see how @bors r=compiler-errors |
☀️ Test successful - checks-actions |
Finished benchmarking commit (8a392a5): comparison url. Instruction countThis benchmark run did not return any relevant results for this metric. Max RSS (memory usage)Results
CyclesResults
If you disagree with this performance assessment, please file an issue in rust-lang/rustc-perf. @rustbot label: -perf-regression Footnotes |
The max-rss changes are all related to linking. Either the symbol names for generators have changed (very possible if we encode the types into the monomorphized name for some reason) or the benchmarks are noisy. |
Previously, drop-tracking would incorrectly assume the struct would be dropped immediately, which was not true.
Fixes #98476. Also fixes #98477, I think because the parent HIR node for type variables is the whole function instead of the expression where the variable is used.
r? @eholk