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

fulfillment error involving quickcheck `Testable` trait on `fn` types (and tuples?) #34503

Closed
pnkfelix opened this Issue Jun 27, 2016 · 21 comments

Comments

Projects
None yet
5 participants
@pnkfelix
Copy link
Member

pnkfelix commented Jun 27, 2016

spawned off of #33723

This code (playpen) causes an ICE in the beta and nightly compilers:

// Heavily reduced version of `quickcheck` with an `fn main`that ICE's the compiler.
//
// This is for issues #33723 and #34503

// This provides a modified version of the `Iterator` trait whose
// `fn max` method does not have a `Self: Sized` bound. This is
// to isolate the distinct bug underlying #33723 (which was sidestepped
// by PR #34419).
use self::my_iter::Iterator;
mod my_iter {
    pub trait Iterator {
        type Item;
        fn next(&mut self) -> Option<Self::Item>;
        fn scan<St, B, F>(self, initial_state: St, f: F) -> Scan<Self, St, F>
            where Self: Sized, F: FnMut(&mut St, Self::Item) -> Option<B>,
        {
            Scan{iter: self, f: f, state: initial_state}
        }

        // This version of `fn max` takes `&self` rather than `self` to sidestep
        // the vtable Self: Sized fix from PR #34419
        fn max(&self) -> Option<Self::Item> where Self::Item: Ord
        {
            return select_fold1(self,
                                |_| (),
                                // switch to y even if it is only equal, to preserve
                                // stability.
                                |_, x, _, y| *x <= *y)
                .map(|(_, x)| x);

            fn select_fold1<I: ?Sized,B, FProj, FCmp>(mut _it: &I,
                                                      mut _f_proj: FProj,
                                                      mut _f_cmp: FCmp) -> Option<(B, I::Item)>
                where I: Iterator, FProj: FnMut(&I::Item) -> B, FCmp: FnMut(&B, &I::Item, &B, &I::Item) -> bool
            {
                unimplemented!()
            }
        }
    }

    #[derive(Clone)]
    pub struct Scan<I, St, F> {
        iter: I,
        f: F,
        state: St,
    }

    impl<B, I, St, F> Iterator for Scan<I, St, F> where
        I: Iterator,
    F: FnMut(&mut St, I::Item) -> Option<B>,
    {
        type Item = B;

        #[inline]
        fn next(&mut self) -> Option<B> {
            self.iter.next().and_then(|a| (self.f)(&mut self.state, a))
        }
    }

    impl<I: Iterator + ?Sized> Iterator for Box<I> {
        type Item = I::Item;
        fn next(&mut self) -> Option<I::Item> {
            (**self).next()
        }
    }
}

pub trait Arbitrary : Clone + 'static {
    fn arbitrary<G>() -> Self { unimplemented! () }
    fn shrink(&self) -> Box<Iterator<Item=Self>> { unimplemented!() }
}

impl<A: Arbitrary> Arbitrary for Option<A> { }

impl<A: Arbitrary> Arbitrary for (A,)
{
    fn shrink(&self) -> Box<Iterator<Item=(A,)>> {
        let (ref a, ) = *self;
        let srest = a
            .shrink()
            .scan((), |_, _| unimplemented!());
        Box::new(srest)
    }
}

impl<A: Arbitrary, B: Arbitrary> Arbitrary for (A, B,)
{
    fn shrink(&self) -> Box<Iterator<Item=(A, B)>> {
        let (ref a, ref b) = *self;
        let srest = (b.clone(),) // <--- use of unary tuple is key to reproducing bug
            .shrink()
            .scan(a.clone(), |_, _| unimplemented!());
        Box::new(srest)
    }
}

pub fn quickcheck<A: Testable>(f: A) {
    f.result::<A>();
}

pub trait Testable : Send + 'static {
    fn result<G>(&self) { unimplemented!() }
}

impl Testable for () { }

impl<T: Testable, A: Arbitrary, B: Arbitrary> Testable for fn(A, B) -> T
{
    fn result<G_>(&self) {
        let a: (A, B) = Arbitrary::arbitrary::<G_>();
        a.shrink();
    }
}

#[derive(Clone, Debug)]
struct PackedNode;
impl Arbitrary for PackedNode { }

fn main() {
    fn with_nodes(_: PackedNode, _: Option<PackedNode>) { unimplemented!() }
    quickcheck(with_nodes as fn(PackedNode, Option<PackedNode>));
}

here is the compiler ICE message:

error: internal compiler error: ../src/librustc/infer/mod.rs:681: Encountered errors `[FulfillmentError(Obligation(predicate=Binder(TraitPredicate(<PackedNode as std::cmp::PartialEq>)),depth=2),Unimplemented), FulfillmentError(Obligation(predicate=Binder(TraitPredicate(<PackedNode as std::cmp::PartialOrd>)),depth=2),Unimplemented)]` resolving bounds after type-checking
note: the compiler unexpectedly panicked. this is a bug.

(What follows is the original reduced test case, which stopped ICE'ing recently due to an unrelated bug fix, which prompted me to write the above revision of the code to continue demonstrating the bug in question.)

Old Description

// Heavily reduced version of `quickcheck` with an `fn main`that ICE's the compiler.
//
// This is for issue #33723.

pub trait Arbitrary : Clone + 'static {
    fn arbitrary<G>() -> Self { unimplemented! () }
    fn shrink(&self) -> Box<Iterator<Item=Self>> { unimplemented!() }
}

impl<A: Arbitrary> Arbitrary for Option<A> { }

impl<A: Arbitrary> Arbitrary for (A,)
{
    fn shrink(&self) -> Box<Iterator<Item=(A,)>> {
        let (ref a, ) = *self;
        let srest = a
            .shrink()
            .scan((), |_, _| unimplemented!());
        Box::new(srest)
    }
}

impl<A: Arbitrary, B: Arbitrary> Arbitrary for (A, B,)
{
    fn shrink(&self) -> Box<Iterator<Item=(A, B)>> {
        let (ref a, ref b) = *self;
        let srest = (b.clone(),) // <--- use of unary tuple is key to reproducing bug
            .shrink()
            .scan(a.clone(), |_, _| unimplemented!());
        Box::new(srest)
    }
}

pub fn quickcheck<A: Testable>(f: A) {
    f.result::<A>();
}

pub trait Testable : Send + 'static {
    fn result<G>(&self) { unimplemented!() }
}

impl Testable for () { }

impl<T: Testable, A: Arbitrary, B: Arbitrary> Testable for fn(A, B) -> T
{
    fn result<G_>(&self) {
        let a: (A, B) = Arbitrary::arbitrary::<G_>();
        a.shrink();
    }
}

#[derive(Clone, Debug)]
struct PackedNode;
impl Arbitrary for PackedNode { }

fn main() {
    fn with_nodes(_: PackedNode, _: Option<PackedNode>) { unimplemented!() }
    quickcheck(with_nodes as fn(PackedNode, Option<PackedNode>));
}

This is the ICE in question:

error: internal compiler error: ../src/librustc/infer/mod.rs:642: Encountered errors 
`[FulfillmentError(Obligation(predicate=Binder(TraitPredicate(<PackedNode as std::cmp::PartialEq>)),depth=2),Unimplemented), 
  FulfillmentError(Obligation(predicate=Binder(TraitPredicate(<PackedNode as std::cmp::PartialOrd>)),depth=2),Unimplemented)]`
 resolving bounds after type-checking
@pnkfelix

This comment has been minimized.

Copy link
Member Author

pnkfelix commented Jun 27, 2016

The ICE here goes away if I add derived of PartialEq and PartialOrd to the struct PackedNode.

I need to double-check if this the actually the same ICE that plagues #33723.

@pnkfelix

This comment has been minimized.

Copy link
Member Author

pnkfelix commented Jun 27, 2016

(of course, AFAICT there is no reason that we should be requiring the PartialEq nor the PartialOrd traits on the struct in question, so I claim this is still a valid bug.)

@pnkfelix

This comment has been minimized.

Copy link
Member Author

pnkfelix commented Jun 27, 2016

I'm going to copy over the labels from #33723 since this is at least as high priority as that bug is.

@brson

This comment has been minimized.

Copy link
Contributor

brson commented Jun 29, 2016

cc @pnkfelix @rust-lang/compiler There's a release next week. Is there any hope of fixing this?

@pnkfelix

This comment has been minimized.

Copy link
Member Author

pnkfelix commented Jun 30, 2016

Current investigation results:

  • The item collector tries to include the Iterator::max (and Iterator::min) methods on a vtable that it is building for an Iterator whose items do not impement PartialEq nor PartialOrd (in this case, PackedNode and Option<PackedNode>). The item collector subsequently processes the bodies of the default implementations of fn max and adds a fulfillment obligation, TraitPredicate(<PackedNode as PartialEq>) and TraitPredicate(<PackedNode as PartialOrd>).
  • So, I am trying to figure out why the item collector is not filtering out max/min since the Item here does not implement PartialOrd.

I don't myself know the direct way to disable item collection; I looked into it briefly but it was not obvious what parts were safe to "switch off." I hope @michaelwoerister can look at that in time for the release.

@eddyb

This comment has been minimized.

Copy link
Member

eddyb commented Jun 30, 2016

As I explained on IRC, the trans item collector looks correct, and if it weren't there, building the vtable itself would fail for the same reason: meth::get_vtable_methods must be not doing what it's supposed to (filter out inapplicable methods).

@michaelwoerister

This comment has been minimized.

Copy link
Contributor

michaelwoerister commented Jun 30, 2016

building the vtable itself would fail for the same reason

So, I have disabled the collector on a local branch and the compiler does not crash then. Maybe trans takes additional measures to filter out methods when building vtables.

@eddyb

This comment has been minimized.

Copy link
Member

eddyb commented Jun 30, 2016

@michaelwoerister That's odd, the code looks identical. But maybe I wasn't looking in the right place.

@michaelwoerister

This comment has been minimized.

Copy link
Contributor

michaelwoerister commented Jun 30, 2016

Maybe it calls meth::get_vtable_methods with different parameters for some reason?

@michaelwoerister

This comment has been minimized.

Copy link
Contributor

michaelwoerister commented Jun 30, 2016

Hm, it seems that cannot reproduce the issue at all (with or without collector) with the current HEAD of master. Is it possible that this is fixed already? With the latest nightly from rustup (ad7fe65 2016-06-23) it does crash.

@arielb1

This comment has been minimized.

Copy link
Contributor

arielb1 commented Jul 1, 2016

This was fixed between 7d8e6dd3bfdec0b0a6f47b10966673f88a9242f5 and ea0dc9297283daff6486807f43e190b4eb561412.

@pnkfelix

This comment has been minimized.

Copy link
Member Author

pnkfelix commented Jul 1, 2016

(I can't believe I forgot to update my copy of master before I spent yesterday afternoon investigating...)

Thanks @arielb1 for narrowing it down!

I did further bisection this morning, and determined it was introduced between
af2fe63 (which still ICE'd) and
b42884f (which no longer ICE's).

The one that no longer ICE's is the merge of #34419


In hindsight, its clear why #34419 fixes this this case: Iterator::max (and Iterator::min) both take self by value, so they end up having Self: Sized prerequisites.

(My suspicion is that we should be able to come up with a similar bit of code that exposes the old ICE that I was investigating. But in the meantime, my recommendation is that we beta-nominate #34419 since that seems like a really safe PR to backport to beta.)

@pnkfelix

This comment has been minimized.

Copy link
Member Author

pnkfelix commented Jul 1, 2016

Yes I have managed to make a new test case that continues to expose the bug:

https://play.rust-lang.org/?gist=9398442e3b7aaeb387e5b3630fd0c8d3&version=nightly&backtrace=0

I'll update the description to use this variant instead.

(I know the playpen is using a version of nightly that predates #34419; I verified this against a local build of rustc that has #34419 incorporated, and it still ICE's there.)

@michaelwoerister could you look into how your item-collection-disabled branch behaves against the above gist of code?

@pnkfelix

This comment has been minimized.

Copy link
Member Author

pnkfelix commented Jul 1, 2016

(Note @eddyb has predicted that trans itself will fail during vtable construction even if item-collection were disabled; nonetheless, its worth verifying that prediction.)

@michaelwoerister

This comment has been minimized.

Copy link
Contributor

michaelwoerister commented Jul 1, 2016

@pnkfelix Disabling the collector makes the ICE go away. Things do not fail later during vtable construction. Which is interesting, since the code really looks very similar.

EDIT: Note that the above applies to the updated version of the code under test.

@eddyb

This comment has been minimized.

Copy link
Member

eddyb commented Jul 1, 2016

@michaelwoerister On IRC @arielb1 said he'd found the bug in the obligation "forest" handling of nested obligations which have already failed and that he's working on a fix.

@eddyb

This comment has been minimized.

Copy link
Member

eddyb commented Jul 1, 2016

@michaelwoerister Maybe without the collector the obligation that poisons the cache is never tested?
That is, (PackedNode, Option<PackedNode>): Ord (which fails, but also leaves Option<PackedNode>: Ord in the cache, wrongly).

@michaelwoerister

This comment has been minimized.

Copy link
Contributor

michaelwoerister commented Jul 1, 2016

Maybe without the collector the obligation that poisons the cache is never tested?

Oh, so this would be a mutable state problem? I know little about how obligation caches work (I should probably start learning more about that part of the compiler).

@michaelwoerister

This comment has been minimized.

Copy link
Contributor

michaelwoerister commented Jul 1, 2016

Just FYI, if you want to test with the collector disabled: It suffices to replace the body rustc_trans::collector::collect_crate_translation_items() with return (FnvHashSet(), InliningMap::new());. Subsequent stages of trans can handle the set of translation items being empty.

@eddyb

This comment has been minimized.

Copy link
Member

eddyb commented Jul 1, 2016

@michaelwoerister The cache is global with a simple check that the obligation has no inference variables. Once that mistakenly successful Option<PackedNode>: Ord gets into the cache, it succeeds forever, even if PackedNode: Ord still doesn't succeed and neither of them can be selected.

@eddyb

This comment has been minimized.

Copy link
Member

eddyb commented Jul 1, 2016

@michaelwoerister Intuitively constructed reproduction: https://play.rust-lang.org/?gist=95f6ad7e16621d718490d4f81e8eec23&version=nightly&backtrace=0.

You can try to add things that would fail but cause some sub-obligations to succeed, like duplicating the foo method or having more traits like this, etc.

arielb1 added a commit to arielb1/rust that referenced this issue Jul 1, 2016

fail obligations that depend on erroring obligations
Fix a bug where an obligation that depend on an erroring obligation would
be regarded as successful, leading to global cache pollution and random
lossage.

Fixes rust-lang#33723.
Fixes rust-lang#34503.

arielb1 added a commit to arielb1/rust that referenced this issue Jul 1, 2016

fail obligations that depend on erroring obligations
Fix a bug where an obligation that depend on an erroring obligation would
be regarded as successful, leading to global cache pollution and random
lossage.

Fixes rust-lang#33723.
Fixes rust-lang#34503.

bors added a commit that referenced this issue Jul 2, 2016

Auto merge of #34605 - arielb1:bug-in-the-jungle, r=eddyb
fail obligations that depend on erroring obligations

Fix a bug where an obligation that depend on an erroring obligation would
be regarded as successful, leading to global cache pollution and random
lossage.

Fixes #33723.
Fixes #34503.

r? @eddyb since @nikomatsakis is on vacation

beta-nominating because of the massive lossage potential (e.g. with `Copy` this could lead to random memory leaks), plus this is a regression.

@bors bors closed this in #34605 Jul 2, 2016

alexcrichton added a commit to alexcrichton/rust that referenced this issue Jul 3, 2016

fail obligations that depend on erroring obligations
Fix a bug where an obligation that depend on an erroring obligation would
be regarded as successful, leading to global cache pollution and random
lossage.

Fixes rust-lang#33723.
Fixes rust-lang#34503.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.