// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.


#![feature(box_syntax)]
#![feature(intrinsics)]

mod rusti {
    extern "rust-intrinsic" {
        pub fn init<T>() -> T;
        pub fn move_val_init<T>(dst: *mut T, src: T);
    }
}

pub fn main() {
    unsafe {
        // sanity check
        check_drops_state(0, None);

        let mut x: Box<D> = box D(1);
        assert_eq!(x.0, 1);

        // A normal overwrite, to demonstrate `check_drops_state`.
        x = box D(2);

        // At this point, one destructor has run, because the
        // overwrite of `x` drops its initial value.
        check_drops_state(1, Some(1));

        let mut y: Box<D> = rusti::init();

        // An initial binding does not overwrite anything.
        check_drops_state(1, Some(1));

        // Since `y` has been initialized via the `init` intrinsic, it
        // would be unsound to directly overwrite its value via normal
        // assignment.
        //
        // The code currently generated by the compiler is overly
        // accepting, however, in that it will check if `y` is itself
        // null and thus avoid the unsound action of attempting to
        // free null. In other words, if we were to do a normal
        // assignment like `y = box D(4);` here, it probably would not
        // crash today. But the plan is that it may well crash in the
        // future, (I believe).

        // `x` is moved here; the manner in which this is tracked by the
        // compiler is hidden.
        rusti::move_val_init(&mut y, x);

        // In particular, it may be tracked via a drop-flag embedded
        // in the value, or via a null pointer, or via
        // mem::POST_DROP_USIZE, or (most preferably) via a
        // stack-local drop flag.
        //
        // (This test used to build-in knowledge of how it was
        // tracked, and check that the underlying stack slot had been
        // set to `mem::POST_DROP_USIZE`.)

        // But what we *can* observe is how many times the destructor
        // for `D` is invoked, and what the last value we saw was
        // during such a destructor call. We do so after the end of
        // this scope.

        assert_eq!(y.0, 2);
        y.0 = 3;
        assert_eq!(y.0, 3);

        check_drops_state(1, Some(1));
    }

    check_drops_state(2, Some(3));
}

static mut NUM_DROPS: i32 = 0;
static mut LAST_DROPPED: Option<i32> = None;

fn check_drops_state(num_drops: i32, last_dropped: Option<i32>) {
    unsafe {
        assert_eq!(NUM_DROPS, num_drops);
        assert_eq!(LAST_DROPPED, last_dropped);
    }
}

struct D(i32);
impl Drop for D {
    fn drop(&mut self) {
        unsafe {
            NUM_DROPS += 1;
            LAST_DROPPED = Some(self.0);
        }
    }
}
