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

Trait with HRTB blanket impl is implemented when it shouldn't be #54302

Closed
dtolnay opened this issue Sep 17, 2018 · 14 comments · Fixed by #54624
Closed

Trait with HRTB blanket impl is implemented when it shouldn't be #54302

dtolnay opened this issue Sep 17, 2018 · 14 comments · Fixed by #54624
Assignees
Labels
I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness P-high High priority regression-from-stable-to-stable Performance or correctness regression from one stable version to another. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@dtolnay
Copy link
Member

dtolnay commented Sep 17, 2018

trait Deserialize<'de> {}

trait DeserializeOwned: for<'de> Deserialize<'de> {}
impl<T> DeserializeOwned for T where T: for<'de> Deserialize<'de> {}

// Based on this impl, `&'static str` only implements Deserialize<'static>.
// It does not implement for<'de> Deserialize<'de>.
impl<'de: 'a, 'a> Deserialize<'de> for &'a str {}

fn main() {
    // Then why does it implement DeserializeOwned? This compiles.
    fn assert_deserialize_owned<T: DeserializeOwned>() {}
    assert_deserialize_owned::<&'static str>();

    // It correctly does not implement for<'de> Deserialize<'de>.
    //fn assert_hrtb<T: for<'de> Deserialize<'de>>() {}
    //assert_hrtb::<&'static str>();
}

This is a regression between rustc 1.20.0 and 1.21.0.

1.20 correctly rejects this code with the following error.

error[E0279]: the requirement `for<'de> 'de : ` is not satisfied (`expected bound lifetime parameter 'de, found concrete lifetime`)
  --> src/main.rs:13:5
   |
13 |     assert_deserialize_owned::<&'static str>();
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: required because of the requirements on the impl of `for<'de> Deserialize<'de>` for `&'static str`
   = note: required because of the requirements on the impl of `DeserializeOwned` for `&'static str`
   = note: required by `main::assert_deserialize_owned`
@dtolnay dtolnay added regression-from-stable-to-stable Performance or correctness regression from one stable version to another. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. P-high High priority I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness labels Sep 17, 2018
@dtolnay
Copy link
Member Author

dtolnay commented Sep 17, 2018

Tagging P-high because this very much affects Serde. @rust-lang/compiler

trait Deserialize<'de> {
    fn from_str(s: &'de str) -> Self;
}

trait DeserializeOwned: for<'de> Deserialize<'de> {}
impl<T> DeserializeOwned for T where T: for<'de> Deserialize<'de> {}

impl<'de: 'a, 'a> Deserialize<'de> for &'a str {
    fn from_str(s: &'de str) -> Self {
        s
    }
}

fn main() {
    println!("{}", use_after_free::<&'static str>());
}

fn use_after_free<T: DeserializeOwned>() -> T {
    let s = "oh my".to_owned();
    T::from_str(&s)
}

@dtolnay
Copy link
Member Author

dtolnay commented Sep 17, 2018

Same issue using Serde traits:

extern crate serde;
extern crate serde_json;

fn use_after_free<T: serde::de::DeserializeOwned>() -> T {
    let json = r#" "oh my" "#.to_owned();
    serde_json::from_str(&json).unwrap()
}

fn main() {
    println!("{}", use_after_free::<&'static str>());
}

(One possible) output:

¢ÚU�

@estebank
Copy link
Contributor

Even better, enabling NLL causes an ICE:

error: internal compiler error: broken MIR in DefId(0/0:7 ~ playground[9687]::main[0]) (NoSolution): could not prove Binder(TraitPredicate(<&str as DeserializeOwned>))

thread 'main' panicked at 'no errors encountered even though `delay_span_bug` issued', librustc_errors/lib.rs:321:17
note: Run with `RUST_BACKTRACE=1` for a backtrace.

error: internal compiler error: unexpected panic

@nikomatsakis
Copy link
Contributor

nikomatsakis commented Sep 19, 2018

Perhaps we can bisect this regression? (e.g., with https://github.com/rust-lang-nursery/cargo-bisect-rustc)

@nikomatsakis nikomatsakis added the E-needs-bisection Call for participation: This issue needs bisection: https://github.com/rust-lang/cargo-bisect-rustc label Sep 19, 2018
@dtolnay
Copy link
Member Author

dtolnay commented Sep 19, 2018

Bisected to correct behavior in nightly-2017-07-07 -- rustc 1.20.0-nightly (696412d 2017-07-06), incorrect behavior in nightly-2017-07-08 -- rustc 1.20.0-nightly (9b85e1c 2017-07-07).

That range is 696412d...9b85e1c.

Looks like #42840.

@dtolnay dtolnay removed the E-needs-bisection Call for participation: This issue needs bisection: https://github.com/rust-lang/cargo-bisect-rustc label Sep 19, 2018
@pnkfelix
Copy link
Member

visited for triage. Assigning to @nikomatsakis

@nikomatsakis
Copy link
Contributor

@dtolnay thanks, very helpful!

@nikomatsakis
Copy link
Contributor

I think the bug is here:

if data.is_global() && !data.has_late_bound_regions() {
// no type variables present, can use evaluation for better caching.
// FIXME: consider caching errors too.
if self.selcx.infcx().predicate_must_hold(&obligation) {
debug!("selecting trait `{:?}` at depth {} evaluated to holds",
data, obligation.recursion_depth);
return ProcessResult::Changed(vec![])
}
}

In particular, I think the has_late_bound_regions condition is not strong enough. predicate_must_hold doesn't consider region annotations, so we probably need to not use this logic if there are any regions.

But I'm wondering if the whole concept is flawed. I'm a bit fearful of 'static regions being introduced through associated type normalization.

@nikomatsakis
Copy link
Contributor

I have a fix.

@arielb1
Copy link
Contributor

arielb1 commented Sep 21, 2018

Of course, any approach that relies on any lifetimes in the affected trait-ref is fatally flawed - here's an example where the trait ref is the region-free <u32 as RefFoo>.

trait Foo {
    fn foo(self) -> &'static u32;
}

impl<'a> Foo for &'a u32 where 'a: 'static {
    fn foo(self) -> &'static u32 { self }
}

trait RefFoo {
    fn ref_foo(&self) -> &'static u32;
}

impl<T> RefFoo for T where for<'a> &'a T: Foo {
    fn ref_foo(&self) -> &'static u32 {
        self.foo()
    }
}

fn coerce_lifetime(a: &u32) -> &'static u32
{
    <u32 as RefFoo>::ref_foo(a)
}

fn main() {
}

@nikomatsakis
Copy link
Contributor

@arielb1 I'm not really sure what your example is meant to illustrate. That said, running that on my branch gives an error:

error[E0279]: the requirement `for<'a> 'a : 'static` is not satisfied (`expected bound lifetime parameter 'a, found concrete lifetime`)
  --> /home/nmatsakis/tmp/arielb1.rs:21:5
   |
21 |     <u32 as RefFoo>::ref_foo(a)
   |     ^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: required because of the requirements on the impl of `for<'a> Foo` for `&'a u32`
   = note: required because of the requirements on the impl of `RefFoo` for `u32`
note: required by `RefFoo::ref_foo`
  --> /home/nmatsakis/tmp/arielb1.rs:10:5
   |
10 |     fn ref_foo(&self) -> &'static u32;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

This seems like the error I would expect, more or less. We try to use the impl to prove that for<'a> &'a T: Foo but we fail to do so (because of the side condition that it adds).

bors added a commit that referenced this issue Sep 25, 2018
make evaluation track whether outlives relationships mattered

Previously, evaluation ignored outlives relationships. Since we using
evaluation to skip the "normal" trait selection (which enforces
outlives relationships) this led to incorrect results in some cases.

Fixes #54302

r? @arielb1
@arielb1
Copy link
Contributor

arielb1 commented Sep 25, 2018

BTW, the has_late_bounds_region check looks like it happened because I used is_global there, and @matthewjasper added an explicit check when he changed is_global in #48557 to note be made true by late-bound regions.

@pnkfelix
Copy link
Member

visited for triage. bug seems to be being dealt with, assuming that PR #54401 is eventually deemed as passing muster...

@nikomatsakis
Copy link
Contributor

@arielb1

BTW, the has_late_bounds_region check looks like it happened because I used is_global there, and @matthewjasper added an explicit check when he changed is_global in #48557 to note be made true by late-bound regions.

Yes, I think that the "no late-bound regions" check was basically a workaround for this same problem; I removed it in the PR since I think it has a "more correct" fix.

arielb1 added a commit to arielb1/rust that referenced this issue Oct 1, 2018
pietroalbini added a commit to pietroalbini/rust that referenced this issue Oct 3, 2018
…tsakis

handle outlives predicates in trait evaluation

This handles higher-ranked outlives predicates in trait evaluation the same way they are handled in projection.

Fixes rust-lang#54302. I think this is a more correct fix than rust-lang#54401 because it fixes the root case in evaluation instead of making evaluation used in less cases. However, we might want to go to a direction closer to @nikomatsakis's solution with Chalk.

r? @nikomatsakis
bors added a commit that referenced this issue Oct 4, 2018
handle outlives predicates in trait evaluation

This handles higher-ranked outlives predicates in trait evaluation the same way they are handled in projection.

Fixes #54302. I think this is a more correct fix than #54401 because it fixes the root case in evaluation instead of making evaluation used in less cases. However, we might want to go to a direction closer to @nikomatsakis's solution with Chalk.

r? @nikomatsakis
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness P-high High priority regression-from-stable-to-stable Performance or correctness regression from one stable version to another. 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.

5 participants