Skip to content
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

Erase late bound regions for dropped instances #107936

Conversation

Rattenkrieg
Copy link
Contributor

Fixes #107205

It may happen that a symbol for vtable shim will be instantiated with ReLateBound regions, which will affect generation of symbol name in get_fn:

let sym = tcx.symbol_name(instance).name;

Thus, the name used to refere to the function will be inconsistent with the actual name emitted for monomorphized item:
let symbol_name = self.symbol_name(cx.tcx()).name;

This PR introduces late bounds erasing pass for DropGlue shims before its instantiations.

Open questions:

  • Should we erase other TyKinds other than FnPtr? If so, should we introduce generic logic, say in terms of TypeFolders? I found one suitable for such needs in the hir_analyis sources and gave it a try only to see how stdlib fails to link - I'm going to craft some reproducers for that later.

@rustbot
Copy link
Collaborator

rustbot commented Feb 11, 2023

r? @michaelwoerister

(rustbot has picked a reviewer for you, use r? to override)

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Feb 11, 2023
} else {
ty
};
let instance = ty::Instance::resolve_drop_in_place(tcx, ty_erased);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More simply, should resolve_drop_in_place call tcx.erase_regions on its argument?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something I thought about adding to the list of open questions, but forgot as soon as I wrote the first one 🐟.

@rust-log-analyzer

This comment has been minimized.

@Rattenkrieg
Copy link
Contributor Author

Apparently there are issues building stdlb on linux that have been introduced by the changes made in this PR. I didn't encounter this on apple aarch64 while working on this PR. I will try to reproduce this on a linux machine.

@@ -0,0 +1,13 @@
trait Marker {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test case is different from the original issue reproducer and compiles on current stable unless I'm missing something important: https://play.rust-lang.org/?version=stable&mode=release&edition=2021&gist=0bb75ef9d55af8608afe6ddfaa054bd3

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to reduce repro so that there would be no dependencies on stdlib traits. By the time I started working on this ~2 weeks ago this code was failing on current master d117135 on aarch64 apple. I can't recall whether I tested it in playground or not though. I've checked that original repro still causing linker to fail, so I'm going to deal with linux issues first and then get back to the repro. Thanks for pointing this out!

@rust-log-analyzer

This comment has been minimized.

@Rattenkrieg
Copy link
Contributor Author

I was able to reproduce it with simple ./x.py build --stage 2 compiler/rustc on both linux and mac. I was using simple ./x.py build library before, that happens to build only std with the stage 1.

@rust-log-analyzer

This comment has been minimized.

@Rattenkrieg Rattenkrieg changed the title [WIP] Erase late bound regions for vtable entries Erase late bound regions for dropped instances Feb 12, 2023
@michaelwoerister
Copy link
Member

Thanks for the PR! I'll take a closer look shortly.

@bors
Copy link
Contributor

bors commented Feb 13, 2023

☔ The latest upstream changes (presumably #107924) made this pull request unmergeable. Please resolve the merge conflicts.

@rust-log-analyzer

This comment has been minimized.

@Rattenkrieg Rattenkrieg force-pushed the erase-late-bound-regions-for-vtable-entries branch from 4c3557f to 8f6d569 Compare February 14, 2023 07:45
@rustbot rustbot added the T-bootstrap Relevant to the bootstrap subteam: Rust's build system (x.py and src/bootstrap) label Feb 14, 2023
@rustbot
Copy link
Collaborator

rustbot commented Feb 14, 2023

These commits modify the Cargo.lock file. Random changes to Cargo.lock can be introduced when switching branches and rebasing PRs.
This was probably unintentional and should be reverted before this PR is merged.

If this was intentional then you can ignore this comment.

Some changes occurred in src/tools/cargo

cc @ehuss

@rust-log-analyzer

This comment has been minimized.

@Rattenkrieg Rattenkrieg force-pushed the erase-late-bound-regions-for-vtable-entries branch from 808e489 to c0b9601 Compare February 14, 2023 08:30
@Rattenkrieg Rattenkrieg force-pushed the erase-late-bound-regions-for-vtable-entries branch from c0b9601 to b00a3c0 Compare February 14, 2023 08:32
@rust-log-analyzer

This comment has been minimized.

Copy link
Member

@michaelwoerister michaelwoerister left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me -- but I have no good intuition about the question whether other types should be handled here too. I can certainly imagine a similar situation can arise for trait objects.


fn main() {
println!("{:?}", Wrapper(useful));
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you try to turn this into a build-pass UI test? That would be more lightweight than a run-make test. Here is an example:

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved it under tests/ui/coercion/ directory.

@michaelwoerister
Copy link
Member

r? types -- I don't have the context to answer the open question.

@rustbot rustbot assigned jackh726 and unassigned michaelwoerister Feb 17, 2023
Comment on lines +542 to +550
let ty_erased =
if let (true, TyKind::FnPtr(poly_fn_sig)) = (ty.has_late_bound_regions(), ty.kind()) {
let re_erased = tcx.erase_late_bound_regions(*poly_fn_sig);
let rebound = Binder::dummy(re_erased);
let reassembled = tcx.mk_fn_ptr(rebound);
reassembled
} else {
ty
};
Copy link
Contributor

@cjgillot cjgillot Feb 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let ty_erased =
if let (true, TyKind::FnPtr(poly_fn_sig)) = (ty.has_late_bound_regions(), ty.kind()) {
let re_erased = tcx.erase_late_bound_regions(*poly_fn_sig);
let rebound = Binder::dummy(re_erased);
let reassembled = tcx.mk_fn_ptr(rebound);
reassembled
} else {
ty
};
// Erase regions before calling resolve, as they must not change the resolution.
let ty_erased = tcx.erase_regions(ty);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I tried that but normalizing late bound regions is not enough in this case. There's still a mismatch because one symbol looks like core::ptr::drop_in_place::<for<'a> fn(&'a ())> and the other like core::ptr::drop_in_place::<fn(&())>.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed my suggestion to use erase_regions that should erase everything.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, not sure actually. It's probably the instance resolution code that needs to be fortified against leaking regions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed my suggestion to use erase_regions that should erase everything.

I believe this already happening without desired effect wrt to this bug: Instance::expect_resolve -> Instance::resolve -> Instance::resolve_opt_const_arg -> tcx.erase_regions(substs). Anyway, I'm going to give it a try.

Copy link
Member

@michaelwoerister michaelwoerister Feb 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed my suggestion to use erase_regions that should erase everything.

That's what I thought too, but it doesn't seem to be the case:

/// Returns an equivalent value with all free regions removed (note
/// that late-bound regions remain, because they are important for
/// subtyping, but they are anonymized and normalized as well)..
pub fn erase_regions(self, value: T) -> T

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just checked, it doesn't erase late bound regions.

Copy link
Contributor Author

@Rattenkrieg Rattenkrieg Feb 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> {
// because late-bound regions affect subtyping, we can't
// erase the bound/free distinction, but we can replace
// all free regions with 'erased.
//
// Note that we *CAN* replace early-bound regions -- the
// type system never "sees" those, they get substituted
// away. In codegen, they will always be erased to 'erased
// whenever a substitution occurs.
match *r {
ty::ReLateBound(..) => r,

this is the TypeFolder invoked during erase_regions

@jackh726
Copy link
Member

I'm not obviously sure if this is the correct approach.

Fn pointers with and without higher-ranked types are definitely different types. I'm not actually sure if we expect different vtables for them or not. If so, then I would imagine this is not correct. If not, then I suppose this is fine.

@bors
Copy link
Contributor

bors commented Feb 22, 2023

☔ The latest upstream changes (presumably #108340) made this pull request unmergeable. Please resolve the merge conflicts.

@Rattenkrieg
Copy link
Contributor Author

I'm not obviously sure if this is the correct approach.

Fn pointers with and without higher-ranked types are definitely different types. I'm not actually sure if we expect different vtables for them or not. If so, then I would imagine this is not correct. If not, then I suppose this is fine.

To my knowledge, lifetime regions are just a matter of type/borrow checker, and their bounds meant not to be a part of codegen stage. Otherwise monomorphization would have taken care of that, and as I mentioned in the original message, there is no special logic wrt regions in mono item symbol name generation.

let symbol_name = self.symbol_name(cx.tcx()).name;

I could be completely wrong in my understanding, but regardless of my assumptions, there is a mismatch between the callee and callsite names of the vtable functions.

@pnkfelix pnkfelix added the T-types Relevant to the types team, which will review and decide on the PR/issue. label Mar 23, 2023
@pnkfelix
Copy link
Member

Nominating for discussion at T-types meeting, to resolve the questions raised about how to handle cases like #107205

@rustbot label: I-types-nominated

@rustbot rustbot added the I-types-nominated Nominated for discussion during a types team meeting. label Mar 23, 2023
@BoxyUwU
Copy link
Member

BoxyUwU commented Mar 24, 2023

This PR is incorrect as is:
Erasing late bound regions is wrong, for<'a> fn(&'a ()) and fn(&'static ()) are different types not just from lifetimes but also to hir typeck and and TypeId. it is possible to implement the same trait for both types and have differing behaviour. In this specific case of fn ptrs they have no drop glue so its not observable

A targetted fix just for drop in place of fn ptrs is not enough, this problem is not restricted to fn ptrs or drop in place. From looking at rustc_codegen_ssa for a bit (all day 😭) this is a problem for all cases where we have mir like _7 = _8 and typeof(_8) is a supertype of typeof(_7) rather than equal to it. If we then unsize _7 we will create a vtable as if it had _8's type which is exploitable to create unsoundness (which is demonstrated in the original issue now)

@jackh726
Copy link
Member

jackh726 commented Apr 9, 2023

It would be helpful to create a MCVE of the program @BoxyUwU is talking about. (This is precisely the type of program I was thinking about originally)

That being said, I think marking this as waiting on team is the best label here, since it's types-nominated. It might be worth doing a deep dive to discuss (if nobody individually speaks up with a solution).

@jackh726 jackh726 added S-waiting-on-team Status: Awaiting decision from the relevant subteam (see the T-<team> label). and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-bootstrap Relevant to the bootstrap subteam: Rust's build system (x.py and src/bootstrap) labels Apr 9, 2023
@BoxyUwU
Copy link
Member

BoxyUwU commented Apr 9, 2023

a code example for unsoundness caused by this bug is present in the issue as a comment: #107205 (comment)

@lcnr
Copy link
Contributor

lcnr commented May 9, 2023

as stated above, erasing late bound regions is unsound as it can influence trait solving.

the underlying issue here seems to be is that we incorrectly track the types of values without updating the type when the value flows over a "subtyping edge".

looking into a fix for that and discussing it in #107205. Given that we will have to go with a completely separate approach to this PR I am going to remove the types nomination and close this issue.

Thank you for @Rattenkrieg for working on this, even if it didn't work out in the end.

@lcnr lcnr closed this May 9, 2023
@lcnr lcnr removed the I-types-nominated Nominated for discussion during a types team meeting. label May 9, 2023
@apiraino apiraino removed the S-waiting-on-team Status: Awaiting decision from the relevant subteam (see the T-<team> label). label May 25, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-types Relevant to the types team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

codegen generates vtables for a variable's supertype when unsizing sometimes