<commit_before>// xfail-fast

// 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.

// Ping-pong is a bounded protocol. This is place where I can
// experiment with what code the compiler should generate for bounded
// protocols.

// This was generated initially by the pipe compiler, but it's been
// modified in hopefully straightforward ways.
mod pingpong {
    use core::pipes::*;

    type packets = {
        // This is probably a resolve bug, I forgot to export packet,
        // but since I didn't import pipes::*, it worked anyway.
        ping: Packet<ping>,
        pong: Packet<pong>,
    };

    fn init() -> (client::ping, server::ping) {
        let buffer = ~{
            header: BufferHeader(),
            data: {
                ping: mk_packet::<ping>(),
                pong: mk_packet::<pong>()
            }
        };
        do pipes::entangle_buffer(move buffer) |buffer, data| {
            data.ping.set_buffer_(buffer);
            data.pong.set_buffer_(buffer);
            ptr::addr_of(&(data.ping))
        }
    }
    pub enum ping = server::pong;
    pub enum pong = client::ping;
    pub mod client {
        pub fn ping(+pipe: ping) -> pong {
            {
                let b = pipe.reuse_buffer();
                let s = SendPacketBuffered(ptr::addr_of(&(b.buffer.data.pong)));
                let c = RecvPacketBuffered(ptr::addr_of(&(b.buffer.data.pong)));
                let message = pingpong::ping(move s);
                pipes::send(move pipe, move message);
                move c
            }
        }
        pub type ping = pipes::SendPacketBuffered<pingpong::ping,
        pingpong::packets>;
        pub type pong = pipes::RecvPacketBuffered<pingpong::pong,
        pingpong::packets>;
    }
    pub mod server {
        pub type ping = pipes::RecvPacketBuffered<pingpong::ping,
        pingpong::packets>;
        pub fn pong(+pipe: pong) -> ping {
            {
                let b = pipe.reuse_buffer();
                let s = SendPacketBuffered(ptr::addr_of(&(b.buffer.data.ping)));
                let c = RecvPacketBuffered(ptr::addr_of(&(b.buffer.data.ping)));
                let message = pingpong::pong(move s);
                pipes::send(move pipe, move message);
                move c
            }
        }
        pub type pong = pipes::SendPacketBuffered<pingpong::pong,
        pingpong::packets>;
    }
}

mod test {
    use pipes::recv;
    use pingpong::{ping, pong};

    pub fn client(-chan: pingpong::client::ping) {
        use pingpong::client;

        let chan = client::ping(move chan); return;
        log(error, "Sent ping");
        let pong(_chan) = recv(move chan);
        log(error, "Received pong");
    }

    pub fn server(-chan: pingpong::server::ping) {
        use pingpong::server;

        let ping(chan) = recv(move chan); return;
        log(error, "Received ping");
        let _chan = server::pong(move chan);
        log(error, "Sent pong");
    }
}

fn main() {
    let (client_, server_) = pingpong::init();
    let client_ = ~mut Some(move client_);
    let server_ = ~mut Some(move server_);
    do task::spawn |move client_| {
        let mut client__ = None;
        *client_ <-> client__;
        test::client(option::unwrap(move client__));
    };
    do task::spawn |move server_| {
        let mut server_ˊ = None;
        *server_ <-> server_ˊ;
        test::server(option::unwrap(move server_ˊ));
    };
}
<commit_msg>test: Fix test/run-pass/pipe-pingpong-bounded. rs=bustage<commit_after>// xfail-fast

// 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.

// Ping-pong is a bounded protocol. This is place where I can
// experiment with what code the compiler should generate for bounded
// protocols.

// This was generated initially by the pipe compiler, but it's been
// modified in hopefully straightforward ways.
mod pingpong {
    use core::pipes::*;
    use core::ptr;

    pub type packets = {
        ping: Packet<ping>,
        pong: Packet<pong>,
    };

    pub fn init() -> (client::ping, server::ping) {
        let buffer = ~{
            header: BufferHeader(),
            data: {
                ping: mk_packet::<ping>(),
                pong: mk_packet::<pong>()
            }
        };
        do pipes::entangle_buffer(move buffer) |buffer, data| {
            data.ping.set_buffer_(buffer);
            data.pong.set_buffer_(buffer);
            ptr::addr_of(&(data.ping))
        }
    }
    pub enum ping = server::pong;
    pub enum pong = client::ping;
    pub mod client {
        use core::ptr;

        pub fn ping(+pipe: ping) -> pong {
            {
                let b = pipe.reuse_buffer();
                let s = SendPacketBuffered(ptr::addr_of(&(b.buffer.data.pong)));
                let c = RecvPacketBuffered(ptr::addr_of(&(b.buffer.data.pong)));
                let message = ::pingpong::ping(move s);
                ::pipes::send(move pipe, move message);
                move c
            }
        }
        pub type ping = pipes::SendPacketBuffered<::pingpong::ping,
                                                  ::pingpong::packets>;
        pub type pong = pipes::RecvPacketBuffered<::pingpong::pong,
                                                  ::pingpong::packets>;
    }
    pub mod server {
        use core::ptr;

        pub type ping = pipes::RecvPacketBuffered<::pingpong::ping,
        ::pingpong::packets>;
        pub fn pong(+pipe: pong) -> ping {
            {
                let b = pipe.reuse_buffer();
                let s = SendPacketBuffered(ptr::addr_of(&(b.buffer.data.ping)));
                let c = RecvPacketBuffered(ptr::addr_of(&(b.buffer.data.ping)));
                let message = ::pingpong::pong(move s);
                ::pipes::send(move pipe, move message);
                move c
            }
        }
        pub type pong = pipes::SendPacketBuffered<::pingpong::pong,
                                                  ::pingpong::packets>;
    }
}

mod test {
    use pipes::recv;
    use pingpong::{ping, pong};

    pub fn client(-chan: ::pingpong::client::ping) {
        use pingpong::client;

        let chan = client::ping(move chan); return;
        log(error, "Sent ping");
        let pong(_chan) = recv(move chan);
        log(error, "Received pong");
    }

    pub fn server(-chan: ::pingpong::server::ping) {
        use pingpong::server;

        let ping(chan) = recv(move chan); return;
        log(error, "Received ping");
        let _chan = server::pong(move chan);
        log(error, "Sent pong");
    }
}

fn main() {
    let (client_, server_) = ::pingpong::init();
    let client_ = ~mut Some(move client_);
    let server_ = ~mut Some(move server_);
    do task::spawn |move client_| {
        let mut client__ = None;
        *client_ <-> client__;
        test::client(option::unwrap(move client__));
    };
    do task::spawn |move server_| {
        let mut server_ˊ = None;
        *server_ <-> server_ˊ;
        test::server(option::unwrap(move server_ˊ));
    };
}
<|endoftext|>


<commit_before>// Std
use std::{
    ffi::{OsStr, OsString},
    iter::{Cloned, Flatten},
    slice::Iter,
};

// TODO: Maybe make this public?
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ValueType {
    EnvVariable,
    CommandLine,
    DefaultValue,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct MatchedArg {
    pub(crate) occurs: u64,
    pub(crate) ty: ValueType,
    indices: Vec<usize>,
    vals: Vec<Vec<OsString>>,
}

impl MatchedArg {
    pub(crate) fn new(ty: ValueType) -> Self {
        MatchedArg {
            occurs: 0,
            ty,
            indices: Vec::new(),
            vals: Vec::new(),
        }
    }

    pub(crate) fn indices(&self) -> Cloned<Iter<'_, usize>> {
        self.indices.iter().cloned()
    }

    pub(crate) fn get_index(&self, index: usize) -> Option<usize> {
        self.indices.get(index).cloned()
    }

    pub(crate) fn push_index(&mut self, index: usize) {
        self.indices.push(index)
    }

    pub(crate) fn vals(&self) -> Iter<Vec<OsString>> {
        self.vals.iter()
    }

    pub(crate) fn vals_flatten(&self) -> Flatten<Iter<Vec<OsString>>> {
        self.vals.iter().flatten()
    }

    pub(crate) fn get_val(&self, index: usize) -> Option<&OsString> {
        self.vals.get(0)?.get(index)
    }

    pub(crate) fn push_val(&mut self, val: OsString) {
        self.vals.push(vec![val])
    }

    pub(crate) fn append_val(&mut self, val: OsString) {
        if let Some(val_last) = self.vals.last_mut() {
            val_last.push(val);
        } else {
            // Fall back to push val when no val
            self.push_val(val);
        }
    }

    pub(crate) fn push_vals(&mut self, vals: Vec<OsString>) {
        self.vals.push(vals)
    }

    pub(crate) fn num_vals(&self) -> usize {
        self.vals.len()
    }

    pub(crate) fn no_val(&self) -> bool {
        self.vals.is_empty()
    }

    pub(crate) fn remove_vals(&mut self, len: usize) {
        self.vals.drain(0..len);
    }

    pub(crate) fn contains_val(&self, val: &str) -> bool {
        self.vals_flatten()
            .any(|v| OsString::as_os_str(v) == OsStr::new(val))
    }
}
<commit_msg>More convenient MatchesArg functions<commit_after>// Std
use std::{
    ffi::{OsStr, OsString},
    iter::{Cloned, Flatten},
    slice::Iter,
};

use crate::INTERNAL_ERROR_MSG;

// TODO: Maybe make this public?
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ValueType {
    EnvVariable,
    CommandLine,
    DefaultValue,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct MatchedArg {
    pub(crate) occurs: u64,
    pub(crate) ty: ValueType,
    indices: Vec<usize>,
    vals: Vec<Vec<OsString>>,
}

impl MatchedArg {
    pub(crate) fn new(ty: ValueType) -> Self {
        MatchedArg {
            occurs: 0,
            ty,
            indices: Vec::new(),
            vals: Vec::new(),
        }
    }

    pub(crate) fn indices(&self) -> Cloned<Iter<'_, usize>> {
        self.indices.iter().cloned()
    }

    pub(crate) fn get_index(&self, index: usize) -> Option<usize> {
        self.indices.get(index).cloned()
    }

    pub(crate) fn push_index(&mut self, index: usize) {
        self.indices.push(index)
    }

    pub(crate) fn vals(&self) -> Iter<Vec<OsString>> {
        self.vals.iter()
    }

    pub(crate) fn vals_flatten(&self) -> Flatten<Iter<Vec<OsString>>> {
        self.vals.iter().flatten()
    }

    pub(crate) fn get_val(&self, index: usize) -> Option<&OsString> {
        self.vals.get(0)?.get(index)
    }

    pub(crate) fn push_val(&mut self, val: OsString) {
        self.vals.push(vec![val])
    }

    pub(crate) fn new_val_group(&mut self) {
        self.vals.push(vec![])
    }

    pub(crate) fn append_val(&mut self, val: OsString) {
        // We assume there is always a group created before.
        self.vals.last_mut().expect(INTERNAL_ERROR_MSG).push(val)
    }

    pub(crate) fn push_vals(&mut self, vals: Vec<OsString>) {
        self.vals.push(vals)
    }

    pub(crate) fn num_vals(&self) -> usize {
        self.vals.iter().flatten().count()
    }

    pub(crate) fn no_val(&self) -> bool {
        self.vals.iter().flatten().count() == 0
    }

    pub(crate) fn remove_vals(&mut self, len: usize) {
        self.vals.drain(0..len);
    }

    pub(crate) fn contains_val(&self, val: &str) -> bool {
        self.vals_flatten()
            .any(|v| OsString::as_os_str(v) == OsStr::new(val))
    }
}
<|endoftext|>


<commit_before>// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution.
//
// 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.

use stb_image::bindgen::*;

use std::any::Any;
use libc;
use libc::{c_void, c_int};
use std::slice::raw::from_buf_raw;

pub struct Image<T> {
    pub width   : uint,
    pub height  : uint,
    pub depth   : uint,
    pub data    : ~[T],
}

pub fn new_image<T>(width: uint, height: uint, depth: uint, data: ~[T]) -> Image<T> {
    Image::<T> {
        width   : width,
        height  : height,
        depth   : depth,
        data    : data,
    }
}

pub enum LoadResult {
    Error(~str),
    ImageU8(Image<u8>),
    ImageF32(Image<f32>),
}

impl LoadResult {
    pub fn from_result(res: Result<LoadResult,~Any>)-> LoadResult {
        match res {
            Ok(res) => res,
            Err(e)  => Error(e.to_str()),
        }
    }
}

pub fn load(path: ~str) -> LoadResult {
    let force_depth = 0;
    load_with_depth(path, force_depth, false)
}

fn load_internal<T>(buf : *T, w : c_int, h : c_int, d : c_int) -> Image<T> {
    unsafe {
        // FIXME: Shouldn't copy; instead we should use a sendable resource. They
        // aren't particularly safe yet though.
        let data = from_buf_raw(buf, (w * h * d) as uint);
        libc::free(buf as *mut c_void);
        Image::<T>{
            width   : w as uint,
            height  : h as uint,
            depth   : d as uint,
            data    : data}
    }
}

pub fn load_with_depth(path: ~str, force_depth: uint, convert_hdr:bool) -> LoadResult {
    unsafe {
        let mut width = 0 as c_int;
        let mut height = 0 as c_int;
        let mut depth = 0 as c_int;
        path.to_c_str().with_ref(|bytes| {
            if !convert_hdr && stbi_is_hdr(bytes)!=0   {
                let buffer = stbi_loadf(bytes,
                                        &mut width,
                                        &mut height,
                                        &mut depth,
                                        force_depth as c_int);
                if buffer.is_null() {
                    Error(~"stbi_loadf failed")
                } else {
                    ImageF32(load_internal(buffer, width, height, depth))
                }
            } else {
                let buffer = stbi_load(bytes,
                                       &mut width,
                                       &mut height,
                                       &mut depth,
                                       force_depth as c_int);
                if buffer.is_null() {
                    Error(~"stbi_load failed")
                } else {
                    ImageU8(load_internal(buffer, width, height, depth))
                }
            }
        })
    }
}

pub fn load_from_memory(buffer: &[u8]) -> LoadResult {
    let force_depth = 0;
    load_from_memory_with_depth(buffer, force_depth, false)
}

pub fn load_from_memory_with_depth(buffer: &[u8], force_depth: uint, convert_hdr:bool) -> LoadResult {
    unsafe {
        let mut width = 0 as c_int;
        let mut height = 0 as c_int;
        let mut depth = 0 as c_int;
        if !convert_hdr && stbi_is_hdr_from_memory(buffer.as_ptr(), buffer.len() as c_int) != 0 {
            let buffer = stbi_loadf_from_memory(buffer.as_ptr(),
                                                buffer.len() as c_int,
                                                &mut width,
                                                &mut height,
                                                &mut depth,
                                                force_depth as c_int);
            if buffer.is_null() {
                Error(~"stbi_loadf_from_memory failed")
            } else {
                let actual_depth = if force_depth != 0 { force_depth as c_int } else { depth };
                ImageF32(load_internal(buffer, width, height, actual_depth))
            }
        } else {
            let buffer = stbi_load_from_memory(buffer.as_ptr(),
                                               buffer.len() as c_int,
                                               &mut width,
                                               &mut height,
                                               &mut depth,
                                               force_depth as c_int);
            if buffer.is_null() {
                Error(~"stbi_load_from_memory failed")
            } else {
                let actual_depth = if force_depth != 0 { force_depth as c_int } else { depth };
                ImageU8(load_internal(buffer, width, height, actual_depth))
            }
        }
    }
}
<commit_msg>~str -> &Path<commit_after>// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution.
//
// 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.

use stb_image::bindgen::*;

use std::any::Any;
use libc;
use libc::{c_void, c_int};
use std::slice::raw::from_buf_raw;

pub struct Image<T> {
    pub width   : uint,
    pub height  : uint,
    pub depth   : uint,
    pub data    : ~[T],
}

pub fn new_image<T>(width: uint, height: uint, depth: uint, data: ~[T]) -> Image<T> {
    Image::<T> {
        width   : width,
        height  : height,
        depth   : depth,
        data    : data,
    }
}

pub enum LoadResult {
    Error(~str),
    ImageU8(Image<u8>),
    ImageF32(Image<f32>),
}

impl LoadResult {
    pub fn from_result(res: Result<LoadResult,~Any>)-> LoadResult {
        match res {
            Ok(res) => res,
            Err(e)  => Error(e.to_str()),
        }
    }
}

pub fn load(path: &Path) -> LoadResult {
    let force_depth = 0;
    load_with_depth(path, force_depth, false)
}

fn load_internal<T>(buf : *T, w : c_int, h : c_int, d : c_int) -> Image<T> {
    unsafe {
        // FIXME: Shouldn't copy; instead we should use a sendable resource. They
        // aren't particularly safe yet though.
        let data = from_buf_raw(buf, (w * h * d) as uint);
        libc::free(buf as *mut c_void);
        Image::<T>{
            width   : w as uint,
            height  : h as uint,
            depth   : d as uint,
            data    : data}
    }
}

pub fn load_with_depth(path: &Path, force_depth: uint, convert_hdr: bool) -> LoadResult {
    unsafe {
        let mut width = 0 as c_int;
        let mut height = 0 as c_int;
        let mut depth = 0 as c_int;
        let path_as_str = match path.as_str() {
            Some(s) => s,
            None => return Error(~"path is not valid utf8"),
        };
        path_as_str.with_c_str(|bytes| {
            if !convert_hdr && stbi_is_hdr(bytes)!=0   {
                let buffer = stbi_loadf(bytes,
                                        &mut width,
                                        &mut height,
                                        &mut depth,
                                        force_depth as c_int);
                if buffer.is_null() {
                    Error(~"stbi_loadf failed")
                } else {
                    ImageF32(load_internal(buffer, width, height, depth))
                }
            } else {
                let buffer = stbi_load(bytes,
                                       &mut width,
                                       &mut height,
                                       &mut depth,
                                       force_depth as c_int);
                if buffer.is_null() {
                    Error(~"stbi_load failed")
                } else {
                    ImageU8(load_internal(buffer, width, height, depth))
                }
            }
        })
    }
}

pub fn load_from_memory(buffer: &[u8]) -> LoadResult {
    let force_depth = 0;
    load_from_memory_with_depth(buffer, force_depth, false)
}

pub fn load_from_memory_with_depth(buffer: &[u8], force_depth: uint, convert_hdr:bool) -> LoadResult {
    unsafe {
        let mut width = 0 as c_int;
        let mut height = 0 as c_int;
        let mut depth = 0 as c_int;
        if !convert_hdr && stbi_is_hdr_from_memory(buffer.as_ptr(), buffer.len() as c_int) != 0 {
            let buffer = stbi_loadf_from_memory(buffer.as_ptr(),
                                                buffer.len() as c_int,
                                                &mut width,
                                                &mut height,
                                                &mut depth,
                                                force_depth as c_int);
            if buffer.is_null() {
                Error(~"stbi_loadf_from_memory failed")
            } else {
                let actual_depth = if force_depth != 0 { force_depth as c_int } else { depth };
                ImageF32(load_internal(buffer, width, height, actual_depth))
            }
        } else {
            let buffer = stbi_load_from_memory(buffer.as_ptr(),
                                               buffer.len() as c_int,
                                               &mut width,
                                               &mut height,
                                               &mut depth,
                                               force_depth as c_int);
            if buffer.is_null() {
                Error(~"stbi_load_from_memory failed")
            } else {
                let actual_depth = if force_depth != 0 { force_depth as c_int } else { depth };
                ImageU8(load_internal(buffer, width, height, actual_depth))
            }
        }
    }
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use dom::attr::Attr;
use dom::attr::AttrHelpers;
use dom::bindings::codegen::Bindings::HTMLIFrameElementBinding;
use dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElementMethods;
use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use dom::bindings::codegen::InheritTypes::{NodeCast, ElementCast};
use dom::bindings::codegen::InheritTypes::{HTMLElementCast, HTMLIFrameElementDerived};
use dom::bindings::js::{JSRef, Temporary, OptionalRootable};
use dom::document::Document;
use dom::element::Element;
use dom::element::AttributeHandlers;
use dom::eventtarget::{EventTarget, EventTargetTypeId};
use dom::element::ElementTypeId;
use dom::htmlelement::{HTMLElement, HTMLElementTypeId};
use dom::node::{Node, NodeHelpers, NodeTypeId, window_from_node};
use dom::urlhelper::UrlHelper;
use dom::virtualmethods::VirtualMethods;
use dom::window::Window;
use page::{IterablePage, Page};

use servo_msg::constellation_msg::{PipelineId, SubpageId, ConstellationChan};
use servo_msg::constellation_msg::IFrameSandboxState::{IFrameSandboxed, IFrameUnsandboxed};
use servo_msg::constellation_msg::Msg as ConstellationMsg;
use util::str::DOMString;

use std::ascii::AsciiExt;
use std::cell::Cell;
use url::{Url, UrlParser};

enum SandboxAllowance {
    AllowNothing = 0x00,
    AllowSameOrigin = 0x01,
    AllowTopNavigation = 0x02,
    AllowForms = 0x04,
    AllowScripts = 0x08,
    AllowPointerLock = 0x10,
    AllowPopups = 0x20
}

#[dom_struct]
pub struct HTMLIFrameElement {
    htmlelement: HTMLElement,
    subpage_id: Cell<Option<SubpageId>>,
    containing_page_pipeline_id: Cell<Option<PipelineId>>,
    sandbox: Cell<Option<u8>>,
}

impl HTMLIFrameElementDerived for EventTarget {
    fn is_htmliframeelement(&self) -> bool {
        *self.type_id() == EventTargetTypeId::Node(NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLIFrameElement)))
    }
}

pub trait HTMLIFrameElementHelpers {
    fn is_sandboxed(self) -> bool;
    fn get_url(self) -> Option<Url>;
    /// http://www.whatwg.org/html/#process-the-iframe-attributes
    fn process_the_iframe_attributes(self);
    fn generate_new_subpage_id(self, page: &Page) -> (SubpageId, Option<SubpageId>);
}

impl<'a> HTMLIFrameElementHelpers for JSRef<'a, HTMLIFrameElement> {
    fn is_sandboxed(self) -> bool {
        self.sandbox.get().is_some()
    }

    fn get_url(self) -> Option<Url> {
        let element: JSRef<Element> = ElementCast::from_ref(self);
        element.get_attribute(ns!(""), &atom!("src")).root().and_then(|src| {
            let url = src.r().value();
            if url.as_slice().is_empty() {
                None
            } else {
                let window = window_from_node(self).root();
                UrlParser::new().base_url(&window.r().page().get_url())
                    .parse(url.as_slice()).ok()
            }
        })
    }

    fn generate_new_subpage_id(self, page: &Page) -> (SubpageId, Option<SubpageId>) {
        let old_subpage_id = self.subpage_id.get();
        let subpage_id = page.get_next_subpage_id();
        self.subpage_id.set(Some(subpage_id));
        (subpage_id, old_subpage_id)
    }

    fn process_the_iframe_attributes(self) {
        let url = match self.get_url() {
            Some(url) => url.clone(),
            None => Url::parse("about:blank").unwrap(),
        };

        let sandboxed = if self.is_sandboxed() {
            IFrameSandboxed
        } else {
            IFrameUnsandboxed
        };

        let window = window_from_node(self).root();
        let window = window.r();
        let page = window.page();
        let (new_subpage_id, old_subpage_id) = self.generate_new_subpage_id(page);

        self.containing_page_pipeline_id.set(Some(page.id));

        let ConstellationChan(ref chan) = page.constellation_chan;
    chan.send(ConstellationMsg::ScriptLoadedURLInIFrame(url,
                                                        page.id,
                                                        new_subpage_id,
                                                        old_subpage_id,
                                                        sandboxed)).unwrap();
    }
}

impl HTMLIFrameElement {
    fn new_inherited(localName: DOMString, prefix: Option<DOMString>, document: JSRef<Document>) -> HTMLIFrameElement {
        HTMLIFrameElement {
            htmlelement: HTMLElement::new_inherited(HTMLElementTypeId::HTMLIFrameElement, localName, prefix, document),
            subpage_id: Cell::new(None),
            containing_page_pipeline_id: Cell::new(None),
            sandbox: Cell::new(None),
        }
    }

    #[allow(unrooted_must_root)]
    pub fn new(localName: DOMString, prefix: Option<DOMString>, document: JSRef<Document>) -> Temporary<HTMLIFrameElement> {
        let element = HTMLIFrameElement::new_inherited(localName, prefix, document);
        Node::reflect_node(box element, document, HTMLIFrameElementBinding::Wrap)
    }

    #[inline]
    pub fn containing_page_pipeline_id(&self) -> Option<PipelineId> {
        self.containing_page_pipeline_id.get()
    }

    #[inline]
    pub fn subpage_id(&self) -> Option<SubpageId> {
        self.subpage_id.get()
    }
}

impl<'a> HTMLIFrameElementMethods for JSRef<'a, HTMLIFrameElement> {
    fn Src(self) -> DOMString {
        let element: JSRef<Element> = ElementCast::from_ref(self);
        element.get_string_attribute(&atom!("src"))
    }

    fn SetSrc(self, src: DOMString) {
        let element: JSRef<Element> = ElementCast::from_ref(self);
        element.set_url_attribute(&atom!("src"), src)
    }

    fn Sandbox(self) -> DOMString {
        let element: JSRef<Element> = ElementCast::from_ref(self);
        element.get_string_attribute(&atom!("sandbox"))
    }

    fn SetSandbox(self, sandbox: DOMString) {
        let element: JSRef<Element> = ElementCast::from_ref(self);
        element.set_tokenlist_attribute(&atom!("sandbox"), sandbox);
    }

    fn GetContentWindow(self) -> Option<Temporary<Window>> {
        self.subpage_id.get().and_then(|subpage_id| {
            let window = window_from_node(self).root();
            let children = window.page().children.borrow();
            let child = children.iter().find(|child| {
                child.subpage_id.unwrap() == subpage_id
            });
            child.and_then(|page| {
                page.frame.borrow().as_ref().map(|frame| {
                    Temporary::new(frame.window.clone())
                })
            })
        })
    }

    fn GetContentDocument(self) -> Option<Temporary<Document>> {
        self.GetContentWindow().root().and_then(|window| {
            let self_url = match self.get_url() {
                Some(self_url) => self_url,
                None => return None,
            };
            let win_url = window_from_node(self).root().r().page().get_url();

            if UrlHelper::SameOrigin(&self_url, &win_url) {
                Some(window.r().Document())
            } else {
                None
            }
        })
    }
}

impl<'a> VirtualMethods for JSRef<'a, HTMLIFrameElement> {
    fn super_type<'b>(&'b self) -> Option<&'b VirtualMethods> {
        let htmlelement: &JSRef<HTMLElement> = HTMLElementCast::from_borrowed_ref(self);
        Some(htmlelement as &VirtualMethods)
    }

    fn after_set_attr(&self, attr: JSRef<Attr>) {
        match self.super_type() {
            Some(ref s) => s.after_set_attr(attr),
            _ => ()
        }

        match attr.local_name() {
            &atom!("sandbox") => {
                let mut modes = SandboxAllowance::AllowNothing as u8;
                if let Some(ref tokens) = attr.value().tokens() {
                    for token in tokens.iter() {
                        modes |= match token.as_slice().to_ascii_lowercase().as_slice() {
                            "allow-same-origin" => SandboxAllowance::AllowSameOrigin,
                            "allow-forms" => SandboxAllowance::AllowForms,
                            "allow-pointer-lock" => SandboxAllowance::AllowPointerLock,
                            "allow-popups" => SandboxAllowance::AllowPopups,
                            "allow-scripts" => SandboxAllowance::AllowScripts,
                            "allow-top-navigation" => SandboxAllowance::AllowTopNavigation,
                            _ => SandboxAllowance::AllowNothing
                        } as u8;
                    }
                }
                self.sandbox.set(Some(modes));
            },
            &atom!("src") => {
                let node: JSRef<Node> = NodeCast::from_ref(*self);
                if node.is_in_doc() {
                    self.process_the_iframe_attributes()
                }
            },
            _ => ()
        }
    }

    fn before_remove_attr(&self, attr: JSRef<Attr>) {
        match self.super_type() {
            Some(ref s) => s.before_remove_attr(attr),
            _ => ()
        }

        match attr.local_name() {
            &atom!("sandbox") => self.sandbox.set(None),
            _ => ()
        }
    }

    fn bind_to_tree(&self, tree_in_doc: bool) {
        match self.super_type() {
            Some(ref s) => s.bind_to_tree(tree_in_doc),
            _ => (),
        }

        if tree_in_doc {
            self.process_the_iframe_attributes();
        }
    }
}

<commit_msg>Parsing sandbox attribute on iframe elements<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use dom::attr::Attr;
use dom::attr::AttrValue;
use dom::attr::AttrHelpers;
use dom::bindings::codegen::Bindings::HTMLIFrameElementBinding;
use dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElementMethods;
use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use dom::bindings::codegen::InheritTypes::{NodeCast, ElementCast};
use dom::bindings::codegen::InheritTypes::{HTMLElementCast, HTMLIFrameElementDerived};
use dom::bindings::js::{JSRef, Temporary, OptionalRootable};
use dom::document::Document;
use dom::element::Element;
use dom::element::AttributeHandlers;
use dom::eventtarget::{EventTarget, EventTargetTypeId};
use dom::element::ElementTypeId;
use dom::htmlelement::{HTMLElement, HTMLElementTypeId};
use dom::node::{Node, NodeHelpers, NodeTypeId, window_from_node};
use dom::urlhelper::UrlHelper;
use dom::virtualmethods::VirtualMethods;
use dom::window::Window;
use page::{IterablePage, Page};

use servo_msg::constellation_msg::{PipelineId, SubpageId, ConstellationChan};
use servo_msg::constellation_msg::IFrameSandboxState::{IFrameSandboxed, IFrameUnsandboxed};
use servo_msg::constellation_msg::Msg as ConstellationMsg;
use util::str::DOMString;
use string_cache::Atom;

use std::ascii::AsciiExt;
use std::cell::Cell;
use url::{Url, UrlParser};

enum SandboxAllowance {
    AllowNothing = 0x00,
    AllowSameOrigin = 0x01,
    AllowTopNavigation = 0x02,
    AllowForms = 0x04,
    AllowScripts = 0x08,
    AllowPointerLock = 0x10,
    AllowPopups = 0x20
}

#[dom_struct]
pub struct HTMLIFrameElement {
    htmlelement: HTMLElement,
    subpage_id: Cell<Option<SubpageId>>,
    containing_page_pipeline_id: Cell<Option<PipelineId>>,
    sandbox: Cell<Option<u8>>,
}

impl HTMLIFrameElementDerived for EventTarget {
    fn is_htmliframeelement(&self) -> bool {
        *self.type_id() == EventTargetTypeId::Node(NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLIFrameElement)))
    }
}

pub trait HTMLIFrameElementHelpers {
    fn is_sandboxed(self) -> bool;
    fn get_url(self) -> Option<Url>;
    /// http://www.whatwg.org/html/#process-the-iframe-attributes
    fn process_the_iframe_attributes(self);
    fn generate_new_subpage_id(self, page: &Page) -> (SubpageId, Option<SubpageId>);
}

impl<'a> HTMLIFrameElementHelpers for JSRef<'a, HTMLIFrameElement> {
    fn is_sandboxed(self) -> bool {
        self.sandbox.get().is_some()
    }

    fn get_url(self) -> Option<Url> {
        let element: JSRef<Element> = ElementCast::from_ref(self);
        element.get_attribute(ns!(""), &atom!("src")).root().and_then(|src| {
            let url = src.r().value();
            if url.as_slice().is_empty() {
                None
            } else {
                let window = window_from_node(self).root();
                UrlParser::new().base_url(&window.r().page().get_url())
                    .parse(url.as_slice()).ok()
            }
        })
    }

    fn generate_new_subpage_id(self, page: &Page) -> (SubpageId, Option<SubpageId>) {
        let old_subpage_id = self.subpage_id.get();
        let subpage_id = page.get_next_subpage_id();
        self.subpage_id.set(Some(subpage_id));
        (subpage_id, old_subpage_id)
    }

    fn process_the_iframe_attributes(self) {
        let url = match self.get_url() {
            Some(url) => url.clone(),
            None => Url::parse("about:blank").unwrap(),
        };

        let sandboxed = if self.is_sandboxed() {
            IFrameSandboxed
        } else {
            IFrameUnsandboxed
        };

        let window = window_from_node(self).root();
        let window = window.r();
        let page = window.page();
        let (new_subpage_id, old_subpage_id) = self.generate_new_subpage_id(page);

        self.containing_page_pipeline_id.set(Some(page.id));

        let ConstellationChan(ref chan) = page.constellation_chan;
    chan.send(ConstellationMsg::ScriptLoadedURLInIFrame(url,
                                                        page.id,
                                                        new_subpage_id,
                                                        old_subpage_id,
                                                        sandboxed)).unwrap();
    }
}

impl HTMLIFrameElement {
    fn new_inherited(localName: DOMString, prefix: Option<DOMString>, document: JSRef<Document>) -> HTMLIFrameElement {
        HTMLIFrameElement {
            htmlelement: HTMLElement::new_inherited(HTMLElementTypeId::HTMLIFrameElement, localName, prefix, document),
            subpage_id: Cell::new(None),
            containing_page_pipeline_id: Cell::new(None),
            sandbox: Cell::new(None),
        }
    }

    #[allow(unrooted_must_root)]
    pub fn new(localName: DOMString, prefix: Option<DOMString>, document: JSRef<Document>) -> Temporary<HTMLIFrameElement> {
        let element = HTMLIFrameElement::new_inherited(localName, prefix, document);
        Node::reflect_node(box element, document, HTMLIFrameElementBinding::Wrap)
    }

    #[inline]
    pub fn containing_page_pipeline_id(&self) -> Option<PipelineId> {
        self.containing_page_pipeline_id.get()
    }

    #[inline]
    pub fn subpage_id(&self) -> Option<SubpageId> {
        self.subpage_id.get()
    }
}

impl<'a> HTMLIFrameElementMethods for JSRef<'a, HTMLIFrameElement> {
    fn Src(self) -> DOMString {
        let element: JSRef<Element> = ElementCast::from_ref(self);
        element.get_string_attribute(&atom!("src"))
    }

    fn SetSrc(self, src: DOMString) {
        let element: JSRef<Element> = ElementCast::from_ref(self);
        element.set_url_attribute(&atom!("src"), src)
    }

    fn Sandbox(self) -> DOMString {
        let element: JSRef<Element> = ElementCast::from_ref(self);
        element.get_string_attribute(&atom!("sandbox"))
    }

    fn SetSandbox(self, sandbox: DOMString) {
        let element: JSRef<Element> = ElementCast::from_ref(self);
        element.set_tokenlist_attribute(&atom!("sandbox"), sandbox);
    }

    fn GetContentWindow(self) -> Option<Temporary<Window>> {
        self.subpage_id.get().and_then(|subpage_id| {
            let window = window_from_node(self).root();
            let children = window.page().children.borrow();
            let child = children.iter().find(|child| {
                child.subpage_id.unwrap() == subpage_id
            });
            child.and_then(|page| {
                page.frame.borrow().as_ref().map(|frame| {
                    Temporary::new(frame.window.clone())
                })
            })
        })
    }

    fn GetContentDocument(self) -> Option<Temporary<Document>> {
        self.GetContentWindow().root().and_then(|window| {
            let self_url = match self.get_url() {
                Some(self_url) => self_url,
                None => return None,
            };
            let win_url = window_from_node(self).root().r().page().get_url();

            if UrlHelper::SameOrigin(&self_url, &win_url) {
                Some(window.r().Document())
            } else {
                None
            }
        })
    }
}

impl<'a> VirtualMethods for JSRef<'a, HTMLIFrameElement> {
    fn super_type<'b>(&'b self) -> Option<&'b VirtualMethods> {
        let htmlelement: &JSRef<HTMLElement> = HTMLElementCast::from_borrowed_ref(self);
        Some(htmlelement as &VirtualMethods)
    }

    fn after_set_attr(&self, attr: JSRef<Attr>) {
        match self.super_type() {
            Some(ref s) => s.after_set_attr(attr),
            _ => ()
        }

        match attr.local_name() {
            &atom!("sandbox") => {
                let mut modes = SandboxAllowance::AllowNothing as u8;
                if let Some(ref tokens) = attr.value().tokens() {
                    for token in tokens.iter() {
                        modes |= match token.as_slice().to_ascii_lowercase().as_slice() {
                            "allow-same-origin" => SandboxAllowance::AllowSameOrigin,
                            "allow-forms" => SandboxAllowance::AllowForms,
                            "allow-pointer-lock" => SandboxAllowance::AllowPointerLock,
                            "allow-popups" => SandboxAllowance::AllowPopups,
                            "allow-scripts" => SandboxAllowance::AllowScripts,
                            "allow-top-navigation" => SandboxAllowance::AllowTopNavigation,
                            _ => SandboxAllowance::AllowNothing
                        } as u8;
                    }
                }
                self.sandbox.set(Some(modes));
            },
            &atom!("src") => {
                let node: JSRef<Node> = NodeCast::from_ref(*self);
                if node.is_in_doc() {
                    self.process_the_iframe_attributes()
                }
            },
            _ => ()
        }
    }

    fn parse_plain_attribute(&self, name: &Atom, value: DOMString) -> AttrValue {
        match name {
            &atom!("sandbox") => AttrValue::from_serialized_tokenlist(value),
            _ => self.super_type().unwrap().parse_plain_attribute(name, value),
        }
    }

    fn before_remove_attr(&self, attr: JSRef<Attr>) {
        match self.super_type() {
            Some(ref s) => s.before_remove_attr(attr),
            _ => ()
        }

        match attr.local_name() {
            &atom!("sandbox") => self.sandbox.set(None),
            _ => ()
        }
    }

    fn bind_to_tree(&self, tree_in_doc: bool) {
        match self.super_type() {
            Some(ref s) => s.bind_to_tree(tree_in_doc),
            _ => (),
        }

        if tree_in_doc {
            self.process_the_iframe_attributes();
        }
    }
}

<|endoftext|>


<commit_before>// Copyright (c) 2016-2017 Chef Software Inc. and/or applicable contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Provides facilities for notifying when any spec files have changed
//! on disk. This is how we know when to start, stop, or restart
//! services in response to the various `hab svc` commands.

use super::spec_dir::SpecDir;
use crate::error::{Error,
                   Result};
use habitat_core::env::Config as EnvConfig;
use notify::{DebouncedEvent,
             RecommendedWatcher,
             RecursiveMode,
             Watcher};
use std::{num::ParseIntError,
          str::FromStr,
          sync::mpsc,
          thread::Builder,
          time::Duration};

static LOGKEY: &'static str = "SW";

/// How long should we wait to consolidate filesystem events?
///
/// This should strike a balance between responsiveness and
/// too-granular a series of events.
///
/// See https://docs.rs/notify/4.0.6/notify/trait.Watcher.html#tymethod.new
struct SpecWatcherDelay(Duration);

impl From<Duration> for SpecWatcherDelay {
    fn from(d: Duration) -> SpecWatcherDelay { SpecWatcherDelay(d) }
}

impl Default for SpecWatcherDelay {
    fn default() -> Self {
        // There's nothing particularly magical about 2s, particularly
        // since we're monitoring at such a coarse level ("something
        // happened in this directory").
        //
        // Smaller is probably fine, but you wouldn't want to go much
        // higher, as this could extend the amount of time you'd need
        // to wait before realizing you need to take action on a
        // service.
        Duration::from_secs(2).into()
    }
}

impl FromStr for SpecWatcherDelay {
    type Err = ParseIntError;

    fn from_str(s: &str) -> ::std::result::Result<Self, Self::Err> {
        Ok(Duration::from_millis(s.parse()?).into())
    }
}

impl EnvConfig for SpecWatcherDelay {
    const ENVVAR: &'static str = "HAB_SPEC_WATCHER_DELAY_MS";
}

// TODO (CM): A strong argument could be made for folding the
// SpecWatcher functionality into SpecDir itself.

/// Provides an abstraction layer over filesystem notifications for
/// spec files.
pub struct SpecWatcher {
    // Not actually used; only holding onto it for lifetime / Drop
    // purposes (`Drop` kills the threads that the watcher spawns to do
    // its work).
    _watcher: RecommendedWatcher,
    channel:  mpsc::Receiver<DebouncedEvent>,
}

impl SpecWatcher {
    /// Start up a separate thread to listen for filesystem
    /// events.
    pub fn run(spec_dir: &SpecDir) -> Result<SpecWatcher> {
        // The act of creating a `notify::Watcher` creates threads on
        // its own. It does not, however, allow you to set the _names_
        // of those threads.
        //
        // We're creating a SpecWatcher in a thread just so we can get
        // some control over the name of the threads that the
        // underlying `notify::Watcher` creates (_that_ is what this
        // function's documentation is referring to), which makes
        // monitoring and reasoning about the overall Supervisor
        // process easier. There's no other reason than that; if the
        // `notify` crate allowed us to name the threads, we could
        // just use a slightly modified version of `SpecWatcher::new`
        // instead.

        // I'd rather not have to do this clone, but it's a side
        // effect of this thread business. Better to eat it here
        // rather than at the callsite; having this function take a
        // reference is the true API we want.
        let dir = spec_dir.clone();

        Builder::new().name(String::from("spec-watcher"))
                      .spawn(move || Self::new(&dir))?
                      .join()
                      .map_err(|_| {
                          error!("SpecWatcher spawning thread panicked!");
                          sup_error!(Error::SpecWatcherNotCreated)
                      })?
    }

    /// Isolates the pure creation logic of a `SpecWatcher`, separate
    /// from the thread-based creation we use in `SpecWatcher::run` to
    /// get control over the names of the resulting threads. If we
    /// didn't care what the resulting watcher threads were named,
    /// we'd just use this directly.
    fn new(spec_dir: &SpecDir) -> Result<SpecWatcher> {
        let (tx, rx) = mpsc::channel();
        let delay = SpecWatcherDelay::configured_value();
        let mut watcher = RecommendedWatcher::new(tx, delay.0)?;
        watcher.watch(spec_dir, RecursiveMode::NonRecursive)?;
        Ok(SpecWatcher { _watcher: watcher,
                         channel:  rx, })
    }

    /// Returns `true` if _any_ filesystem events were detected in the
    /// watched directory.
    ///
    /// We are opting for this coarse granularity because it is
    /// difficult, if not impossible, to rely on the resulting stream
    /// of notification events as a 100% complete and accurate record
    /// of everything that takes place in the specs directory. For
    /// example, because of how we write files to temporary locations,
    /// then rename in order to achieve "atomic writes", it is
    /// possible to miss the events that would cause you to see this
    /// as a "rename"; instead, you would see a "new" file (even with
    /// a debounce time of 0 ms!)
    ///
    /// In any event, we would _still_ need to examine on-disk state
    /// of the directory and compare to our in-memory state to know
    /// exactly what to do in order to reconcile the two (i.e., you
    /// can't know whether to start, stop, or restart a service just
    /// by knowing that the spec file changed; you have to actually
    /// look at the current contents to figure that out).
    ///
    /// As a result, we're just using this as a coarse "something
    /// changed in the directory" signal. We are not filtering events
    /// to receive only those that affect `*.spec` files, so we may
    /// respond to modifications to temporary files, or indeed any
    /// file, within the specs directory (e.g., running `touch
    /// /hab/sup/default/specs/blahblah` would count as an event). It
    /// is possible to perform this filtering, of course, but it's not
    /// clear that the extra code would be worth it.
    pub fn has_events(&self) -> bool {
        let events = self.channel.try_iter().collect::<Vec<_>>();
        if events.is_empty() {
            false
        } else {
            trace!("SpecWatcher events: {:?}", events);
            true
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use habitat_common::locked_env_var;
    use std::{fs::File,
              io::{Error as IoError,
                   Write},
              result::Result as StdResult,
              thread};
    use tempfile::TempDir;

    locked_env_var!(HAB_SPEC_WATCHER_DELAY_MS, lock_delay_var);

    fn file_with_content<C>(dir: &TempDir, filename: &str, contents: C) -> StdResult<(), IoError>
        where C: Into<String>
    {
        let path = dir.path().join(filename);
        let mut buffer = File::create(&path)?;
        buffer.write_all(contents.into().as_bytes())
    }

    /// Sleep for the currently-configured debounce interval, plus a
    /// few milliseconds more, just to be certain our filesystem
    /// events have had plenty of time to process.
    fn wait_for_debounce_interval() {
        thread::sleep(SpecWatcherDelay::configured_value().0 + Duration::from_millis(2));
    }

    #[test]
    fn can_be_created() {
        let _delay = lock_delay_var();

        let dir = TempDir::new().expect("Could not create directory");
        let spec_dir = SpecDir::new(dir.path()).expect("Couldn't make SpecDir");
        assert!(SpecWatcher::run(&spec_dir).is_ok(),
                "Couldn't create a SpecWatcher!");
    }

    #[test]
    fn can_get_events_for_spec_files() {
        let _delay = lock_delay_var();

        let dir = TempDir::new().expect("Could not create directory");
        let spec_dir = SpecDir::new(dir.path()).expect("Couldn't make SpecDir");
        let sw = SpecWatcher::run(&spec_dir).expect("Couldn't create a SpecWatcher!");

        assert!(!sw.has_events(), "There should be no events to start");

        file_with_content(&dir, "foo.spec", "fooooooo").expect("couldn't create file");

        assert!(!sw.has_events(),
                "Need to allow for the debounce interval to pass before you can expect events");

        wait_for_debounce_interval();

        assert!(sw.has_events(), "There should be an event now");
        assert!(!sw.has_events(),
                "Should be no more events after you've checked");
    }

    /// Currently, the spec watcher will respond to changes to any
    /// file in the directory, whether it's a `*.spec` file or not.
    ///
    /// This would, for instance, pick up the temp files that
    /// operations like `hab svc stop` lay down before renaming them
    /// to their final `*.spec` form.
    #[test]
    fn can_get_events_for_non_spec_files() {
        let _delay = lock_delay_var();

        let dir = TempDir::new().expect("Could not create directory");
        let spec_dir = SpecDir::new(dir.path()).expect("Couldn't make SpecDir");
        let sw = SpecWatcher::run(&spec_dir).expect("Couldn't create a SpecWatcher!");

        assert!(!sw.has_events(), "There should be no events to start");

        file_with_content(&dir, "foo.abc123xyz", "fooooooo").expect("couldn't create file");

        assert!(!sw.has_events(),
                "Need to allow for the debounce interval to pass before you can expect events");

        wait_for_debounce_interval();

        assert!(sw.has_events(), "There should be an event now");
        assert!(!sw.has_events(),
                "Should be no more events after you've checked");
    }

    #[test]
    fn short_debounce_delays_also_work() {
        let delay = lock_delay_var();
        delay.set("1");

        // Just verifying that our delay variable works correctly
        assert_eq!(SpecWatcherDelay::configured_value().0,
                   Duration::from_millis(1));

        let dir = TempDir::new().expect("Could not create directory");
        let spec_dir = SpecDir::new(dir.path()).expect("Couldn't make SpecDir");
        let sw = SpecWatcher::run(&spec_dir).expect("Couldn't create a SpecWatcher!");

        assert!(!sw.has_events(), "There should be no events to start");

        file_with_content(&dir, "foo.spec", "fooooooo").expect("couldn't create file");

        assert!(!sw.has_events(),
                "Need to allow for the debounce interval to pass before you can expect events");

        wait_for_debounce_interval();

        assert!(sw.has_events(), "There should be an event now");
        assert!(!sw.has_events(),
                "Should be no more events after you've checked");
    }
}
<commit_msg>Add debugging for can_get_events_for_non_spec_files<commit_after>// Copyright (c) 2016-2017 Chef Software Inc. and/or applicable contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Provides facilities for notifying when any spec files have changed
//! on disk. This is how we know when to start, stop, or restart
//! services in response to the various `hab svc` commands.

use super::spec_dir::SpecDir;
use crate::error::{Error,
                   Result};
use habitat_core::env::Config as EnvConfig;
use notify::{DebouncedEvent,
             RecommendedWatcher,
             RecursiveMode,
             Watcher};
use std::{num::ParseIntError,
          str::FromStr,
          sync::mpsc,
          thread::Builder,
          time::Duration};

static LOGKEY: &'static str = "SW";

/// How long should we wait to consolidate filesystem events?
///
/// This should strike a balance between responsiveness and
/// too-granular a series of events.
///
/// See https://docs.rs/notify/4.0.6/notify/trait.Watcher.html#tymethod.new
struct SpecWatcherDelay(Duration);

impl From<Duration> for SpecWatcherDelay {
    fn from(d: Duration) -> SpecWatcherDelay { SpecWatcherDelay(d) }
}

impl Default for SpecWatcherDelay {
    fn default() -> Self {
        // There's nothing particularly magical about 2s, particularly
        // since we're monitoring at such a coarse level ("something
        // happened in this directory").
        //
        // Smaller is probably fine, but you wouldn't want to go much
        // higher, as this could extend the amount of time you'd need
        // to wait before realizing you need to take action on a
        // service.
        Duration::from_secs(2).into()
    }
}

impl FromStr for SpecWatcherDelay {
    type Err = ParseIntError;

    fn from_str(s: &str) -> ::std::result::Result<Self, Self::Err> {
        Ok(Duration::from_millis(s.parse()?).into())
    }
}

impl EnvConfig for SpecWatcherDelay {
    const ENVVAR: &'static str = "HAB_SPEC_WATCHER_DELAY_MS";
}

// TODO (CM): A strong argument could be made for folding the
// SpecWatcher functionality into SpecDir itself.

/// Provides an abstraction layer over filesystem notifications for
/// spec files.
pub struct SpecWatcher {
    // Not actually used; only holding onto it for lifetime / Drop
    // purposes (`Drop` kills the threads that the watcher spawns to do
    // its work).
    _watcher: RecommendedWatcher,
    channel:  mpsc::Receiver<DebouncedEvent>,
}

impl SpecWatcher {
    /// Start up a separate thread to listen for filesystem
    /// events.
    pub fn run(spec_dir: &SpecDir) -> Result<SpecWatcher> {
        // The act of creating a `notify::Watcher` creates threads on
        // its own. It does not, however, allow you to set the _names_
        // of those threads.
        //
        // We're creating a SpecWatcher in a thread just so we can get
        // some control over the name of the threads that the
        // underlying `notify::Watcher` creates (_that_ is what this
        // function's documentation is referring to), which makes
        // monitoring and reasoning about the overall Supervisor
        // process easier. There's no other reason than that; if the
        // `notify` crate allowed us to name the threads, we could
        // just use a slightly modified version of `SpecWatcher::new`
        // instead.

        // I'd rather not have to do this clone, but it's a side
        // effect of this thread business. Better to eat it here
        // rather than at the callsite; having this function take a
        // reference is the true API we want.
        let dir = spec_dir.clone();

        Builder::new().name(String::from("spec-watcher"))
                      .spawn(move || Self::new(&dir))?
                      .join()
                      .map_err(|_| {
                          error!("SpecWatcher spawning thread panicked!");
                          sup_error!(Error::SpecWatcherNotCreated)
                      })?
    }

    /// Isolates the pure creation logic of a `SpecWatcher`, separate
    /// from the thread-based creation we use in `SpecWatcher::run` to
    /// get control over the names of the resulting threads. If we
    /// didn't care what the resulting watcher threads were named,
    /// we'd just use this directly.
    fn new(spec_dir: &SpecDir) -> Result<SpecWatcher> {
        let (tx, rx) = mpsc::channel();
        let delay = SpecWatcherDelay::configured_value();
        let mut watcher = RecommendedWatcher::new(tx, delay.0)?;
        watcher.watch(spec_dir, RecursiveMode::NonRecursive)?;
        Ok(SpecWatcher { _watcher: watcher,
                         channel:  rx, })
    }

    /// Returns `true` if _any_ filesystem events were detected in the
    /// watched directory.
    ///
    /// We are opting for this coarse granularity because it is
    /// difficult, if not impossible, to rely on the resulting stream
    /// of notification events as a 100% complete and accurate record
    /// of everything that takes place in the specs directory. For
    /// example, because of how we write files to temporary locations,
    /// then rename in order to achieve "atomic writes", it is
    /// possible to miss the events that would cause you to see this
    /// as a "rename"; instead, you would see a "new" file (even with
    /// a debounce time of 0 ms!)
    ///
    /// In any event, we would _still_ need to examine on-disk state
    /// of the directory and compare to our in-memory state to know
    /// exactly what to do in order to reconcile the two (i.e., you
    /// can't know whether to start, stop, or restart a service just
    /// by knowing that the spec file changed; you have to actually
    /// look at the current contents to figure that out).
    ///
    /// As a result, we're just using this as a coarse "something
    /// changed in the directory" signal. We are not filtering events
    /// to receive only those that affect `*.spec` files, so we may
    /// respond to modifications to temporary files, or indeed any
    /// file, within the specs directory (e.g., running `touch
    /// /hab/sup/default/specs/blahblah` would count as an event). It
    /// is possible to perform this filtering, of course, but it's not
    /// clear that the extra code would be worth it.
    pub fn has_events(&self) -> bool {
        let events = self.channel.try_iter().collect::<Vec<_>>();
        if events.is_empty() {
            false
        } else {
            trace!("SpecWatcher events: {:?}", events);
            true
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use habitat_common::locked_env_var;
    use std::{fs::File,
              io::{Error as IoError,
                   Write},
              result::Result as StdResult,
              thread};
    use tempfile::TempDir;

    locked_env_var!(HAB_SPEC_WATCHER_DELAY_MS, lock_delay_var);

    fn file_with_content<C>(dir: &TempDir, filename: &str, contents: C) -> StdResult<(), IoError>
        where C: Into<String>
    {
        let path = dir.path().join(filename);
        let mut buffer = File::create(&path)?;
        buffer.write_all(contents.into().as_bytes())
    }

    /// Sleep for the currently-configured debounce interval, plus a
    /// few milliseconds more, just to be certain our filesystem
    /// events have had plenty of time to process.
    fn wait_for_debounce_interval() {
        let wait_duration = SpecWatcherDelay::configured_value().0 + Duration::from_millis(2);
        trace!("wait_for_debounce_interval: waiting {:?}", wait_duration);
        thread::sleep(wait_duration);
    }

    #[test]
    fn can_be_created() {
        let _delay = lock_delay_var();

        let dir = TempDir::new().expect("Could not create directory");
        let spec_dir = SpecDir::new(dir.path()).expect("Couldn't make SpecDir");
        assert!(SpecWatcher::run(&spec_dir).is_ok(),
                "Couldn't create a SpecWatcher!");
    }

    #[test]
    fn can_get_events_for_spec_files() {
        let _delay = lock_delay_var();

        let dir = TempDir::new().expect("Could not create directory");
        let spec_dir = SpecDir::new(dir.path()).expect("Couldn't make SpecDir");
        let sw = SpecWatcher::run(&spec_dir).expect("Couldn't create a SpecWatcher!");

        assert!(!sw.has_events(), "There should be no events to start");

        file_with_content(&dir, "foo.spec", "fooooooo").expect("couldn't create file");

        assert!(!sw.has_events(),
                "Need to allow for the debounce interval to pass before you can expect events");

        wait_for_debounce_interval();

        assert!(sw.has_events(), "There should be an event now");
        assert!(!sw.has_events(),
                "Should be no more events after you've checked");
    }

    /// Currently, the spec watcher will respond to changes to any
    /// file in the directory, whether it's a `*.spec` file or not.
    ///
    /// This would, for instance, pick up the temp files that
    /// operations like `hab svc stop` lay down before renaming them
    /// to their final `*.spec` form.
    #[test]
    fn can_get_events_for_non_spec_files() {
        env_logger::try_init().ok();
        debug!("can_get_events_for_non_spec_files starting");
        let _delay = lock_delay_var();

        let dir = TempDir::new().expect("Could not create directory");
        let spec_dir = SpecDir::new(dir.path()).expect("Couldn't make SpecDir");
        let sw = SpecWatcher::run(&spec_dir).expect("Couldn't create a SpecWatcher!");

        assert!(!sw.has_events(), "There should be no events to start");

        file_with_content(&dir, "foo.abc123xyz", "fooooooo").expect("couldn't create file");

        assert!(!sw.has_events(),
                "Need to allow for the debounce interval to pass before you can expect events");

        while !sw.has_events() {
            wait_for_debounce_interval();
        }

        // assert!(sw.has_events(), "There should be an event now");
        assert!(!sw.has_events(),
                "Should be no more events after you've checked");
        debug!("can_get_events_for_non_spec_files done");
    }

    #[test]
    fn short_debounce_delays_also_work() {
        let delay = lock_delay_var();
        delay.set("1");

        // Just verifying that our delay variable works correctly
        assert_eq!(SpecWatcherDelay::configured_value().0,
                   Duration::from_millis(1));

        let dir = TempDir::new().expect("Could not create directory");
        let spec_dir = SpecDir::new(dir.path()).expect("Couldn't make SpecDir");
        let sw = SpecWatcher::run(&spec_dir).expect("Couldn't create a SpecWatcher!");

        assert!(!sw.has_events(), "There should be no events to start");

        file_with_content(&dir, "foo.spec", "fooooooo").expect("couldn't create file");

        assert!(!sw.has_events(),
                "Need to allow for the debounce interval to pass before you can expect events");

        wait_for_debounce_interval();

        assert!(sw.has_events(), "There should be an event now");
        assert!(!sw.has_events(),
                "Should be no more events after you've checked");
    }
}
<|endoftext|>


<commit_before>// Copyright 2012-2013 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.

// Finds items that are externally reachable, to determine which items
// need to have their metadata (and possibly their AST) serialized.
// All items that can be referred to through an exported name are
// reachable, and when a reachable thing is inline or generic, it
// makes all other generics or inline functions that it references
// reachable as well.

use front::map as ast_map;
use middle::def;
use middle::def_id::{DefId, LOCAL_CRATE};
use middle::ty;
use middle::privacy;
use session::config;
use util::nodemap::NodeSet;

use std::collections::HashSet;
use syntax::abi;
use syntax::ast;
use rustc_front::hir;
use rustc_front::attr;
use rustc_front::visit::Visitor;
use rustc_front::visit;

// Returns true if the given set of generics implies that the item it's
// associated with must be inlined.
fn generics_require_inlining(generics: &hir::Generics) -> bool {
    !generics.ty_params.is_empty()
}

// Returns true if the given item must be inlined because it may be
// monomorphized or it was marked with `#[inline]`. This will only return
// true for functions.
fn item_might_be_inlined(item: &hir::Item) -> bool {
    if attr::requests_inline(&item.attrs) {
        return true
    }

    match item.node {
        hir::ItemImpl(_, _, ref generics, _, _, _) |
        hir::ItemFn(_, _, _, _, ref generics, _) => {
            generics_require_inlining(generics)
        }
        _ => false,
    }
}

fn method_might_be_inlined(tcx: &ty::ctxt, sig: &hir::MethodSig,
                           impl_item: &hir::ImplItem,
                           impl_src: DefId) -> bool {
    if attr::requests_inline(&impl_item.attrs) ||
        generics_require_inlining(&sig.generics) {
        return true
    }
    if impl_src.is_local() {
        {
            match tcx.map.find(impl_src.node) {
                Some(ast_map::NodeItem(item)) => {
                    item_might_be_inlined(&*item)
                }
                Some(..) | None => {
                    tcx.sess.span_bug(impl_item.span, "impl did is not an item")
                }
            }
        }
    } else {
        tcx.sess.span_bug(impl_item.span, "found a foreign impl as a parent \
                                           of a local method")
    }
}

// Information needed while computing reachability.
struct ReachableContext<'a, 'tcx: 'a> {
    // The type context.
    tcx: &'a ty::ctxt<'tcx>,
    // The set of items which must be exported in the linkage sense.
    reachable_symbols: NodeSet,
    // A worklist of item IDs. Each item ID in this worklist will be inlined
    // and will be scanned for further references.
    worklist: Vec<ast::NodeId>,
    // Whether any output of this compilation is a library
    any_library: bool,
}

impl<'a, 'tcx, 'v> Visitor<'v> for ReachableContext<'a, 'tcx> {

    fn visit_expr(&mut self, expr: &hir::Expr) {

        match expr.node {
            hir::ExprPath(..) => {
                let def = match self.tcx.def_map.borrow().get(&expr.id) {
                    Some(d) => d.full_def(),
                    None => {
                        self.tcx.sess.span_bug(expr.span,
                                               "def ID not in def map?!")
                    }
                };

                let def_id = def.def_id();
                if def_id.is_local() {
                    if self.def_id_represents_local_inlined_item(def_id) {
                        self.worklist.push(def_id.node)
                    } else {
                        match def {
                            // If this path leads to a constant, then we need to
                            // recurse into the constant to continue finding
                            // items that are reachable.
                            def::DefConst(..) | def::DefAssociatedConst(..) => {
                                self.worklist.push(def_id.node);
                            }

                            // If this wasn't a static, then the destination is
                            // surely reachable.
                            _ => {
                                self.reachable_symbols.insert(def_id.node);
                            }
                        }
                    }
                }
            }
            hir::ExprMethodCall(..) => {
                let method_call = ty::MethodCall::expr(expr.id);
                let def_id = self.tcx.tables.borrow().method_map[&method_call].def_id;
                match self.tcx.impl_or_trait_item(def_id).container() {
                    ty::ImplContainer(_) => {
                        if def_id.is_local() {
                            if self.def_id_represents_local_inlined_item(def_id) {
                                self.worklist.push(def_id.node)
                            }
                            self.reachable_symbols.insert(def_id.node);
                        }
                    }
                    ty::TraitContainer(_) => {}
                }
            }
            _ => {}
        }

        visit::walk_expr(self, expr)
    }

    fn visit_item(&mut self, _item: &hir::Item) {
        // Do not recurse into items. These items will be added to the worklist
        // and recursed into manually if necessary.
    }
}

impl<'a, 'tcx> ReachableContext<'a, 'tcx> {
    // Creates a new reachability computation context.
    fn new(tcx: &'a ty::ctxt<'tcx>) -> ReachableContext<'a, 'tcx> {
        let any_library = tcx.sess.crate_types.borrow().iter().any(|ty| {
            *ty != config::CrateTypeExecutable
        });
        ReachableContext {
            tcx: tcx,
            reachable_symbols: NodeSet(),
            worklist: Vec::new(),
            any_library: any_library,
        }
    }

    // Returns true if the given def ID represents a local item that is
    // eligible for inlining and false otherwise.
    fn def_id_represents_local_inlined_item(&self, def_id: DefId) -> bool {
        if def_id.krate != LOCAL_CRATE {
            return false
        }

        let node_id = def_id.node;
        match self.tcx.map.find(node_id) {
            Some(ast_map::NodeItem(item)) => {
                match item.node {
                    hir::ItemFn(..) => item_might_be_inlined(&*item),
                    _ => false,
                }
            }
            Some(ast_map::NodeTraitItem(trait_method)) => {
                match trait_method.node {
                    hir::ConstTraitItem(_, ref default) => default.is_some(),
                    hir::MethodTraitItem(_, ref body) => body.is_some(),
                    hir::TypeTraitItem(..) => false,
                }
            }
            Some(ast_map::NodeImplItem(impl_item)) => {
                match impl_item.node {
                    hir::ConstImplItem(..) => true,
                    hir::MethodImplItem(ref sig, _) => {
                        if generics_require_inlining(&sig.generics) ||
                                attr::requests_inline(&impl_item.attrs) {
                            true
                        } else {
                            let impl_did = self.tcx
                                               .map
                                               .get_parent_did(node_id);
                            // Check the impl. If the generics on the self
                            // type of the impl require inlining, this method
                            // does too.
                            assert!(impl_did.is_local());
                            match self.tcx
                                      .map
                                      .expect_item(impl_did.node)
                                      .node {
                                hir::ItemImpl(_, _, ref generics, _, _, _) => {
                                    generics_require_inlining(generics)
                                }
                                _ => false
                            }
                        }
                    }
                    hir::TypeImplItem(_) => false,
                }
            }
            Some(_) => false,
            None => false   // This will happen for default methods.
        }
    }

    // Step 2: Mark all symbols that the symbols on the worklist touch.
    fn propagate(&mut self) {
        let mut scanned = HashSet::new();
        loop {
            let search_item = match self.worklist.pop() {
                Some(item) => item,
                None => break,
            };
            if !scanned.insert(search_item) {
                continue
            }

            match self.tcx.map.find(search_item) {
                Some(ref item) => self.propagate_node(item, search_item),
                None if search_item == ast::CRATE_NODE_ID => {}
                None => {
                    self.tcx.sess.bug(&format!("found unmapped ID in worklist: \
                                               {}",
                                              search_item))
                }
            }
        }
    }

    fn propagate_node(&mut self, node: &ast_map::Node,
                      search_item: ast::NodeId) {
        if !self.any_library {
            // If we are building an executable, then there's no need to flag
            // anything as external except for `extern fn` types. These
            // functions may still participate in some form of native interface,
            // but all other rust-only interfaces can be private (they will not
            // participate in linkage after this product is produced)
            if let ast_map::NodeItem(item) = *node {
                if let hir::ItemFn(_, _, _, abi, _, _) = item.node {
                    if abi != abi::Rust {
                        self.reachable_symbols.insert(search_item);
                    }
                }
            }
        } else {
            // If we are building a library, then reachable symbols will
            // continue to participate in linkage after this product is
            // produced. In this case, we traverse the ast node, recursing on
            // all reachable nodes from this one.
            self.reachable_symbols.insert(search_item);
        }

        match *node {
            ast_map::NodeItem(item) => {
                match item.node {
                    hir::ItemFn(_, _, _, _, _, ref search_block) => {
                        if item_might_be_inlined(&*item) {
                            visit::walk_block(self, &**search_block)
                        }
                    }

                    // Reachable constants will be inlined into other crates
                    // unconditionally, so we need to make sure that their
                    // contents are also reachable.
                    hir::ItemConst(_, ref init) => {
                        self.visit_expr(&**init);
                    }

                    // These are normal, nothing reachable about these
                    // inherently and their children are already in the
                    // worklist, as determined by the privacy pass
                    hir::ItemExternCrate(_) | hir::ItemUse(_) |
                    hir::ItemTy(..) | hir::ItemStatic(_, _, _) |
                    hir::ItemMod(..) | hir::ItemForeignMod(..) |
                    hir::ItemImpl(..) | hir::ItemTrait(..) |
                    hir::ItemStruct(..) | hir::ItemEnum(..) |
                    hir::ItemDefaultImpl(..) => {}
                }
            }
            ast_map::NodeTraitItem(trait_method) => {
                match trait_method.node {
                    hir::ConstTraitItem(_, None) |
                    hir::MethodTraitItem(_, None) => {
                        // Keep going, nothing to get exported
                    }
                    hir::ConstTraitItem(_, Some(ref expr)) => {
                        self.visit_expr(&*expr);
                    }
                    hir::MethodTraitItem(_, Some(ref body)) => {
                        visit::walk_block(self, body);
                    }
                    hir::TypeTraitItem(..) => {}
                }
            }
            ast_map::NodeImplItem(impl_item) => {
                match impl_item.node {
                    hir::ConstImplItem(_, ref expr) => {
                        self.visit_expr(&*expr);
                    }
                    hir::MethodImplItem(ref sig, ref body) => {
                        let did = self.tcx.map.get_parent_did(search_item);
                        if method_might_be_inlined(self.tcx, sig, impl_item, did) {
                            visit::walk_block(self, body)
                        }
                    }
                    hir::TypeImplItem(_) => {}
                }
            }
            // Nothing to recurse on for these
            ast_map::NodeForeignItem(_) |
            ast_map::NodeVariant(_) |
            ast_map::NodeStructCtor(_) => {}
            _ => {
                self.tcx
                    .sess
                    .bug(&format!("found unexpected thingy in worklist: {}",
                                 self.tcx
                                     .map
                                     .node_to_string(search_item)))
            }
        }
    }

    // Step 3: Mark all destructors as reachable.
    //
    // FIXME(pcwalton): This is a conservative overapproximation, but fixing
    // this properly would result in the necessity of computing *type*
    // reachability, which might result in a compile time loss.
    fn mark_destructors_reachable(&mut self) {
        for adt in self.tcx.adt_defs() {
            if let Some(destructor_def_id) = adt.destructor() {
                if destructor_def_id.is_local() {
                    self.reachable_symbols.insert(destructor_def_id.node);
                }
            }
        }
    }
}

pub fn find_reachable(tcx: &ty::ctxt,
                      exported_items: &privacy::ExportedItems)
                      -> NodeSet {
    let mut reachable_context = ReachableContext::new(tcx);

    // Step 1: Seed the worklist with all nodes which were found to be public as
    //         a result of the privacy pass along with all local lang items. If
    //         other crates link to us, they're going to expect to be able to
    //         use the lang items, so we need to be sure to mark them as
    //         exported.
    for id in exported_items {
        reachable_context.worklist.push(*id);
    }
    for (_, item) in tcx.lang_items.items() {
        match *item {
            Some(did) if did.is_local() => {
                reachable_context.worklist.push(did.node);
            }
            _ => {}
        }
    }

    // Step 2: Mark all symbols that the symbols on the worklist touch.
    reachable_context.propagate();

    // Step 3: Mark all destructors as reachable.
    reachable_context.mark_destructors_reachable();

    // Return the set of reachable symbols.
    reachable_context.reachable_symbols
}
<commit_msg>Rollup merge of #28194 - steveklabnik:add_fixme, r=alexcrichton<commit_after>// Copyright 2012-2013 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.

// Finds items that are externally reachable, to determine which items
// need to have their metadata (and possibly their AST) serialized.
// All items that can be referred to through an exported name are
// reachable, and when a reachable thing is inline or generic, it
// makes all other generics or inline functions that it references
// reachable as well.

use front::map as ast_map;
use middle::def;
use middle::def_id::{DefId, LOCAL_CRATE};
use middle::ty;
use middle::privacy;
use session::config;
use util::nodemap::NodeSet;

use std::collections::HashSet;
use syntax::abi;
use syntax::ast;
use rustc_front::hir;
use rustc_front::attr;
use rustc_front::visit::Visitor;
use rustc_front::visit;

// Returns true if the given set of generics implies that the item it's
// associated with must be inlined.
fn generics_require_inlining(generics: &hir::Generics) -> bool {
    !generics.ty_params.is_empty()
}

// Returns true if the given item must be inlined because it may be
// monomorphized or it was marked with `#[inline]`. This will only return
// true for functions.
fn item_might_be_inlined(item: &hir::Item) -> bool {
    if attr::requests_inline(&item.attrs) {
        return true
    }

    match item.node {
        hir::ItemImpl(_, _, ref generics, _, _, _) |
        hir::ItemFn(_, _, _, _, ref generics, _) => {
            generics_require_inlining(generics)
        }
        _ => false,
    }
}

fn method_might_be_inlined(tcx: &ty::ctxt, sig: &hir::MethodSig,
                           impl_item: &hir::ImplItem,
                           impl_src: DefId) -> bool {
    if attr::requests_inline(&impl_item.attrs) ||
        generics_require_inlining(&sig.generics) {
        return true
    }
    if impl_src.is_local() {
        {
            match tcx.map.find(impl_src.node) {
                Some(ast_map::NodeItem(item)) => {
                    item_might_be_inlined(&*item)
                }
                Some(..) | None => {
                    tcx.sess.span_bug(impl_item.span, "impl did is not an item")
                }
            }
        }
    } else {
        tcx.sess.span_bug(impl_item.span, "found a foreign impl as a parent \
                                           of a local method")
    }
}

// Information needed while computing reachability.
struct ReachableContext<'a, 'tcx: 'a> {
    // The type context.
    tcx: &'a ty::ctxt<'tcx>,
    // The set of items which must be exported in the linkage sense.
    reachable_symbols: NodeSet,
    // A worklist of item IDs. Each item ID in this worklist will be inlined
    // and will be scanned for further references.
    worklist: Vec<ast::NodeId>,
    // Whether any output of this compilation is a library
    any_library: bool,
}

impl<'a, 'tcx, 'v> Visitor<'v> for ReachableContext<'a, 'tcx> {

    fn visit_expr(&mut self, expr: &hir::Expr) {

        match expr.node {
            hir::ExprPath(..) => {
                let def = match self.tcx.def_map.borrow().get(&expr.id) {
                    Some(d) => d.full_def(),
                    None => {
                        self.tcx.sess.span_bug(expr.span,
                                               "def ID not in def map?!")
                    }
                };

                let def_id = def.def_id();
                if def_id.is_local() {
                    if self.def_id_represents_local_inlined_item(def_id) {
                        self.worklist.push(def_id.node)
                    } else {
                        match def {
                            // If this path leads to a constant, then we need to
                            // recurse into the constant to continue finding
                            // items that are reachable.
                            def::DefConst(..) | def::DefAssociatedConst(..) => {
                                self.worklist.push(def_id.node);
                            }

                            // If this wasn't a static, then the destination is
                            // surely reachable.
                            _ => {
                                self.reachable_symbols.insert(def_id.node);
                            }
                        }
                    }
                }
            }
            hir::ExprMethodCall(..) => {
                let method_call = ty::MethodCall::expr(expr.id);
                let def_id = self.tcx.tables.borrow().method_map[&method_call].def_id;
                match self.tcx.impl_or_trait_item(def_id).container() {
                    ty::ImplContainer(_) => {
                        if def_id.is_local() {
                            if self.def_id_represents_local_inlined_item(def_id) {
                                self.worklist.push(def_id.node)
                            }
                            self.reachable_symbols.insert(def_id.node);
                        }
                    }
                    ty::TraitContainer(_) => {}
                }
            }
            _ => {}
        }

        visit::walk_expr(self, expr)
    }

    fn visit_item(&mut self, _item: &hir::Item) {
        // Do not recurse into items. These items will be added to the worklist
        // and recursed into manually if necessary.
    }
}

impl<'a, 'tcx> ReachableContext<'a, 'tcx> {
    // Creates a new reachability computation context.
    fn new(tcx: &'a ty::ctxt<'tcx>) -> ReachableContext<'a, 'tcx> {
        let any_library = tcx.sess.crate_types.borrow().iter().any(|ty| {
            *ty != config::CrateTypeExecutable
        });
        ReachableContext {
            tcx: tcx,
            reachable_symbols: NodeSet(),
            worklist: Vec::new(),
            any_library: any_library,
        }
    }

    // Returns true if the given def ID represents a local item that is
    // eligible for inlining and false otherwise.
    fn def_id_represents_local_inlined_item(&self, def_id: DefId) -> bool {
        if def_id.krate != LOCAL_CRATE {
            return false
        }

        let node_id = def_id.node;
        match self.tcx.map.find(node_id) {
            Some(ast_map::NodeItem(item)) => {
                match item.node {
                    hir::ItemFn(..) => item_might_be_inlined(&*item),
                    _ => false,
                }
            }
            Some(ast_map::NodeTraitItem(trait_method)) => {
                match trait_method.node {
                    hir::ConstTraitItem(_, ref default) => default.is_some(),
                    hir::MethodTraitItem(_, ref body) => body.is_some(),
                    hir::TypeTraitItem(..) => false,
                }
            }
            Some(ast_map::NodeImplItem(impl_item)) => {
                match impl_item.node {
                    hir::ConstImplItem(..) => true,
                    hir::MethodImplItem(ref sig, _) => {
                        if generics_require_inlining(&sig.generics) ||
                                attr::requests_inline(&impl_item.attrs) {
                            true
                        } else {
                            let impl_did = self.tcx
                                               .map
                                               .get_parent_did(node_id);
                            // Check the impl. If the generics on the self
                            // type of the impl require inlining, this method
                            // does too.
                            assert!(impl_did.is_local());
                            match self.tcx
                                      .map
                                      .expect_item(impl_did.node)
                                      .node {
                                hir::ItemImpl(_, _, ref generics, _, _, _) => {
                                    generics_require_inlining(generics)
                                }
                                _ => false
                            }
                        }
                    }
                    hir::TypeImplItem(_) => false,
                }
            }
            Some(_) => false,
            None => false   // This will happen for default methods.
        }
    }

    // Step 2: Mark all symbols that the symbols on the worklist touch.
    fn propagate(&mut self) {
        let mut scanned = HashSet::new();
        loop {
            let search_item = match self.worklist.pop() {
                Some(item) => item,
                None => break,
            };
            if !scanned.insert(search_item) {
                continue
            }

            match self.tcx.map.find(search_item) {
                Some(ref item) => self.propagate_node(item, search_item),
                None if search_item == ast::CRATE_NODE_ID => {}
                None => {
                    self.tcx.sess.bug(&format!("found unmapped ID in worklist: \
                                               {}",
                                              search_item))
                }
            }
        }
    }

    fn propagate_node(&mut self, node: &ast_map::Node,
                      search_item: ast::NodeId) {
        if !self.any_library {
            // If we are building an executable, then there's no need to flag
            // anything as external except for `extern fn` types. These
            // functions may still participate in some form of native interface,
            // but all other rust-only interfaces can be private (they will not
            // participate in linkage after this product is produced)
            if let ast_map::NodeItem(item) = *node {
                if let hir::ItemFn(_, _, _, abi, _, _) = item.node {
                    if abi != abi::Rust {
                        self.reachable_symbols.insert(search_item);
                    }
                }
            }
        } else {
            // If we are building a library, then reachable symbols will
            // continue to participate in linkage after this product is
            // produced. In this case, we traverse the ast node, recursing on
            // all reachable nodes from this one.
            self.reachable_symbols.insert(search_item);
        }

        match *node {
            ast_map::NodeItem(item) => {
                match item.node {
                    hir::ItemFn(_, _, _, _, _, ref search_block) => {
                        if item_might_be_inlined(&*item) {
                            visit::walk_block(self, &**search_block)
                        }
                    }

                    // Reachable constants will be inlined into other crates
                    // unconditionally, so we need to make sure that their
                    // contents are also reachable.
                    hir::ItemConst(_, ref init) => {
                        self.visit_expr(&**init);
                    }

                    // These are normal, nothing reachable about these
                    // inherently and their children are already in the
                    // worklist, as determined by the privacy pass
                    hir::ItemExternCrate(_) | hir::ItemUse(_) |
                    hir::ItemTy(..) | hir::ItemStatic(_, _, _) |
                    hir::ItemMod(..) | hir::ItemForeignMod(..) |
                    hir::ItemImpl(..) | hir::ItemTrait(..) |
                    hir::ItemStruct(..) | hir::ItemEnum(..) |
                    hir::ItemDefaultImpl(..) => {}
                }
            }
            ast_map::NodeTraitItem(trait_method) => {
                match trait_method.node {
                    hir::ConstTraitItem(_, None) |
                    hir::MethodTraitItem(_, None) => {
                        // Keep going, nothing to get exported
                    }
                    hir::ConstTraitItem(_, Some(ref expr)) => {
                        self.visit_expr(&*expr);
                    }
                    hir::MethodTraitItem(_, Some(ref body)) => {
                        visit::walk_block(self, body);
                    }
                    hir::TypeTraitItem(..) => {}
                }
            }
            ast_map::NodeImplItem(impl_item) => {
                match impl_item.node {
                    hir::ConstImplItem(_, ref expr) => {
                        self.visit_expr(&*expr);
                    }
                    hir::MethodImplItem(ref sig, ref body) => {
                        let did = self.tcx.map.get_parent_did(search_item);
                        if method_might_be_inlined(self.tcx, sig, impl_item, did) {
                            visit::walk_block(self, body)
                        }
                    }
                    hir::TypeImplItem(_) => {}
                }
            }
            // Nothing to recurse on for these
            ast_map::NodeForeignItem(_) |
            ast_map::NodeVariant(_) |
            ast_map::NodeStructCtor(_) => {}
            _ => {
                self.tcx
                    .sess
                    .bug(&format!("found unexpected thingy in worklist: {}",
                                 self.tcx
                                     .map
                                     .node_to_string(search_item)))
            }
        }
    }

    // Step 3: Mark all destructors as reachable.
    //
    // FIXME #10732: This is a conservative overapproximation, but fixing
    // this properly would result in the necessity of computing *type*
    // reachability, which might result in a compile time loss.
    fn mark_destructors_reachable(&mut self) {
        for adt in self.tcx.adt_defs() {
            if let Some(destructor_def_id) = adt.destructor() {
                if destructor_def_id.is_local() {
                    self.reachable_symbols.insert(destructor_def_id.node);
                }
            }
        }
    }
}

pub fn find_reachable(tcx: &ty::ctxt,
                      exported_items: &privacy::ExportedItems)
                      -> NodeSet {
    let mut reachable_context = ReachableContext::new(tcx);

    // Step 1: Seed the worklist with all nodes which were found to be public as
    //         a result of the privacy pass along with all local lang items. If
    //         other crates link to us, they're going to expect to be able to
    //         use the lang items, so we need to be sure to mark them as
    //         exported.
    for id in exported_items {
        reachable_context.worklist.push(*id);
    }
    for (_, item) in tcx.lang_items.items() {
        match *item {
            Some(did) if did.is_local() => {
                reachable_context.worklist.push(did.node);
            }
            _ => {}
        }
    }

    // Step 2: Mark all symbols that the symbols on the worklist touch.
    reachable_context.propagate();

    // Step 3: Mark all destructors as reachable.
    reachable_context.mark_destructors_reachable();

    // Return the set of reachable symbols.
    reachable_context.reachable_symbols
}
<|endoftext|>