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

Should there be a warning at compile time when implementing Drop trait with creation of same type inside? #55388

Closed
tom7980 opened this issue Oct 26, 2018 · 13 comments · Fixed by #113902
Labels
A-lint Area: Lints (warnings about flaws in source code) such as unused_mut. E-mentor Call for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@tom7980
Copy link
Contributor

tom7980 commented Oct 26, 2018

I'm fairly new to Rust and have been working through the reference book recently, I've just covered the Drop trait implementation and thought it would be fun to see what happens if I create a new instance of the type I just dropped inside of the function.

Obviously this is completely nonsensical as nobody is going to do this but as you can probably tell this caused my stack to overflow and crash the program, would an error message at compile time be appropriate for something like this?

I can't see why anyone would want to initialise new variables inside of the drop trait but it's possible.

Example code of my implementation:

struct CustomSmartPointer {
    data: String,
}

impl CustomSmartPointer{
    fn new(s: String) -> CustomSmartPointer {
        CustomSmartPointer{
            data: s,
        }
    }
}

impl Drop for CustomSmartPointer {
    fn drop(&mut self){
        println!("Dropping & creating new pointer");
        let c = CustomSmartPointer::new(String::from("test"));
        println!("New pointer created in memory");
    }
}

fn main() {
    let c = CustomSmartPointer::new(String::from("test"));
}
@oli-obk
Copy link
Contributor

oli-obk commented Oct 26, 2018

cc @wesleywiser

Do you think your new MIR based unconditional recursion lint could be extended to also know about Drop calls?

@wesleywiser
Copy link
Member

@oli-obk I think that's totally possible. It should just be a matter of handling TerminatorKind::Drop

rust/src/librustc/mir/mod.rs

Lines 1032 to 1036 in 8ec22e7

Drop {
location: Place<'tcx>,
target: BasicBlock,
unwind: Option<BasicBlock>,
},

and treating it as a call to Drop::drop() for the item's type.

@oli-obk
Copy link
Contributor

oli-obk commented Oct 26, 2018

I'm guessing we should handle this in in

The type of the dropped item can probably be extracted from the Place

@oli-obk oli-obk added A-diagnostics Area: Messages for errors, warnings, and lints E-mentor Call for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion. labels Oct 27, 2018
@agnxy
Copy link
Contributor

agnxy commented Oct 31, 2018

Hi @oli-obk , I'd like to work on this to dive deep into rustc.

@oli-obk
Copy link
Contributor

oli-obk commented Oct 31, 2018

Great!

So there's already some advice up above, but I'll repeat what I think are the steps needed:

  1. write a test that should be causing this lint to be emitted
  2. add a match arm for TerminatorKind::Drop to
  3. obtain the drop type by calling place.ty(tcx, mir).to_ty(tcx)
  4. figure out if that type is already the real type we wanted or whether it's a pointer/reference to the real type. Use info! statements for that and pass RUST_LOG=rustc_mir::lints to the command line when you invoke the test
  5. if it's not the type, adjust it by extracting the correct type (e.g. using ty.builtin_deref(true)
  6. register a call to Drop::drop with the Self type set to the type you obtained
    • this is a little bit involved, you first need to get the lang item for the drop trait. Start off by calling https://doc.rust-lang.org/nightly/nightly-rustc/rustc/ty/struct.TyCtxt.html#method.lang_items and have a look at the docs for how to get the drop trait's DefId out of that
    • When you have the drop trait's DefId, get the DefId of its method (No idea how to do that off the top of my head, if you get stuck on this, don't hesitate to ask)
    • With the DefId of the Drop::drop method and the type to be dropped, you can use Instance::resolve to get an Instance for the concrete T::drop method. The resolve method takes a DefId and Substs, so you'll have to create substs with just the concreate type as an element. Look at some calls to mk_substs to see how to do that.

@tom7980
Copy link
Contributor Author

tom7980 commented Oct 31, 2018

  • When you have the drop trait, get the DefId of its method (No idea how to do that off the top of my head, if you get stuck on this, don't hesitate to ask)

It looks like this method in LanguageItems returns an option with the DefId inside of it, looks like it procedurally generates methods for all the kinds of items.

#[allow(dead_code)]
pub fn $method(&self) -> Option<DefId> {
self.items[$variant as usize]
}

including the drop_trait method:

https://doc.rust-lang.org/nightly/nightly-rustc/rustc/middle/lang_items/struct.LanguageItems.html#method.drop_trait

I think this only gives you the DefId of the Drop Trait though, not the method implemented on the type.

Not sure if this is helpful but I'm quite interested in how it all works so thought I'd do some digging. Look forward to seeing how it's all worked out though.

@agnxy
Copy link
Contributor

agnxy commented Nov 12, 2018

Hi @oli-obk @tom7980 ,
Thanks for your guidance.
I made a little progress with the following prototype.

let drop_type = location.ty(mir, tcx).to_ty(tcx);
// get the drop trait's DefId
let drop_trait = tcx.lang_items().drop_trait().unwrap();
// get DefId of its method
let drop_fn = tcx.associated_items(drop_trait).next().unwrap();

But I haven't figured out how to use mk_substs to create substs as a parameter of Instance::resolve. I tried something like tcx.mk_substs(std::iter::once(Kind::from(drop_type))) but got ICE when compiling std. And another question is, after we get an Instance, is the next step for TerminatorKind::Drop match arm similar with the following code?

if let Some(instance) = Instance::resolve(tcx,
Thanks a lot.

@tom7980
Copy link
Contributor Author

tom7980 commented Nov 12, 2018

Hi @agnxy

For your issue with the mk_substs, I found this code in the monomorphize.rs source. It looks like you can just call it with an array of items cast to an iter with the .iter().cloned() methods.

let substs = tcx.mk_substs([
        Kind::from(self_ty),
        Kind::from(sig.inputs()[0]),
    ].iter().cloned());

I imagine it could look something like this

let substs = tcx.mk_substs([Kind::from(drop_type)].iter().cloned());

As for the Instance, I'm unsure where to go from there I'd need to spend some time digging around. Let me know if this is useful at all hopefully oli can chime in to give a better explanation

@oli-obk
Copy link
Contributor

oli-obk commented Nov 12, 2018

but got ICE when compiling std

can you elaborate on the ICE? I'm guessing it's due to your unwrap calls?

is the next step for TerminatorKind::Drop match arm similar with the following code?

Kind of. I think you can factor out

let (call_fn_id, call_substs) =
if let Some(instance) = Instance::resolve(tcx,
param_env,
fn_def_id,
substs) {
(instance.def_id(), instance.substs)
} else {
(fn_def_id, substs)
};
let is_self_call =
call_fn_id == def_id &&
&call_substs[..caller_substs.len()] == caller_substs;
if is_self_call {
self_call_locations.push(terminator.source_info);
//this is a self call so we shouldn't explore
//further down this path
continue;
}

into its own function in order to "just call it" from the original location and from the new drop checks

@agnxy
Copy link
Contributor

agnxy commented Nov 17, 2018

Thanks @tom7980 @oli-obk . I'll continue investigating and studying trait solving in rustc.

can you elaborate on the ICE? I'm guessing it's due to your unwrap calls?

Removing unwrap calls doesn't fix this. It seems that the compiler fails to resolve an obligation at codegen_fulfill_obligation. I have attached the backtrace and debug log. And here is the sample code.

@oli-obk
Copy link
Contributor

oli-obk commented Nov 19, 2018

This failure is happening because we are in a generic context.

There are three ways to continue

  1. make codegen_fulfill_obligation return an Option in case the arguments are too generic to resolve
  2. call needs_subst() on the drop_type, if the result is true, then we don't proceed to use Instance::resolve and just use the fallback
  3. always use the fallback and don't try to resolve Drop, not sure if that will actually work in practice, but it's worth a try.

@jonas-schievink jonas-schievink added A-lint Area: Lints (warnings about flaws in source code) such as unused_mut. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. and removed A-diagnostics Area: Messages for errors, warnings, and lints labels Jan 28, 2019
@tom7980
Copy link
Contributor Author

tom7980 commented Mar 21, 2019

So I've been looking at this in my spare time and tried implementing it with needs_subst() on the drop type, but I'm also getting the same issue with being in a generic context and the compiler not being able to resolve an instance of the drop method on the type. I'm looking at how I would make codegen_fulfill_obligation return an Option currently but need to work on that further.

My real question is however, once we have the T::drop method defId & substs what is the best way to check that we are initializing the same type inside of the method and produce an error?

@Enselic
Copy link
Member

Enselic commented Jul 20, 2023

PR with fix: #113902

@bors bors closed this as completed in 84ec263 Aug 7, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-lint Area: Lints (warnings about flaws in source code) such as unused_mut. E-mentor Call for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants