Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upPrevent unwinding past FFI boundaries #46833
Conversation
rust-highfive
assigned
nikomatsakis
Dec 19, 2017
This comment has been minimized.
This comment has been minimized.
|
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @nikomatsakis (or someone else) soon. If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes. Please see the contribution instructions for more information. |
diwic
referenced this pull request
Dec 19, 2017
Closed
prevent unwinding past FFI boundaries in code generation #18510
This comment has been minimized.
This comment has been minimized.
|
|
arielb1
reviewed
Dec 19, 2017
| @@ -806,6 +806,7 @@ impl<'a, 'tcx> MutVisitor<'tcx> for Integrator<'a, 'tcx> { | |||
| *kind = TerminatorKind::Goto { target: tgt } | |||
| } | |||
| } | |||
| TerminatorKind::Abort => { unimplemented!("Not sure what to do here?!"); } | |||
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
arielb1
reviewed
Dec 19, 2017
|
|
||
| // FIXME: Figure out why we can't use something like this instead: | ||
| // tcx.is_foreign_item(tcx.hir.local_def_id(fn_id)); | ||
| // tcx.has_attr(tcx.hir.local_def_id(fn_id), "unwind"); |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
diwic
Dec 19, 2017
Author
Contributor
AFAICT, tcx.has_attr(tcx.hir.local_def_id(fn_id), "unwind") returns false also for "__rust_start_panic" and "panicking::rust_begin_panic".
I don't know why, all these different contexts and ids are a bit bewildering to me. My guess is that I'm trying with the wrong ID or something, but then I don't know what ID would be the right one.
This comment has been minimized.
This comment has been minimized.
diwic
Dec 21, 2017
Author
Contributor
Scratch this comment, I must have done something wrong. It does seem to work in the new - just pushed - version.
arielb1
reviewed
Dec 19, 2017
| // Therefore generate an extra "Abort" landing pad. | ||
|
|
||
| // FIXME: Figure out why we can't use something like this instead: | ||
| // tcx.is_foreign_item(tcx.hir.local_def_id(fn_id)); |
This comment has been minimized.
This comment has been minimized.
arielb1
Dec 19, 2017
Contributor
is_foreign_item checks for foreign items, aka
extern "C" {
fn foreign_item(); // we don't generate MIR for this
}Rather than extern fns, aka
extern "C" fn extern_fn() {
// we *do* generate MIR for this
}
This comment has been minimized.
This comment has been minimized.
|
arielb1
reviewed
Dec 19, 2017
| pub fn schedule_abort(&mut self) -> BasicBlock { | ||
| self.scopes[0].needs_cleanup = true; | ||
| let abortblk = self.cfg.start_new_cleanup_block(); | ||
| self.cfg.terminate(abortblk, self.scopes[0].source_info(self.fn_span), TerminatorKind::Abort); |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
nikomatsakis
reviewed
Dec 19, 2017
| // tcx.is_foreign_item(tcx.hir.local_def_id(fn_id)); | ||
| // tcx.has_attr(tcx.hir.local_def_id(fn_id), "unwind"); | ||
|
|
||
| let is_foreign = match abi { |
This comment has been minimized.
This comment has been minimized.
nikomatsakis
Dec 19, 2017
Contributor
Actually, I think that an abi check is exactly the right thing. The key point is that the "C" ABI (and other non-Rust ABIs) don't have a defined way to propagate Rust panics, right?
This comment has been minimized.
This comment has been minimized.
diwic
Dec 19, 2017
Author
Contributor
I'm not so sure.
"__rust_start_panic" and "panicking::rust_begin_panic" are of C ABI and are able to panic, so it can't be that undefined...
Rather, the real danger is when we tell LLVM that a function is "nounwind" and then we end up panicking within - or through - it. That's the undefined behavior this patch is trying to resolve.
So, then I was trying to figure out when we actually mark a function as "nounwind", and it seems now I did not look closely enough. The algorithm seems to be:
- ABI check - so you're right, it should be an ABI check.
- Set as unwinding if there is an unwind attribute
- Set as unwinding if it isn't a foreign item
So maybe that's what I'm supposed to mimic, or possibly try to refactor somehow if we need it in two places?
This comment has been minimized.
This comment has been minimized.
nikomatsakis
Dec 19, 2017
Contributor
It seems like it would be ideal to have the criteria extracted into a helper function, yes.
This comment has been minimized.
This comment has been minimized.
arielb1
Dec 19, 2017
•
Contributor
I'm not sure extracting the criteria directly would help:
- Unwinding across a lang boundary is "instant" LLVM UB as we emit the
nounwindattribute. If we want to continue doing that, we can't also make it trap. - Therefore, we want to catch unwinding before we reach the lang boundary. That means stopping unwinding from proceeding from Rust to C, because we don't control the C-to-Rust lang boundary.
- This means that we need to prevent unwinding on non-foreign items, which means we need to ignore the code for (3) from the previous list.
Disabling the check that allows non-foreign C ABI Rust functions to unwind would allow us to make these functions abort on unwind.
This comment has been minimized.
This comment has been minimized.
nikomatsakis
Dec 20, 2017
Contributor
I must be confused about something. @arielb1 what do you mean by this:
Unwinding across a lang boundary is "instant" LLVM UB as we emit the nounwind attribute. If we want to continue doing that, we can't also make it trap.
Do you mean that the call is tagged with nounwind, or the function? I was assuming the latter.
This comment has been minimized.
This comment has been minimized.
arielb1
Dec 20, 2017
Contributor
It doesn't matter whether the call or the function are tagged as nounwind. In both cases, unwinding is UB LLVM-side and therefore can't be turned to an abort..
This comment has been minimized.
This comment has been minimized.
nikomatsakis
Dec 20, 2017
Contributor
I thnk we are talking past each other to a certain extent. Let me first define a set of functions I will call "border functions" -- i.e., functions implemented in Rust but which are invokable from C and hence have C ABI. For these functions, it is considered UB if they unwind (and hence these border functions may also be marked as "no-unwind"). In that case, when we generate the fn body, we can trap/abort if an unwind does occur. (This is, I believe, the same thing C++ does in such cases, though I may be mistaken.) This costs us nothing to the same extent that unwinding is "zero cost".
I guess you are saying that we should ignore the #[unwind] attribute for the purpose of this trap, and generate it anyway? This is (I guess) because C code may still call such a function? That sort of makes sense, though it does raise the question of the purpose of the #[unwind] attribute.
This comment has been minimized.
This comment has been minimized.
arielb1
Dec 20, 2017
Contributor
Sure enough. So for that we'll have to remove the "Set as unwinding if it isn't a foreign item" check.
kennytm
added
the
S-waiting-on-author
label
Dec 19, 2017
This comment has been minimized.
This comment has been minimized.
|
OK, so @arielb1 and I were chatting on gitter, and we came to roughly this conclusion:
Note though that this is a change in behavior -- albeit only quasi-defined behavior -- and it feels like it ought to go through the RFC process. Still, it'd be good to have a working implementation so that we can do a crater run and assess possible impact. So it may not be that there is a "common helper" to extract, that's not entirely clear to me. |
diwic
added some commits
Dec 19, 2017
diwic
force-pushed the
diwic:7c-abort-ffi
branch
from
5935057
to
f536143
Dec 21, 2017
This comment has been minimized.
This comment has been minimized.
Ok, so I think we're mostly on the same page w r t what needs to be done. I rebased it on top of master and skipped the "common helper" part.
Hmm, so I was thinking "what could this possibly break" and came up with this contrived example:
But even in this case; looking at the LLVM IR, we mark this function as EDIT: So what I wanted to say - is this ever a change in behavior where the previous behavior was not UB? |
This comment has been minimized.
This comment has been minimized.
|
@kennytm Is there a way I can remove the "waiting on author" tag, now that I've responded and so it is no longer waiting for me (but for CI and a new review pass)? |
This comment has been minimized.
This comment has been minimized.
|
Btw: Not sure about the current status of Also, |
kennytm
added
S-waiting-on-review
and removed
S-waiting-on-author
labels
Dec 21, 2017
This comment has been minimized.
This comment has been minimized.
|
@diwic Retagged :) If this PR is no longer a work-in-progress, please also remove the "WIP" from the title. |
This comment has been minimized.
This comment has been minimized.
|
Why should |
arielb1
reviewed
Dec 21, 2017
| @@ -383,6 +405,11 @@ fn construct_fn<'a, 'gcx, 'tcx, A>(hir: Cx<'a, 'gcx, 'tcx>, | |||
| let source_info = builder.source_info(span); | |||
| let call_site_s = (call_site_scope, source_info); | |||
| unpack!(block = builder.in_scope(call_site_s, LintLevel::Inherited, block, |builder| { | |||
|
|
|||
This comment has been minimized.
This comment has been minimized.
arielb1
reviewed
Dec 21, 2017
| @@ -353,6 +354,27 @@ macro_rules! unpack { | |||
| }; | |||
| } | |||
|
|
|||
| fn needs_abort_block<'a, 'gcx, 'tcx>(tcx: TyCtxt<'a, 'gcx, 'tcx>, | |||
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
r=me with nits addressed |
arielb1
changed the title
[WIP] prevent unwinding past FFI boundaries
Prevent unwinding past FFI boundaries
Dec 21, 2017
This comment has been minimized.
This comment has been minimized.
Good point. FFI can call Rust functions too with the wrong calling convention, this is no worse really. Nits addressed. |
This comment has been minimized.
This comment has been minimized.
|
@bors r+ |
This comment has been minimized.
This comment has been minimized.
|
|
diwic commentedDec 19, 2017
•
edited
Second attempt to write a patch to solve this.
r? @nikomatsakis
So, my biggest issue with this patch is the way the patch determines what functions should have an abort landing pad (inFIXEDconstruct_fn). I would ideally have this code match src/librustc_trans/callee.rs::get_fn but couldn't find an id that returns true foris_foreign_item. Also triedtcx.has_attr("unwind")with no luck.Other issues:
llvm.trap is an SIGILL on amd64. Ideally we could use panic-abort's version of aborting which is nicer but we don't want to depend on that library...
Mir inlining is a stub currently.FIXED (no-op)Also, when reviewing please take into account that I'm new to the code and only partially know what I'm doing... and that I've mostly made made matches on
TerminatorKind::Abortmatch eitherTerminatorKind::ResumeorTerminatorKind::Unreachablebased on what looked best.