<commit_before>use crate::config::*;
use crate::errors::RunError;
use cargo_metadata::{diagnostic::DiagnosticLevel, CargoOpt, Message, MetadataCommand};
use log::{error, info, trace, warn};
use std::collections::HashMap;
use std::env;
use std::fs::File;
use std::io;
use std::io::{BufRead, BufReader};
use std::path::{Component, Path, PathBuf};
use std::process::{Command, Stdio};
use walkdir::{DirEntry, WalkDir};

#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TestBinary {
    path: PathBuf,
    ty: RunType,
    cargo_dir: Option<PathBuf>,
    pkg_name: Option<String>,
    pkg_version: Option<String>,
    pkg_authors: Option<Vec<String>>,
    should_panic: bool,
}

impl TestBinary {
    pub fn new(path: PathBuf, ty: RunType) -> Self {
        Self {
            path,
            ty,
            pkg_name: None,
            pkg_version: None,
            pkg_authors: None,
            cargo_dir: None,
            should_panic: false,
        }
    }

    pub fn path(&self) -> &Path {
        &self.path
    }

    pub fn run_type(&self) -> RunType {
        self.ty
    }

    pub fn manifest_dir(&self) -> &Option<PathBuf> {
        &self.cargo_dir
    }

    pub fn pkg_name(&self) -> &Option<String> {
        &self.pkg_name
    }

    pub fn pkg_version(&self) -> &Option<String> {
        &self.pkg_version
    }

    pub fn pkg_authors(&self) -> &Option<Vec<String>> {
        &self.pkg_authors
    }

    /// Should be `false` for normal tests and for doctests either `true` or
    /// `false` depending on the test attribute
    pub fn should_panic(&self) -> bool {
        self.should_panic
    }
}

pub fn get_tests(config: &Config) -> Result<Vec<TestBinary>, RunError> {
    let mut result = vec![];
    let manifest = match config.manifest.as_path().to_str() {
        Some(s) => s,
        None => "Cargo.toml",
    };
    let metadata = MetadataCommand::new()
        .manifest_path(manifest)
        .features(CargoOpt::AllFeatures)
        .exec()
        .map_err(|e| RunError::Cargo(e.to_string()))?;

    for ty in &config.run_types {
        let mut cmd = create_command(manifest, config, ty);
        if ty != &RunType::Doctests {
            cmd.stdout(Stdio::piped());
        } else {
            cmd.stdout(Stdio::null());
        }
        if !config.verbose {
            cmd.stderr(Stdio::null());
        }
        trace!("Running command {:?}", cmd);
        let mut child = cmd.spawn().map_err(|e| RunError::Cargo(e.to_string()))?;

        if ty != &RunType::Doctests {
            let mut package_ids = vec![];
            let reader = std::io::BufReader::new(child.stdout.take().unwrap());
            for msg in Message::parse_stream(reader) {
                match msg {
                    Ok(Message::CompilerArtifact(art)) => {
                        if let Some(path) = art.executable {
                            if !art.profile.test && ty == &RunType::Tests {
                                continue;
                            }
                            result.push(TestBinary::new(path, *ty));
                            package_ids.push(art.package_id.clone());
                        }
                    }
                    Ok(Message::CompilerMessage(m)) => match m.message.level {
                        DiagnosticLevel::Error | DiagnosticLevel::Ice => {
                            let _ = child.wait();
                            return Err(RunError::TestCompile(m.message.message));
                        }
                        _ => {}
                    },
                    Err(e) => {
                        error!("Error parsing cargo messages {}", e);
                    }
                    _ => {}
                }
            }
            for (res, package) in result.iter_mut().zip(package_ids.iter()) {
                let package = &metadata[package];
                res.cargo_dir = package.manifest_path.parent().map(|x| x.to_path_buf());
                res.pkg_name = Some(package.name.clone());
                res.pkg_version = Some(package.version.to_string());
                res.pkg_authors = Some(package.authors.clone());
            }
            child.wait().map_err(|e| RunError::Cargo(e.to_string()))?;
        } else {
            // need to wait for compiling to finish before getting doctests
            // also need to wait with output to ensure the stdout buffer doesn't fill up
            let out = child
                .wait_with_output()
                .map_err(|e| RunError::Cargo(e.to_string()))?;
            if !out.status.success() {
                error!("Building doctests failed");
            }
            let walker = WalkDir::new(&config.doctest_dir()).into_iter();
            let dir_entries = walker
                .filter_map(|e| e.ok())
                .filter(|e| match e.metadata() {
                    Ok(ref m) if m.is_file() && m.len() != 0 => true,
                    _ => false,
                })
                .collect::<Vec<_>>();

            let should_panics = get_panic_candidates(&dir_entries, config);
            for dt in &dir_entries {
                trace!("Found doctest binary {}", dt.path().display());
                result.push(TestBinary::new(dt.path().to_path_buf(), *ty));
            }
        }
    }
    Ok(result)
}

fn convert_to_prefix(p: &Path) -> Option<String> {
    p.to_str()
        .map(|s| s.replace(std::path::MAIN_SEPARATOR, "_").replace(".", "_"))
}

fn is_prefix_match(prefix: &str, entry: &Path) -> bool {
    convert_to_prefix(entry)
        .map(|s| prefix.starts_with(&s))
        .unwrap_or(false)
}

/// This returns a map of the string prefixes for the file in the doc test and a list of lines
/// which contain the string `should_panic` it makes no guarantees that all these lines are a
/// doctest attribute showing panic behaviour (but some of them will be)
///
/// Currently all doctest files take the pattern of `{name}_{line}_{number}` where name is the
/// path to the file with directory separators and dots replaced with underscores. Therefore
/// each name could potentially map to many files as `src_some_folder_foo_rs_0_1` could go to
/// `src/some/folder_foo.rs` or `src/some/folder/foo.rs` here we're going to work on a heuristic
/// that any matching file is good because we can't do any better
fn get_panic_candidates(tests: &[DirEntry], config: &Config) -> HashMap<String, Vec<usize>> {
    let mut result = HashMap::new();
    let root = config.root();
    let target = config.target_dir();
    for test in tests {
        let test_name = test.path().components().nth_back(1);
        if let Some(Component::Normal(s)) = test_name {
            let s = s.to_string_lossy();
            let end = match s.rfind("rs") {
                Some(i) => i + 2,
                None => continue,
            };
            let walker = WalkDir::new(&root).into_iter();
            // TODO some walkdir stuff is being reused here and in source_analysis (and source
            // analysis does it a bit wrong) Fix before merging
            for dir_entry in walker
                .filter_entry(|e| {
                    !(e.path().starts_with(&target)
                        || e.path()
                            .iter()
                            .any(|x| x.to_string_lossy().starts_with(".")))
                })
                .filter_map(|e| e.ok())
            {
                let path = dir_entry.path();
                if path.is_file() {
                    if let Some(p) = path_relative_from(path, &root) {
                        if is_prefix_match(&s[..end], &p) {
                            let prefix = convert_to_prefix(&p).unwrap_or_default();
                            if !result.contains_key(&prefix) {
                                trace!("Assessing {} for `should_panic` doctests", path.display());
                                let lines = find_panics_in_file(path).unwrap_or_default();
                                result.insert(prefix, lines);
                            }
                            break;
                        }
                    }
                }
            }
        } else {
            warn!(
                "Invalid characters in name of doctest {}",
                test.path().display()
            );
        }
    }
    result
}

fn find_panics_in_file(file: &Path) -> io::Result<Vec<usize>> {
    let f = File::open(file)?;
    let reader = BufReader::new(f);
    let lines = reader
        .lines()
        .enumerate()
        .filter(|(_, l)| {
            l.as_ref()
                .map(|x| x.contains("should_panic"))
                .unwrap_or(false)
        })
        .map(|(i, _)| i)
        .collect();
    Ok(lines)
}

fn create_command(manifest_path: &str, config: &Config, ty: &RunType) -> Command {
    let mut test_cmd = Command::new("cargo");
    if *ty == RunType::Doctests {
        if let Some(toolchain) = env::var("RUSTUP_TOOLCHAIN")
            .ok()
            .filter(|t| t.starts_with("nightly"))
        {
            test_cmd.args(&[format!("+{}", toolchain).as_str(), "test"]);
        } else {
            test_cmd.args(&["+nightly", "test"]);
        }
    } else {
        if let Ok(toolchain) = env::var("RUSTUP_TOOLCHAIN") {
            test_cmd.arg(format!("+{}", toolchain));
        }
        test_cmd.args(&["test", "--no-run"]);
    }
    test_cmd.args(&["--message-format", "json", "--manifest-path", manifest_path]);
    match ty {
        RunType::Tests => test_cmd.arg("--tests"),
        RunType::Doctests => test_cmd.arg("--doc"),
        RunType::Benchmarks => test_cmd.arg("--benches"),
        RunType::Examples => test_cmd.arg("--examples"),
    };
    init_args(&mut test_cmd, config);
    setup_environment(&mut test_cmd, config);
    test_cmd
}

fn init_args(test_cmd: &mut Command, config: &Config) {
    if config.debug {
        test_cmd.arg("-vvv");
    }
    if config.locked {
        test_cmd.arg("--locked");
    }
    if config.frozen {
        test_cmd.arg("--frozen");
    }
    if let Some(features) = config.features.as_ref() {
        test_cmd.arg("--features");
        test_cmd.arg(features);
    }
    if config.all_features {
        test_cmd.arg("--all-features");
    }
    if config.no_default_features {
        test_cmd.arg("--no-default-features");
    }
    if config.all {
        test_cmd.arg("--workspace");
    }
    if config.release {
        test_cmd.arg("--release");
    }
    config.packages.iter().for_each(|package| {
        test_cmd.arg("--package");
        test_cmd.arg(package);
    });
    config.exclude.iter().for_each(|package| {
        test_cmd.arg("--exclude");
        test_cmd.arg(package);
    });
    if let Some(target) = config.target.as_ref() {
        test_cmd.args(&["--target", target]);
    }
    let args = vec![
        "--target-dir".to_string(),
        format!("{}", config.target_dir().display()),
    ];
    test_cmd.args(args);
    if config.offline {
        test_cmd.arg("--offline");
    }
    for feat in &config.unstable_features {
        test_cmd.arg(format!("-Z{}", feat));
    }
    if !config.varargs.is_empty() {
        let mut args = vec!["--".to_string()];
        args.extend_from_slice(&config.varargs);
        test_cmd.args(args);
    }
}

fn setup_environment(cmd: &mut Command, config: &Config) {
    cmd.env("TARPAULIN", "1");
    let rustflags = "RUSTFLAGS";
    let common_opts = " -C relocation-model=dynamic-no-pic -C link-dead-code -C debuginfo=2 ";
    let mut value = common_opts.to_string();
    if config.release {
        value = format!("{}-C debug-assertions=off ", value);
    }
    if let Ok(vtemp) = env::var(rustflags) {
        value.push_str(vtemp.as_ref());
    }
    cmd.env(rustflags, value);
    // doesn't matter if we don't use it
    let rustdoc = "RUSTDOCFLAGS";
    let mut value = format!(
        "{} --persist-doctests {} -Z unstable-options ",
        common_opts,
        config.doctest_dir().display()
    );
    if let Ok(vtemp) = env::var(rustdoc) {
        if !vtemp.contains("--persist-doctests") {
            value.push_str(vtemp.as_ref());
        }
    }
    trace!("Setting RUSTDOCFLAGS='{}'", value);
    cmd.env(rustdoc, value);
}
<commit_msg>Get the panic status for tests<commit_after>use crate::config::*;
use crate::errors::RunError;
use cargo_metadata::{diagnostic::DiagnosticLevel, CargoOpt, Message, MetadataCommand};
use log::{error, info, trace, warn};
use std::collections::HashMap;
use std::env;
use std::fs::File;
use std::io;
use std::io::{BufRead, BufReader};
use std::path::{Component, Path, PathBuf};
use std::process::{Command, Stdio};
use walkdir::{DirEntry, WalkDir};

#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TestBinary {
    path: PathBuf,
    ty: RunType,
    cargo_dir: Option<PathBuf>,
    pkg_name: Option<String>,
    pkg_version: Option<String>,
    pkg_authors: Option<Vec<String>>,
    should_panic: bool,
}

#[derive(Clone, Debug)]
struct DocTestBinaryMeta {
    prefix: String,
    line: usize,
}

impl TestBinary {
    pub fn new(path: PathBuf, ty: RunType) -> Self {
        Self {
            path,
            ty,
            pkg_name: None,
            pkg_version: None,
            pkg_authors: None,
            cargo_dir: None,
            should_panic: false,
        }
    }

    pub fn path(&self) -> &Path {
        &self.path
    }

    pub fn run_type(&self) -> RunType {
        self.ty
    }

    pub fn manifest_dir(&self) -> &Option<PathBuf> {
        &self.cargo_dir
    }

    pub fn pkg_name(&self) -> &Option<String> {
        &self.pkg_name
    }

    pub fn pkg_version(&self) -> &Option<String> {
        &self.pkg_version
    }

    pub fn pkg_authors(&self) -> &Option<Vec<String>> {
        &self.pkg_authors
    }

    /// Should be `false` for normal tests and for doctests either `true` or
    /// `false` depending on the test attribute
    pub fn should_panic(&self) -> bool {
        self.should_panic
    }
}

impl DocTestBinaryMeta {
    fn new<P: AsRef<Path>>(test: P) -> Option<Self> {
        if let Some(Component::Normal(folder)) = test.as_ref().components().nth_back(1) {
            let temp = folder.to_string_lossy();
            let file_end = temp.rfind("rs").map(|i| i + 2)?;
            let end = temp.rfind("_")?;
            if end > file_end + 1 {
                let line = temp[(file_end + 1)..end].parse::<usize>().ok()?;
                Some(Self {
                    prefix: temp[..file_end].to_string(),
                    line,
                })
            } else {
                None
            }
        } else {
            None
        }
    }
}

pub fn get_tests(config: &Config) -> Result<Vec<TestBinary>, RunError> {
    let mut result = vec![];
    let manifest = match config.manifest.as_path().to_str() {
        Some(s) => s,
        None => "Cargo.toml",
    };
    let metadata = MetadataCommand::new()
        .manifest_path(manifest)
        .features(CargoOpt::AllFeatures)
        .exec()
        .map_err(|e| RunError::Cargo(e.to_string()))?;

    for ty in &config.run_types {
        let mut cmd = create_command(manifest, config, ty);
        if ty != &RunType::Doctests {
            cmd.stdout(Stdio::piped());
        } else {
            cmd.stdout(Stdio::null());
        }
        if !config.verbose {
            cmd.stderr(Stdio::null());
        }
        trace!("Running command {:?}", cmd);
        let mut child = cmd.spawn().map_err(|e| RunError::Cargo(e.to_string()))?;

        if ty != &RunType::Doctests {
            let mut package_ids = vec![];
            let reader = std::io::BufReader::new(child.stdout.take().unwrap());
            for msg in Message::parse_stream(reader) {
                match msg {
                    Ok(Message::CompilerArtifact(art)) => {
                        if let Some(path) = art.executable {
                            if !art.profile.test && ty == &RunType::Tests {
                                continue;
                            }
                            result.push(TestBinary::new(path, *ty));
                            package_ids.push(art.package_id.clone());
                        }
                    }
                    Ok(Message::CompilerMessage(m)) => match m.message.level {
                        DiagnosticLevel::Error | DiagnosticLevel::Ice => {
                            let _ = child.wait();
                            return Err(RunError::TestCompile(m.message.message));
                        }
                        _ => {}
                    },
                    Err(e) => {
                        error!("Error parsing cargo messages {}", e);
                    }
                    _ => {}
                }
            }
            for (res, package) in result.iter_mut().zip(package_ids.iter()) {
                let package = &metadata[package];
                res.cargo_dir = package.manifest_path.parent().map(|x| x.to_path_buf());
                res.pkg_name = Some(package.name.clone());
                res.pkg_version = Some(package.version.to_string());
                res.pkg_authors = Some(package.authors.clone());
            }
            child.wait().map_err(|e| RunError::Cargo(e.to_string()))?;
        } else {
            // need to wait for compiling to finish before getting doctests
            // also need to wait with output to ensure the stdout buffer doesn't fill up
            let out = child
                .wait_with_output()
                .map_err(|e| RunError::Cargo(e.to_string()))?;
            if !out.status.success() {
                error!("Building doctests failed");
            }
            let walker = WalkDir::new(&config.doctest_dir()).into_iter();
            let dir_entries = walker
                .filter_map(|e| e.ok())
                .filter(|e| match e.metadata() {
                    Ok(ref m) if m.is_file() && m.len() != 0 => true,
                    _ => false,
                })
                .collect::<Vec<_>>();

            let should_panics = get_panic_candidates(&dir_entries, config);
            for dt in &dir_entries {
                trace!("Found doctest binary {}", dt.path().display());
                let mut tb = TestBinary::new(dt.path().to_path_buf(), *ty);
                // Now to do my magic!
                if let Some(meta) = DocTestBinaryMeta::new(dt.path()) {
                    if let Some(lines) = should_panics.get(&meta.prefix) {
                        tb.should_panic |= lines.contains(&meta.line);
                    }
                }
                result.push(tb);
            }
        }
    }
    Ok(result)
}

fn convert_to_prefix(p: &Path) -> Option<String> {
    p.to_str()
        .map(|s| s.replace(std::path::MAIN_SEPARATOR, "_").replace(".", "_"))
}

fn is_prefix_match(prefix: &str, entry: &Path) -> bool {
    convert_to_prefix(entry)
        .map(|s| prefix.starts_with(&s))
        .unwrap_or(false)
}

/// This returns a map of the string prefixes for the file in the doc test and a list of lines
/// which contain the string `should_panic` it makes no guarantees that all these lines are a
/// doctest attribute showing panic behaviour (but some of them will be)
///
/// Currently all doctest files take the pattern of `{name}_{line}_{number}` where name is the
/// path to the file with directory separators and dots replaced with underscores. Therefore
/// each name could potentially map to many files as `src_some_folder_foo_rs_0_1` could go to
/// `src/some/folder_foo.rs` or `src/some/folder/foo.rs` here we're going to work on a heuristic
/// that any matching file is good because we can't do any better
fn get_panic_candidates(tests: &[DirEntry], config: &Config) -> HashMap<String, Vec<usize>> {
    let mut result = HashMap::new();
    let root = config.root();
    let target = config.target_dir();
    for test in tests {
        if let Some(test_binary) = DocTestBinaryMeta::new(test.path()) {
            let walker = WalkDir::new(&root).into_iter();
            // TODO some walkdir stuff is being reused here and in source_analysis (and source
            // analysis does it a bit wrong) Fix before merging
            for dir_entry in walker
                .filter_entry(|e| {
                    !(e.path().starts_with(&target)
                        || e.path()
                            .iter()
                            .any(|x| x.to_string_lossy().starts_with(".")))
                })
                .filter_map(|e| e.ok())
            {
                let path = dir_entry.path();
                if path.is_file() {
                    if let Some(p) = path_relative_from(path, &root) {
                        if is_prefix_match(&test_binary.prefix, &p) {
                            let prefix = convert_to_prefix(&p).unwrap_or_default();
                            if !result.contains_key(&prefix) {
                                trace!("Assessing {} for `should_panic` doctests", path.display());
                                let lines = find_panics_in_file(path).unwrap_or_default();
                                result.insert(prefix, lines);
                            }
                            break;
                        }
                    }
                }
            }
        } else {
            warn!(
                "Invalid characters in name of doctest {}",
                test.path().display()
            );
        }
    }
    result
}

fn find_panics_in_file(file: &Path) -> io::Result<Vec<usize>> {
    let f = File::open(file)?;
    let reader = BufReader::new(f);
    let lines = reader
        .lines()
        .enumerate()
        .filter(|(_, l)| {
            l.as_ref()
                .map(|x| x.contains("should_panic"))
                .unwrap_or(false)
        })
        .map(|(i, _)| i + 1) // move from line index to line number
        .collect();
    Ok(lines)
}

fn create_command(manifest_path: &str, config: &Config, ty: &RunType) -> Command {
    let mut test_cmd = Command::new("cargo");
    if *ty == RunType::Doctests {
        if let Some(toolchain) = env::var("RUSTUP_TOOLCHAIN")
            .ok()
            .filter(|t| t.starts_with("nightly"))
        {
            test_cmd.args(&[format!("+{}", toolchain).as_str(), "test"]);
        } else {
            test_cmd.args(&["+nightly", "test"]);
        }
    } else {
        if let Ok(toolchain) = env::var("RUSTUP_TOOLCHAIN") {
            test_cmd.arg(format!("+{}", toolchain));
        }
        test_cmd.args(&["test", "--no-run"]);
    }
    test_cmd.args(&["--message-format", "json", "--manifest-path", manifest_path]);
    match ty {
        RunType::Tests => test_cmd.arg("--tests"),
        RunType::Doctests => test_cmd.arg("--doc"),
        RunType::Benchmarks => test_cmd.arg("--benches"),
        RunType::Examples => test_cmd.arg("--examples"),
    };
    init_args(&mut test_cmd, config);
    setup_environment(&mut test_cmd, config);
    test_cmd
}

fn init_args(test_cmd: &mut Command, config: &Config) {
    if config.debug {
        test_cmd.arg("-vvv");
    }
    if config.locked {
        test_cmd.arg("--locked");
    }
    if config.frozen {
        test_cmd.arg("--frozen");
    }
    if let Some(features) = config.features.as_ref() {
        test_cmd.arg("--features");
        test_cmd.arg(features);
    }
    if config.all_features {
        test_cmd.arg("--all-features");
    }
    if config.no_default_features {
        test_cmd.arg("--no-default-features");
    }
    if config.all {
        test_cmd.arg("--workspace");
    }
    if config.release {
        test_cmd.arg("--release");
    }
    config.packages.iter().for_each(|package| {
        test_cmd.arg("--package");
        test_cmd.arg(package);
    });
    config.exclude.iter().for_each(|package| {
        test_cmd.arg("--exclude");
        test_cmd.arg(package);
    });
    if let Some(target) = config.target.as_ref() {
        test_cmd.args(&["--target", target]);
    }
    let args = vec![
        "--target-dir".to_string(),
        format!("{}", config.target_dir().display()),
    ];
    test_cmd.args(args);
    if config.offline {
        test_cmd.arg("--offline");
    }
    for feat in &config.unstable_features {
        test_cmd.arg(format!("-Z{}", feat));
    }
    if !config.varargs.is_empty() {
        let mut args = vec!["--".to_string()];
        args.extend_from_slice(&config.varargs);
        test_cmd.args(args);
    }
}

fn setup_environment(cmd: &mut Command, config: &Config) {
    cmd.env("TARPAULIN", "1");
    let rustflags = "RUSTFLAGS";
    let common_opts = " -C relocation-model=dynamic-no-pic -C link-dead-code -C debuginfo=2 ";
    let mut value = common_opts.to_string();
    if config.release {
        value = format!("{}-C debug-assertions=off ", value);
    }
    if let Ok(vtemp) = env::var(rustflags) {
        value.push_str(vtemp.as_ref());
    }
    cmd.env(rustflags, value);
    // doesn't matter if we don't use it
    let rustdoc = "RUSTDOCFLAGS";
    let mut value = format!(
        "{} --persist-doctests {} -Z unstable-options ",
        common_opts,
        config.doctest_dir().display()
    );
    if let Ok(vtemp) = env::var(rustdoc) {
        if !vtemp.contains("--persist-doctests") {
            value.push_str(vtemp.as_ref());
        }
    }
    trace!("Setting RUSTDOCFLAGS='{}'", value);
    cmd.env(rustdoc, value);
}
<|endoftext|>


<commit_before>use crate::{pagecache::*, *};

/// A pending log reservation which can be aborted or completed.
/// NB the holder should quickly call `complete` or `abort` as
/// taking too long to decide will cause the underlying IO
/// buffer to become blocked.
pub struct Reservation<'a> {
    pub(super) log: &'a Log,
    pub(super) iobuf: Arc<IoBuf>,
    pub(super) buf: &'a mut [u8],
    pub(super) flushed: bool,
    pub(super) pointer: DiskPtr,
    pub(super) lsn: Lsn,
    pub(super) is_blob_rewrite: bool,
}

impl<'a> Drop for Reservation<'a> {
    fn drop(&mut self) {
        // We auto-abort if the user never uses a reservation.
        if !self.flushed {
            let _written = self.flush(false).unwrap();
        }
    }
}

impl<'a> Reservation<'a> {
    /// Cancel the reservation, placing a failed flush on disk, returning
    /// the (cancelled) log sequence number and file offset.
    pub fn abort(mut self) -> Result<(Lsn, DiskPtr)> {
        if self.pointer.is_blob() && !self.is_blob_rewrite {
            // we don't want to remove this blob if something
            // else may still be using it.

            trace!(
                "removing blob for aborted reservation at lsn {}",
                self.pointer
            );

            remove_blob(self.pointer.blob().1, &self.log.config)?;
        }

        self.flush(false)
    }

    /// Complete the reservation, placing the buffer on disk. returns
    /// the log sequence number of the write, and the file offset.
    pub fn complete(mut self) -> Result<(Lsn, DiskPtr)> {
        self.flush(true)
    }

    /// Get the log file offset for reading this buffer in the future.
    pub fn lid(&self) -> LogOffset {
        self.pointer.lid()
    }

    /// Get the log sequence number for this update.
    pub const fn lsn(&self) -> Lsn {
        self.lsn
    }

    /// Get the underlying storage location for the written value.
    /// Note that an blob write still has a pointer in the
    /// log at the provided lid location.
    pub const fn pointer(&self) -> DiskPtr {
        self.pointer
    }

    /// Returns the length of the on-log reservation.
    pub fn reservation_len(&self) -> usize {
        self.buf.len()
    }

    /// Refills the reservation buffer with new data.
    /// Must supply a buffer of an identical length
    /// as the one initially provided. Don't use this
    /// on messages subject to compression etc...
    ///
    /// # Panics
    ///
    /// Will panic if the reservation is not the correct
    /// size to hold a serialized Lsn.
    #[doc(hidden)]
    pub fn mark_writebatch(&mut self, lsn: Lsn) {
        trace!(
            "writing batch required stable lsn {} into \
             BatchManifest at lid {} lsn {}",
            lsn,
            self.pointer.lid(),
            self.lsn
        );

        self.buf[0] = MessageKind::BatchManifest.into();

        let buf = u64_to_arr(u64::try_from(lsn).unwrap());

        let dst = &mut self.buf[MSG_HEADER_LEN..];

        dst.copy_from_slice(&buf);
    }

    fn flush(&mut self, valid: bool) -> Result<(Lsn, DiskPtr)> {
        if self.flushed {
            panic!("flushing already-flushed reservation!");
        }

        self.flushed = true;

        if !valid {
            // don't actually zero the message, still check its hash
            // on recovery to find corruption.
            self.buf[0] = MessageKind::Cancelled.into();
        }

        // the order of hashing must be the
        // same here as during calls to
        // LogReader::read_message
        let mut hasher = crc32fast::Hasher::new();
        hasher.update(&self.buf[MSG_HEADER_LEN..]);
        hasher.update(&self.buf[..MSG_HEADER_LEN]);
        let crc32 = hasher.finalize();
        let crc32_arr = u32_to_arr(crc32 ^ 0xFFFF_FFFF);

        #[allow(unsafe_code)]
        unsafe {
            std::ptr::copy_nonoverlapping(
                crc32_arr.as_ptr(),
                self.buf
                    .as_mut_ptr()
                    .add(MSG_HEADER_LEN - std::mem::size_of::<u32>()),
                std::mem::size_of::<u32>(),
            );
        }
        self.log.exit_reservation(&self.iobuf)?;

        Ok((self.lsn(), self.pointer()))
    }
}
<commit_msg>Make another function const<commit_after>use crate::{pagecache::*, *};

/// A pending log reservation which can be aborted or completed.
/// NB the holder should quickly call `complete` or `abort` as
/// taking too long to decide will cause the underlying IO
/// buffer to become blocked.
pub struct Reservation<'a> {
    pub(super) log: &'a Log,
    pub(super) iobuf: Arc<IoBuf>,
    pub(super) buf: &'a mut [u8],
    pub(super) flushed: bool,
    pub(super) pointer: DiskPtr,
    pub(super) lsn: Lsn,
    pub(super) is_blob_rewrite: bool,
}

impl<'a> Drop for Reservation<'a> {
    fn drop(&mut self) {
        // We auto-abort if the user never uses a reservation.
        if !self.flushed {
            let _written = self.flush(false).unwrap();
        }
    }
}

impl<'a> Reservation<'a> {
    /// Cancel the reservation, placing a failed flush on disk, returning
    /// the (cancelled) log sequence number and file offset.
    pub fn abort(mut self) -> Result<(Lsn, DiskPtr)> {
        if self.pointer.is_blob() && !self.is_blob_rewrite {
            // we don't want to remove this blob if something
            // else may still be using it.

            trace!(
                "removing blob for aborted reservation at lsn {}",
                self.pointer
            );

            remove_blob(self.pointer.blob().1, &self.log.config)?;
        }

        self.flush(false)
    }

    /// Complete the reservation, placing the buffer on disk. returns
    /// the log sequence number of the write, and the file offset.
    pub fn complete(mut self) -> Result<(Lsn, DiskPtr)> {
        self.flush(true)
    }

    /// Get the log file offset for reading this buffer in the future.
    pub fn lid(&self) -> LogOffset {
        self.pointer.lid()
    }

    /// Get the log sequence number for this update.
    pub const fn lsn(&self) -> Lsn {
        self.lsn
    }

    /// Get the underlying storage location for the written value.
    /// Note that an blob write still has a pointer in the
    /// log at the provided lid location.
    pub const fn pointer(&self) -> DiskPtr {
        self.pointer
    }

    /// Returns the length of the on-log reservation.
    pub const fn reservation_len(&self) -> usize {
        self.buf.len()
    }

    /// Refills the reservation buffer with new data.
    /// Must supply a buffer of an identical length
    /// as the one initially provided. Don't use this
    /// on messages subject to compression etc...
    ///
    /// # Panics
    ///
    /// Will panic if the reservation is not the correct
    /// size to hold a serialized Lsn.
    #[doc(hidden)]
    pub fn mark_writebatch(&mut self, lsn: Lsn) {
        trace!(
            "writing batch required stable lsn {} into \
             BatchManifest at lid {} lsn {}",
            lsn,
            self.pointer.lid(),
            self.lsn
        );

        self.buf[0] = MessageKind::BatchManifest.into();

        let buf = u64_to_arr(u64::try_from(lsn).unwrap());

        let dst = &mut self.buf[MSG_HEADER_LEN..];

        dst.copy_from_slice(&buf);
    }

    fn flush(&mut self, valid: bool) -> Result<(Lsn, DiskPtr)> {
        if self.flushed {
            panic!("flushing already-flushed reservation!");
        }

        self.flushed = true;

        if !valid {
            // don't actually zero the message, still check its hash
            // on recovery to find corruption.
            self.buf[0] = MessageKind::Cancelled.into();
        }

        // the order of hashing must be the
        // same here as during calls to
        // LogReader::read_message
        let mut hasher = crc32fast::Hasher::new();
        hasher.update(&self.buf[MSG_HEADER_LEN..]);
        hasher.update(&self.buf[..MSG_HEADER_LEN]);
        let crc32 = hasher.finalize();
        let crc32_arr = u32_to_arr(crc32 ^ 0xFFFF_FFFF);

        #[allow(unsafe_code)]
        unsafe {
            std::ptr::copy_nonoverlapping(
                crc32_arr.as_ptr(),
                self.buf
                    .as_mut_ptr()
                    .add(MSG_HEADER_LEN - std::mem::size_of::<u32>()),
                std::mem::size_of::<u32>(),
            );
        }
        self.log.exit_reservation(&self.iobuf)?;

        Ok((self.lsn(), self.pointer()))
    }
}
<|endoftext|>


<commit_before>// TODO:
// - Rewrite this in the same style as shell_expand::words.
// - Validate syntax in methods

use std::fmt::{self, Display, Formatter};

#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
enum LogicalOp {
    And,
    Or,
    None,
}

#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
enum Quotes {
    Single,
    Double,
    None,
}

#[derive(Debug, PartialEq)]
pub enum StatementError {
    IllegalCommandName(String),
    InvalidCharacter(char, usize),
    UnterminatedSubshell,
    UnterminatedBracedVar,
    UnterminatedBrace,
    UnterminatedMethod,
    UnterminatedArithmetic,
    ExpectedCommandButFound(&'static str),
}

impl Display for StatementError {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        match *self {
            StatementError::IllegalCommandName(ref command) => {
                writeln!(f, "illegal command name: {}", command)
            }
            StatementError::InvalidCharacter(character, position) => writeln!(
                f,
                "syntax error: '{}' at position {} is out of place",
                character, position
            ),
            StatementError::UnterminatedSubshell => {
                writeln!(f, "syntax error: unterminated subshell")
            }
            StatementError::UnterminatedBrace => writeln!(f, "syntax error: unterminated brace"),
            StatementError::UnterminatedBracedVar => {
                writeln!(f, "syntax error: unterminated braced var")
            }
            StatementError::UnterminatedMethod => writeln!(f, "syntax error: unterminated method"),
            StatementError::UnterminatedArithmetic => {
                writeln!(f, "syntax error: unterminated arithmetic subexpression")
            }
            StatementError::ExpectedCommandButFound(element) => {
                writeln!(f, "expected command, but found {}", element)
            }
        }
    }
}

#[derive(Debug, PartialEq)]
pub enum StatementVariant<'a> {
    And(&'a str),
    Or(&'a str),
    Default(&'a str),
}

#[derive(Debug)]
pub struct StatementSplitter<'a> {
    data: &'a str,
    read: usize,
    start: usize,
    paren_level: u8,
    brace_level: u8,
    math_paren_level: i8,
    logical: LogicalOp,
    /// Set while parsing through an inline arithmetic expression, e.g. $((foo * bar / baz))
    math_expr: bool,
    skip: bool,
    vbrace: bool,
    method: bool,
    variable: bool,
    quotes: Quotes,
}

impl<'a> StatementSplitter<'a> {
    pub fn new(data: &'a str) -> Self {
        StatementSplitter {
            data,
            read: 0,
            start: 0,
            paren_level: 0,
            brace_level: 0,
            math_paren_level: 0,
            logical: LogicalOp::None,
            math_expr: false,
            skip: false,
            vbrace: false,
            method: false,
            variable: false,
            quotes: Quotes::None,
        }
    }

    fn get_statement(&self, start: usize, end: usize) -> StatementVariant<'a> {
        if self.logical == LogicalOp::And {
            StatementVariant::And(&self.data[start + 1..end].trim())
        } else if self.logical == LogicalOp::Or {
            StatementVariant::Or(&self.data[start + 1..end].trim())
        } else {
            StatementVariant::Default(&self.data[start..end].trim())
        }
    }

    fn get_statement_from(&self, input: &'a str) -> StatementVariant<'a> {
        if self.logical == LogicalOp::And {
            StatementVariant::And(input)
        } else if self.logical == LogicalOp::Or {
            StatementVariant::Or(input)
        } else {
            StatementVariant::Default(input)
        }
    }
}

impl<'a> Iterator for StatementSplitter<'a> {
    type Item = Result<StatementVariant<'a>, StatementError>;

    fn next(&mut self) -> Option<Self::Item> {
        let start = self.read;
        let mut first_arg_found = false;
        let mut error = None;
        let mut bytes = self.data.bytes().enumerate().skip(self.read).peekable();
        let mut last = None;

        bytes.peek()?;

        while let Some((i, character)) = bytes.next() {
            match character {
                _ if self.skip => {
                    self.skip = false;
                    last = None;
                    continue;
                }
                b'\\' => self.skip = true,
                b'\'' => {
                    if self.quotes == Quotes::Single {
                        self.quotes = Quotes::None;
                    } else if self.quotes == Quotes::None {
                        self.variable = false;
                        self.quotes = Quotes::Single;
                    }
                }
                _ if self.quotes == Quotes::Single => {}
                // [^A-Za-z0-9_:,}]
                0...43 | 45...47 | 59...64 | 91...94 | 96 | 123...124 | 126...127
                    if self.vbrace =>
                {
                    // If we are just ending the braced section continue as normal
                    if error.is_none() {
                        error = Some(StatementError::InvalidCharacter(character as char, i + 1))
                    }
                }
                // Toggle quotes and stop matching variables.
                b'"' if self.quotes == Quotes::Double => self.quotes = Quotes::None,
                b'"' => {
                    self.quotes = Quotes::Double;
                    self.variable = false;
                }
                // Array expansion
                b'@' | b'$' => self.variable = true,
                b'{' if [Some(b'$'), Some(b'@')].contains(&last) => self.vbrace = true,
                b'{' if self.quotes == Quotes::None => self.brace_level += 1,
                b'}' if self.vbrace => self.vbrace = false,
                b'}' if self.quotes == Quotes::None => {
                    if self.brace_level == 0 {
                        if error.is_none() {
                            error = Some(StatementError::InvalidCharacter(character as char, i + 1))
                        }
                    } else {
                        self.brace_level -= 1;
                    }
                }
                b'(' if self.math_expr => self.math_paren_level += 1,
                b'(' if self.method || last == Some(b'$') => {
                    self.variable = false;
                    if let Some(&(_, b'(')) = bytes.peek() {
                        self.math_expr = true;
                        // The next character will always be a left paren in this branch;
                        self.math_paren_level = -1;
                    } else {
                        self.paren_level += 1;
                    }
                }
                b'(' if last == Some(b'@') => self.paren_level += 1,
                b'(' if self.variable => {
                    self.method = true;
                    self.variable = false;
                }
                b'(' if error.is_none() && self.quotes == Quotes::None => {
                    error = Some(StatementError::InvalidCharacter(character as char, i + 1))
                }
                b')' if self.math_expr => {
                    if self.math_paren_level == 0 {
                        match bytes.peek() {
                            Some(&(_, b')')) => {
                                self.math_expr = false;
                                self.skip = true;
                            }
                            Some(&(_, next)) if error.is_none() => {
                                error = Some(StatementError::InvalidCharacter(next as char, i + 1));
                            }
                            None if error.is_none() => {
                                error = Some(StatementError::UnterminatedArithmetic)
                            }
                            _ => {}
                        }
                    } else {
                        self.math_paren_level -= 1;
                    }
                }
                b')' if self.method && self.paren_level == 0 => {
                    self.method = false;
                }
                b')' if self.paren_level == 0 => {
                    if error.is_none() && self.quotes == Quotes::None {
                        error = Some(StatementError::InvalidCharacter(character as char, i + 1))
                    }
                }
                b')' => self.paren_level -= 1,
                b';' if self.quotes == Quotes::None && self.paren_level == 0 => {
                    let statement = self.get_statement(start, i);
                    self.logical = LogicalOp::None;

                    self.read = i + 1;
                    return match error {
                        Some(error) => Some(Err(error)),
                        None => Some(Ok(statement)),
                    };
                }
                b'&' | b'|'
                    if self.quotes == Quotes::None
                        && self.paren_level == 0
                        && last == Some(character) =>
                {
                    // Detecting if there is a 2nd `&` character
                    let statement = self.get_statement(start, i - 1);
                    self.logical = if character == b'&' { LogicalOp::And } else { LogicalOp::Or };
                    self.read = i + 1;
                    return match error {
                        Some(error) => Some(Err(error)),
                        None => Some(Ok(statement)),
                    };
                }
                b' ' if !first_arg_found => match self.data[start..i].trim() {
                    "else" => {
                        if self.data.len() < i + 2 || &self.data[i + 1..=i + 2] != "if" {
                            self.logical = LogicalOp::None;
                            self.read = i + 1;
                            return Some(Ok(StatementVariant::Default("else")));
                        }
                    }
                    "" => {}
                    _ => first_arg_found = true,
                },
                // [^A-Za-z0-9_]
                0...47 | 58...64 | 91...94 | 96 | 123...127 => self.variable = false,
                _ => {}
            }
            last = Some(character);
        }

        self.read = self.data.len();
        error.map(Err).or_else(|| {
            if self.paren_level != 0 {
                Some(Err(StatementError::UnterminatedSubshell))
            } else if self.method {
                Some(Err(StatementError::UnterminatedMethod))
            } else if self.vbrace {
                Some(Err(StatementError::UnterminatedBracedVar))
            } else if self.brace_level != 0 {
                Some(Err(StatementError::UnterminatedBrace))
            } else if self.math_expr {
                Some(Err(StatementError::UnterminatedArithmetic))
            } else {
                let output = self.data[start..].trim();
                output.as_bytes().get(0).map(|c| match c {
                    b'>' | b'<' | b'^' => {
                        Err(StatementError::ExpectedCommandButFound("redirection"))
                    }
                    b'|' => Err(StatementError::ExpectedCommandButFound("pipe")),
                    b'&' => Err(StatementError::ExpectedCommandButFound("&")),
                    b'*' | b'%' | b'?' | b'{' | b'}' => {
                        Err(StatementError::IllegalCommandName(String::from(output)))
                    }
                    _ => {
                        let stmt = self.get_statement_from(output);
                        self.logical = LogicalOp::None;
                        Ok(stmt)
                    }
                })
            }
        })
    }
}

#[test]
fn syntax_errors() {
    let command = "echo (echo one); echo $( (echo one); echo ) two; echo $(echo one";
    let results = StatementSplitter::new(command).collect::<Vec<_>>();
    assert_eq!(results[0], Err(StatementError::InvalidCharacter('(', 6)));
    assert_eq!(results[1], Err(StatementError::InvalidCharacter('(', 26)));
    assert_eq!(results[2], Err(StatementError::InvalidCharacter(')', 43)));
    assert_eq!(results[3], Err(StatementError::UnterminatedSubshell));
    assert_eq!(results.len(), 4);

    let command = ">echo";
    let results = StatementSplitter::new(command).collect::<Vec<_>>();
    assert_eq!(results[0], Err(StatementError::ExpectedCommandButFound("redirection")));
    assert_eq!(results.len(), 1);

    let command = "echo $((foo bar baz)";
    let results = StatementSplitter::new(command).collect::<Vec<_>>();
    assert_eq!(results[0], Err(StatementError::UnterminatedArithmetic));
    assert_eq!(results.len(), 1);
}

#[test]
fn methods() {
    let command = "echo $join(array, ', '); echo @join(var, ', ')";
    let statements = StatementSplitter::new(command).collect::<Vec<_>>();
    assert_eq!(statements[0], Ok(StatementVariant::Default("echo $join(array, ', ')")));
    assert_eq!(statements[1], Ok(StatementVariant::Default("echo @join(var, ', ')")));
    assert_eq!(statements.len(), 2);
}

#[test]
fn processes() {
    let command = "echo $(seq 1 10); echo $(seq 1 10)";
    for statement in StatementSplitter::new(command) {
        assert_eq!(statement, Ok(StatementVariant::Default("echo $(seq 1 10)")));
    }
}

#[test]
fn array_processes() {
    let command = "echo @(echo one; sleep 1); echo @(echo one; sleep 1)";
    for statement in StatementSplitter::new(command) {
        assert_eq!(statement, Ok(StatementVariant::Default("echo @(echo one; sleep 1)")));
    }
}

#[test]
fn process_with_statements() {
    let command = "echo $(seq 1 10; seq 1 10)";
    for statement in StatementSplitter::new(command) {
        assert_eq!(statement, Ok(StatementVariant::Default(command)));
    }
}

#[test]
fn quotes() {
    let command = "echo \"This ;'is a test\"; echo 'This ;\" is also a test'";
    let results = StatementSplitter::new(command).collect::<Vec<_>>();
    assert_eq!(results.len(), 2);
    assert_eq!(results[0], Ok(StatementVariant::Default("echo \"This ;'is a test\"")));
    assert_eq!(results[1], Ok(StatementVariant::Default("echo 'This ;\" is also a test'")));
}

#[test]
fn nested_process() {
    let command = "echo $(echo one $(echo two) three)";
    let results = StatementSplitter::new(command).collect::<Vec<_>>();
    assert_eq!(results.len(), 1);
    assert_eq!(results[0], Ok(StatementVariant::Default(command)));

    let command = "echo $(echo $(echo one; echo two); echo two)";
    let results = StatementSplitter::new(command).collect::<Vec<_>>();
    assert_eq!(results.len(), 1);
    assert_eq!(results[0], Ok(StatementVariant::Default(command)));
}

#[test]
fn nested_array_process() {
    let command = "echo @(echo one @(echo two) three)";
    let results = StatementSplitter::new(command).collect::<Vec<_>>();
    assert_eq!(results.len(), 1);
    assert_eq!(results[0], Ok(StatementVariant::Default(command)));

    let command = "echo @(echo @(echo one; echo two); echo two)";
    let results = StatementSplitter::new(command).collect::<Vec<_>>();
    assert_eq!(results.len(), 1);
    assert_eq!(results[0], Ok(StatementVariant::Default(command)));
}

#[test]
fn braced_variables() {
    let command = "echo ${foo}bar ${bar}baz ${baz}quux @{zardoz}wibble";
    let results = StatementSplitter::new(command).collect::<Vec<_>>();
    assert_eq!(results.len(), 1);
    assert_eq!(results[0], Ok(StatementVariant::Default(command)));
}

#[test]
fn variants() {
    let command = r#"echo "Hello!"; echo "How are you doing?" && echo "I'm just an ordinary test." || echo "Helping by making sure your code works right."; echo "Have a good day!""#;
    let results = StatementSplitter::new(command).collect::<Vec<_>>();
    assert_eq!(results.len(), 5);
    assert_eq!(results[0], Ok(StatementVariant::Default(r#"echo "Hello!""#)));
    assert_eq!(results[1], Ok(StatementVariant::Default(r#"echo "How are you doing?""#)));
    assert_eq!(results[2], Ok(StatementVariant::And(r#"echo "I'm just an ordinary test.""#)));
    assert_eq!(
        results[3],
        Ok(StatementVariant::Or(r#"echo "Helping by making sure your code works right.""#))
    );
    assert_eq!(results[4], Ok(StatementVariant::Default(r#"echo "Have a good day!""#)));
}
<commit_msg>Remove redundant/correlated variables<commit_after>// TODO:
// - Rewrite this in the same style as shell_expand::words.
// - Validate syntax in methods

use std::fmt::{self, Display, Formatter};

#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
enum LogicalOp {
    And,
    Or,
    None,
}

#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
enum Quotes {
    Single,
    Double,
    None,
}

#[derive(Debug, PartialEq)]
pub enum StatementError {
    IllegalCommandName(String),
    InvalidCharacter(char, usize),
    UnterminatedSubshell,
    UnterminatedBracedVar,
    UnterminatedBrace,
    UnterminatedMethod,
    UnterminatedArithmetic,
    ExpectedCommandButFound(&'static str),
}

impl Display for StatementError {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        match *self {
            StatementError::IllegalCommandName(ref command) => {
                writeln!(f, "illegal command name: {}", command)
            }
            StatementError::InvalidCharacter(character, position) => writeln!(
                f,
                "syntax error: '{}' at position {} is out of place",
                character, position
            ),
            StatementError::UnterminatedSubshell => {
                writeln!(f, "syntax error: unterminated subshell")
            }
            StatementError::UnterminatedBrace => writeln!(f, "syntax error: unterminated brace"),
            StatementError::UnterminatedBracedVar => {
                writeln!(f, "syntax error: unterminated braced var")
            }
            StatementError::UnterminatedMethod => writeln!(f, "syntax error: unterminated method"),
            StatementError::UnterminatedArithmetic => {
                writeln!(f, "syntax error: unterminated arithmetic subexpression")
            }
            StatementError::ExpectedCommandButFound(element) => {
                writeln!(f, "expected command, but found {}", element)
            }
        }
    }
}

#[derive(Debug, PartialEq)]
pub enum StatementVariant<'a> {
    And(&'a str),
    Or(&'a str),
    Default(&'a str),
}

#[derive(Debug)]
pub struct StatementSplitter<'a> {
    data:             &'a str,
    read:             usize,
    start:            usize,
    paren_level:      u8,
    brace_level:      u8,
    math_paren_level: i8,
    logical:          LogicalOp,
    skip:             bool,
    vbrace:           bool,
    variable:         bool,
    quotes:           Quotes,
}

impl<'a> StatementSplitter<'a> {
    pub fn new(data: &'a str) -> Self {
        StatementSplitter {
            data,
            read: 0,
            start: 0,
            paren_level: 0,
            brace_level: 0,
            math_paren_level: 0,
            logical: LogicalOp::None,
            skip: false,
            vbrace: false,
            variable: false,
            quotes: Quotes::None,
        }
    }

    fn get_statement(&self, start: usize, end: usize) -> StatementVariant<'a> {
        if self.logical == LogicalOp::And {
            StatementVariant::And(&self.data[start + 1..end].trim())
        } else if self.logical == LogicalOp::Or {
            StatementVariant::Or(&self.data[start + 1..end].trim())
        } else {
            StatementVariant::Default(&self.data[start..end].trim())
        }
    }

    fn get_statement_from(&self, input: &'a str) -> StatementVariant<'a> {
        if self.logical == LogicalOp::And {
            StatementVariant::And(input)
        } else if self.logical == LogicalOp::Or {
            StatementVariant::Or(input)
        } else {
            StatementVariant::Default(input)
        }
    }
}

impl<'a> Iterator for StatementSplitter<'a> {
    type Item = Result<StatementVariant<'a>, StatementError>;

    fn next(&mut self) -> Option<Self::Item> {
        let start = self.read;
        let mut error = None;
        let mut bytes = self.data.bytes().enumerate().skip(self.read).peekable();
        let mut last = None;

        bytes.peek()?;

        while let Some((i, character)) = bytes.next() {
            match character {
                _ if self.skip => {
                    self.skip = false;
                    last = None;
                    continue;
                }
                b'\\' => self.skip = true,
                b'\'' => {
                    if self.quotes == Quotes::Single {
                        self.quotes = Quotes::None;
                    } else if self.quotes == Quotes::None {
                        self.variable = false;
                        self.quotes = Quotes::Single;
                    }
                }
                _ if self.quotes == Quotes::Single => {}
                // [^A-Za-z0-9_:,}]
                0...43 | 45...47 | 59...64 | 91...94 | 96 | 123...124 | 126...127
                    if self.vbrace =>
                {
                    // If we are just ending the braced section continue as normal
                    if error.is_none() {
                        error = Some(StatementError::InvalidCharacter(character as char, i + 1))
                    }
                }
                // Toggle quotes and stop matching variables.
                b'"' if self.quotes == Quotes::Double => self.quotes = Quotes::None,
                b'"' => {
                    self.quotes = Quotes::Double;
                    self.variable = false;
                }
                // Array expansion
                b'@' | b'$' => self.variable = true,
                b'{' if [Some(b'$'), Some(b'@')].contains(&last) => self.vbrace = true,
                b'{' if self.quotes == Quotes::None => self.brace_level += 1,
                b'}' if self.vbrace => self.vbrace = false,
                b'}' if self.quotes == Quotes::None => {
                    if self.brace_level == 0 {
                        if error.is_none() {
                            error = Some(StatementError::InvalidCharacter(character as char, i + 1))
                        }
                    } else {
                        self.brace_level -= 1;
                    }
                }
                b'(' if self.math_paren_level > 0 => self.math_paren_level += 1,
                b'(' if last == Some(b'$') => {
                    self.variable = false;
                    if let Some(&(_, b'(')) = bytes.peek() {
                        bytes.next();
                        self.read += 1;
                        // The next character will always be a left paren in this branch;
                        self.math_paren_level = 1;
                    } else {
                        self.paren_level += 1;
                    }
                }
                b'(' if self.variable => self.paren_level += 1,
                b'(' if error.is_none() && self.quotes == Quotes::None => {
                    error = Some(StatementError::InvalidCharacter(character as char, i + 1))
                }
                b')' if self.math_paren_level != 0 => {
                    if self.math_paren_level == 1 {
                        match bytes.peek() {
                            Some(&(_, b')')) => {
                                self.math_paren_level = 0;
                                self.skip = true;
                            }
                            Some(&(_, next)) if error.is_none() => {
                                error = Some(StatementError::InvalidCharacter(next as char, i + 1));
                            }
                            None if error.is_none() => {
                                error = Some(StatementError::UnterminatedArithmetic)
                            }
                            _ => {}
                        }
                    } else {
                        self.math_paren_level -= 1;
                    }
                }
                b')' if self.variable && self.paren_level == 0 => {
                    self.variable = false;
                }
                b')' if self.paren_level == 0 => {
                    if error.is_none() && self.quotes == Quotes::None {
                        error = Some(StatementError::InvalidCharacter(character as char, i + 1))
                    }
                }
                b')' => self.paren_level -= 1,
                b';' if self.quotes == Quotes::None && self.paren_level == 0 => {
                    let statement = self.get_statement(start, i);
                    self.logical = LogicalOp::None;

                    self.read = i + 1;
                    return match error {
                        Some(error) => Some(Err(error)),
                        None => Some(Ok(statement)),
                    };
                }
                b'&' | b'|'
                    if self.quotes == Quotes::None
                        && self.paren_level == 0
                        && last == Some(character) =>
                {
                    // Detecting if there is a 2nd `&` character
                    let statement = self.get_statement(start, i - 1);
                    self.logical = if character == b'&' { LogicalOp::And } else { LogicalOp::Or };
                    self.read = i + 1;
                    return match error {
                        Some(error) => Some(Err(error)),
                        None => Some(Ok(statement)),
                    };
                }
                // [^A-Za-z0-9_]
                0...47 | 58...64 | 91...94 | 96 | 123...127 => self.variable = false,
                _ => {}
            }
            last = Some(character);
        }

        self.read = self.data.len();
        error.map(Err).or_else(|| {
            if self.paren_level != 0 && self.variable {
                Some(Err(StatementError::UnterminatedMethod))
            } else if self.paren_level != 0 {
                Some(Err(StatementError::UnterminatedSubshell))
            } else if self.vbrace {
                Some(Err(StatementError::UnterminatedBracedVar))
            } else if self.brace_level != 0 {
                Some(Err(StatementError::UnterminatedBrace))
            } else if self.math_paren_level != 0 {
                Some(Err(StatementError::UnterminatedArithmetic))
            } else {
                let output = self.data[start..].trim();
                output.as_bytes().get(0).map(|c| match c {
                    b'>' | b'<' | b'^' => {
                        Err(StatementError::ExpectedCommandButFound("redirection"))
                    }
                    b'|' => Err(StatementError::ExpectedCommandButFound("pipe")),
                    b'&' => Err(StatementError::ExpectedCommandButFound("&")),
                    b'*' | b'%' | b'?' | b'{' | b'}' => {
                        Err(StatementError::IllegalCommandName(String::from(output)))
                    }
                    _ => {
                        let stmt = self.get_statement_from(output);
                        self.logical = LogicalOp::None;
                        Ok(stmt)
                    }
                })
            }
        })
    }
}

#[test]
fn syntax_errors() {
    let command = "echo (echo one); echo $( (echo one); echo ) two; echo $(echo one";
    let results = StatementSplitter::new(command).collect::<Vec<_>>();
    assert_eq!(results[0], Err(StatementError::InvalidCharacter('(', 6)));
    assert_eq!(results[1], Err(StatementError::InvalidCharacter('(', 26)));
    assert_eq!(results[2], Err(StatementError::InvalidCharacter(')', 43)));
    assert_eq!(results[3], Err(StatementError::UnterminatedSubshell));
    assert_eq!(results.len(), 4);

    let command = ">echo";
    let results = StatementSplitter::new(command).collect::<Vec<_>>();
    assert_eq!(results[0], Err(StatementError::ExpectedCommandButFound("redirection")));
    assert_eq!(results.len(), 1);

    let command = "echo $((foo bar baz)";
    let results = StatementSplitter::new(command).collect::<Vec<_>>();
    assert_eq!(results[0], Err(StatementError::UnterminatedArithmetic));
    assert_eq!(results.len(), 1);
}

#[test]
fn methods() {
    let command = "echo $join(array, ', '); echo @join(var, ', ')";
    let statements = StatementSplitter::new(command).collect::<Vec<_>>();
    assert_eq!(statements[0], Ok(StatementVariant::Default("echo $join(array, ', ')")));
    assert_eq!(statements[1], Ok(StatementVariant::Default("echo @join(var, ', ')")));
    assert_eq!(statements.len(), 2);
}

#[test]
fn processes() {
    let command = "echo $(seq 1 10); echo $(seq 1 10)";
    for statement in StatementSplitter::new(command) {
        assert_eq!(statement, Ok(StatementVariant::Default("echo $(seq 1 10)")));
    }
}

#[test]
fn array_processes() {
    let command = "echo @(echo one; sleep 1); echo @(echo one; sleep 1)";
    for statement in StatementSplitter::new(command) {
        assert_eq!(statement, Ok(StatementVariant::Default("echo @(echo one; sleep 1)")));
    }
}

#[test]
fn process_with_statements() {
    let command = "echo $(seq 1 10; seq 1 10)";
    for statement in StatementSplitter::new(command) {
        assert_eq!(statement, Ok(StatementVariant::Default(command)));
    }
}

#[test]
fn quotes() {
    let command = "echo \"This ;'is a test\"; echo 'This ;\" is also a test'";
    let results = StatementSplitter::new(command).collect::<Vec<_>>();
    assert_eq!(results.len(), 2);
    assert_eq!(results[0], Ok(StatementVariant::Default("echo \"This ;'is a test\"")));
    assert_eq!(results[1], Ok(StatementVariant::Default("echo 'This ;\" is also a test'")));
}

#[test]
fn nested_process() {
    let command = "echo $(echo one $(echo two) three)";
    let results = StatementSplitter::new(command).collect::<Vec<_>>();
    assert_eq!(results.len(), 1);
    assert_eq!(results[0], Ok(StatementVariant::Default(command)));

    let command = "echo $(echo $(echo one; echo two); echo two)";
    let results = StatementSplitter::new(command).collect::<Vec<_>>();
    assert_eq!(results.len(), 1);
    assert_eq!(results[0], Ok(StatementVariant::Default(command)));
}

#[test]
fn nested_array_process() {
    let command = "echo @(echo one @(echo two) three)";
    let results = StatementSplitter::new(command).collect::<Vec<_>>();
    assert_eq!(results.len(), 1);
    assert_eq!(results[0], Ok(StatementVariant::Default(command)));

    let command = "echo @(echo @(echo one; echo two); echo two)";
    let results = StatementSplitter::new(command).collect::<Vec<_>>();
    assert_eq!(results.len(), 1);
    assert_eq!(results[0], Ok(StatementVariant::Default(command)));
}

#[test]
fn braced_variables() {
    let command = "echo ${foo}bar ${bar}baz ${baz}quux @{zardoz}wibble";
    let results = StatementSplitter::new(command).collect::<Vec<_>>();
    assert_eq!(results.len(), 1);
    assert_eq!(results[0], Ok(StatementVariant::Default(command)));
}

#[test]
fn variants() {
    let command = r#"echo "Hello!"; echo "How are you doing?" && echo "I'm just an ordinary test." || echo "Helping by making sure your code works right."; echo "Have a good day!""#;
    let results = StatementSplitter::new(command).collect::<Vec<_>>();
    assert_eq!(results.len(), 5);
    assert_eq!(results[0], Ok(StatementVariant::Default(r#"echo "Hello!""#)));
    assert_eq!(results[1], Ok(StatementVariant::Default(r#"echo "How are you doing?""#)));
    assert_eq!(results[2], Ok(StatementVariant::And(r#"echo "I'm just an ordinary test.""#)));
    assert_eq!(
        results[3],
        Ok(StatementVariant::Or(r#"echo "Helping by making sure your code works right.""#))
    );
    assert_eq!(results[4], Ok(StatementVariant::Default(r#"echo "Have a good day!""#)));
}
<|endoftext|>


<commit_before>use crate::utils::paths;
use crate::utils::{
    get_trait_def_id, is_automatically_derived, is_copy, match_path, span_lint_and_help, span_lint_and_note,
    span_lint_and_then,
};
use if_chain::if_chain;
use rustc_hir::def_id::DefId;
use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, NestedVisitorMap, Visitor};
use rustc_hir::{
    BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, HirId, Item, ItemKind, TraitRef, UnsafeSource, Unsafety,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::map::Map;
use rustc_middle::ty::{self, Ty};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span;

declare_clippy_lint! {
    /// **What it does:** Checks for deriving `Hash` but implementing `PartialEq`
    /// explicitly or vice versa.
    ///
    /// **Why is this bad?** The implementation of these traits must agree (for
    /// example for use with `HashMap`) so it’s probably a bad idea to use a
    /// default-generated `Hash` implementation with an explicitly defined
    /// `PartialEq`. In particular, the following must hold for any type:
    ///
    /// ```text
    /// k1 == k2 ⇒ hash(k1) == hash(k2)
    /// ```
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    /// ```ignore
    /// #[derive(Hash)]
    /// struct Foo;
    ///
    /// impl PartialEq for Foo {
    ///     ...
    /// }
    /// ```
    pub DERIVE_HASH_XOR_EQ,
    correctness,
    "deriving `Hash` but implementing `PartialEq` explicitly"
}

declare_clippy_lint! {
    /// **What it does:** Checks for deriving `Ord` but implementing `PartialOrd`
    /// explicitly or vice versa.
    ///
    /// **Why is this bad?** The implementation of these traits must agree (for
    /// example for use with `sort`) so it’s probably a bad idea to use a
    /// default-generated `Ord` implementation with an explicitly defined
    /// `PartialOrd`. In particular, the following must hold for any type
    /// implementing `Ord`:
    ///
    /// ```text
    /// k1.cmp(&k2) == k1.partial_cmp(&k2).unwrap()
    /// ```
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    ///
    /// ```rust,ignore
    /// #[derive(Ord, PartialEq, Eq)]
    /// struct Foo;
    ///
    /// impl PartialOrd for Foo {
    ///     ...
    /// }
    /// ```
    /// Use instead:
    /// ```rust,ignore
    /// #[derive(PartialEq, Eq)]
    /// struct Foo;
    ///
    /// impl PartialOrd for Foo {
    ///     fn partial_cmp(&self, other: &Foo) -> Option<Ordering> {
    ///        Some(self.cmp(other))
    ///     }
    /// }
    ///
    /// impl Ord for Foo {
    ///     ...
    /// }
    /// ```
    /// or, if you don't need a custom ordering:
    /// ```rust,ignore
    /// #[derive(Ord, PartialOrd, PartialEq, Eq)]
    /// struct Foo;
    /// ```
    pub DERIVE_ORD_XOR_PARTIAL_ORD,
    correctness,
    "default lint description"
}

declare_clippy_lint! {
    /// **What it does:** Checks for explicit `Clone` implementations for `Copy`
    /// types.
    ///
    /// **Why is this bad?** To avoid surprising behaviour, these traits should
    /// agree and the behaviour of `Copy` cannot be overridden. In almost all
    /// situations a `Copy` type should have a `Clone` implementation that does
    /// nothing more than copy the object, which is what `#[derive(Copy, Clone)]`
    /// gets you.
    ///
    /// **Known problems:** Bounds of generic types are sometimes wrong: https://github.com/rust-lang/rust/issues/26925
    ///
    /// **Example:**
    /// ```rust,ignore
    /// #[derive(Copy)]
    /// struct Foo;
    ///
    /// impl Clone for Foo {
    ///     // ..
    /// }
    /// ```
    pub EXPL_IMPL_CLONE_ON_COPY,
    pedantic,
    "implementing `Clone` explicitly on `Copy` types"
}

declare_clippy_lint! {
    /// **What it does:** Checks for deriving `serde::Deserialize` on a type that
    /// has methods using `unsafe`.
    ///
    /// **Why is this bad?** Deriving `serde::Deserialize` will create a constructor
    /// that may violate invariants hold by another constructor.
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    ///
    /// ```rust,ignore
    /// use serde::Deserialize;
    ///
    /// #[derive(Deserialize)]
    /// pub struct Foo {
    ///     // ..
    /// }
    ///
    /// impl Foo {
    ///     pub fn new() -> Self {
    ///         // setup here ..
    ///     }
    ///
    ///     pub unsafe fn parts() -> (&str, &str) {
    ///         // assumes invariants hold
    ///     }
    /// }
    /// ```
    pub UNSAFE_DERIVE_DESERIALIZE,
    pedantic,
    "deriving `serde::Deserialize` on a type that has methods using `unsafe`"
}

declare_lint_pass!(Derive => [
    EXPL_IMPL_CLONE_ON_COPY,
    DERIVE_HASH_XOR_EQ,
    DERIVE_ORD_XOR_PARTIAL_ORD,
    UNSAFE_DERIVE_DESERIALIZE
]);

impl<'tcx> LateLintPass<'tcx> for Derive {
    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
        if let ItemKind::Impl {
            of_trait: Some(ref trait_ref),
            ..
        } = item.kind
        {
            let ty = cx.tcx.type_of(cx.tcx.hir().local_def_id(item.hir_id));
            let is_automatically_derived = is_automatically_derived(&*item.attrs);

            check_hash_peq(cx, item.span, trait_ref, ty, is_automatically_derived);
            check_ord_pord(cx, item.span, trait_ref, ty, is_automatically_derived);

            if is_automatically_derived {
                check_unsafe_derive_deserialize(cx, item, trait_ref, ty);
            } else {
                check_copy_clone(cx, item, trait_ref, ty);
            }
        }
    }
}

/// Implementation of the `DERIVE_HASH_XOR_EQ` lint.
fn check_hash_peq<'tcx>(
    cx: &LateContext<'tcx>,
    span: Span,
    trait_ref: &TraitRef<'_>,
    ty: Ty<'tcx>,
    hash_is_automatically_derived: bool,
) {
    if_chain! {
        if match_path(&trait_ref.path, &paths::HASH);
        if let Some(peq_trait_def_id) = cx.tcx.lang_items().eq_trait();
        if let Some(def_id) = &trait_ref.trait_def_id();
        if !def_id.is_local();
        then {
            // Look for the PartialEq implementations for `ty`
            cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| {
                let peq_is_automatically_derived = is_automatically_derived(&cx.tcx.get_attrs(impl_id));

                if peq_is_automatically_derived == hash_is_automatically_derived {
                    return;
                }

                let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation");

                // Only care about `impl PartialEq<Foo> for Foo`
                // For `impl PartialEq<B> for A, input_types is [A, B]
                if trait_ref.substs.type_at(1) == ty {
                    let mess = if peq_is_automatically_derived {
                        "you are implementing `Hash` explicitly but have derived `PartialEq`"
                    } else {
                        "you are deriving `Hash` but have implemented `PartialEq` explicitly"
                    };

                    span_lint_and_then(
                        cx,
                        DERIVE_HASH_XOR_EQ,
                        span,
                        mess,
                        |diag| {
                            if let Some(local_def_id) = impl_id.as_local() {
                                let hir_id = cx.tcx.hir().as_local_hir_id(local_def_id);
                                diag.span_note(
                                    cx.tcx.hir().span(hir_id),
                                    "`PartialEq` implemented here"
                                );
                            }
                        }
                    );
                }
            });
        }
    }
}

/// Implementation of the `DERIVE_ORD_XOR_PARTIAL_ORD` lint.
fn check_ord_pord<'tcx>(
    cx: &LateContext<'tcx>,
    span: Span,
    trait_ref: &TraitRef<'_>,
    ty: Ty<'tcx>,
    ord_is_automatically_derived: bool,
) {
    if_chain! {
        if let Some(ord_trait_def_id) = get_trait_def_id(cx, &paths::ORD);
        if let Some(pord_trait_def_id) = cx.tcx.lang_items().partial_ord_trait();
        if let Some(def_id) = &trait_ref.trait_def_id();
        if *def_id == ord_trait_def_id;
        then {
            // Look for the PartialOrd implementations for `ty`
            cx.tcx.for_each_relevant_impl(pord_trait_def_id, ty, |impl_id| {
                let pord_is_automatically_derived = is_automatically_derived(&cx.tcx.get_attrs(impl_id));

                if pord_is_automatically_derived == ord_is_automatically_derived {
                    return;
                }

                let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation");

                // Only care about `impl PartialOrd<Foo> for Foo`
                // For `impl PartialOrd<B> for A, input_types is [A, B]
                if trait_ref.substs.type_at(1) == ty {
                    let mess = if pord_is_automatically_derived {
                        "you are implementing `Ord` explicitly but have derived `PartialOrd`"
                    } else {
                        "you are deriving `Ord` but have implemented `PartialOrd` explicitly"
                    };

                    span_lint_and_then(
                        cx,
                        DERIVE_ORD_XOR_PARTIAL_ORD,
                        span,
                        mess,
                        |diag| {
                            if let Some(local_def_id) = impl_id.as_local() {
                                let hir_id = cx.tcx.hir().as_local_hir_id(local_def_id);
                                diag.span_note(
                                    cx.tcx.hir().span(hir_id),
                                    "`PartialOrd` implemented here"
                                );
                            }
                        }
                    );
                }
            });
        }
    }
}

/// Implementation of the `EXPL_IMPL_CLONE_ON_COPY` lint.
fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &TraitRef<'_>, ty: Ty<'tcx>) {
    if match_path(&trait_ref.path, &paths::CLONE_TRAIT) {
        if !is_copy(cx, ty) {
            return;
        }

        match ty.kind {
            ty::Adt(def, _) if def.is_union() => return,

            // Some types are not Clone by default but could be cloned “by hand” if necessary
            ty::Adt(def, substs) => {
                for variant in &def.variants {
                    for field in &variant.fields {
                        if let ty::FnDef(..) = field.ty(cx.tcx, substs).kind {
                            return;
                        }
                    }
                    for subst in substs {
                        if let ty::subst::GenericArgKind::Type(subst) = subst.unpack() {
                            if let ty::Param(_) = subst.kind {
                                return;
                            }
                        }
                    }
                }
            },
            _ => (),
        }

        span_lint_and_note(
            cx,
            EXPL_IMPL_CLONE_ON_COPY,
            item.span,
            "you are implementing `Clone` explicitly on a `Copy` type",
            Some(item.span),
            "consider deriving `Clone` or removing `Copy`",
        );
    }
}

/// Implementation of the `UNSAFE_DERIVE_DESERIALIZE` lint.
fn check_unsafe_derive_deserialize<'tcx>(
    cx: &LateContext<'tcx>,
    item: &Item<'_>,
    trait_ref: &TraitRef<'_>,
    ty: Ty<'tcx>,
) {
    fn item_from_def_id<'tcx>(cx: &LateContext<'tcx>, def_id: DefId) -> &'tcx Item<'tcx> {
        let hir_id = cx.tcx.hir().as_local_hir_id(def_id.expect_local());
        cx.tcx.hir().expect_item(hir_id)
    }

    fn has_unsafe<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>) -> bool {
        let mut visitor = UnsafeVisitor { cx, has_unsafe: false };
        walk_item(&mut visitor, item);
        visitor.has_unsafe
    }

    if_chain! {
        if match_path(&trait_ref.path, &paths::SERDE_DESERIALIZE);
        if let ty::Adt(def, _) = ty.kind;
        if def.did.is_local();
        if cx.tcx.inherent_impls(def.did)
            .iter()
            .map(|imp_did| item_from_def_id(cx, *imp_did))
            .any(|imp| has_unsafe(cx, imp));
        then {
            span_lint_and_help(
                cx,
                UNSAFE_DERIVE_DESERIALIZE,
                item.span,
                "you are deriving `serde::Deserialize` on a type that has methods using `unsafe`",
                None,
                "consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html"
            );
        }
    }
}

struct UnsafeVisitor<'a, 'tcx> {
    cx: &'a LateContext<'tcx>,
    has_unsafe: bool,
}

impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> {
    type Map = Map<'tcx>;

    fn visit_fn(&mut self, kind: FnKind<'tcx>, decl: &'tcx FnDecl<'_>, body_id: BodyId, span: Span, id: HirId) {
        if self.has_unsafe {
            return;
        }

        if_chain! {
            if let Some(header) = kind.header();
            if let Unsafety::Unsafe = header.unsafety;
            then {
                self.has_unsafe = true;
            }
        }

        walk_fn(self, kind, decl, body_id, span, id);
    }

    fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
        if self.has_unsafe {
            return;
        }

        if let ExprKind::Block(block, _) = expr.kind {
            match block.rules {
                BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)
                | BlockCheckMode::PushUnsafeBlock(UnsafeSource::UserProvided)
                | BlockCheckMode::PopUnsafeBlock(UnsafeSource::UserProvided) => {
                    self.has_unsafe = true;
                },
                _ => {},
            }
        }

        walk_expr(self, expr);
    }

    fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
        NestedVisitorMap::All(self.cx.tcx.hir())
    }
}
<commit_msg>s/pord/partial_ord/ to fix dogfood failure<commit_after>use crate::utils::paths;
use crate::utils::{
    get_trait_def_id, is_automatically_derived, is_copy, match_path, span_lint_and_help, span_lint_and_note,
    span_lint_and_then,
};
use if_chain::if_chain;
use rustc_hir::def_id::DefId;
use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, NestedVisitorMap, Visitor};
use rustc_hir::{
    BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, HirId, Item, ItemKind, TraitRef, UnsafeSource, Unsafety,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::map::Map;
use rustc_middle::ty::{self, Ty};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span;

declare_clippy_lint! {
    /// **What it does:** Checks for deriving `Hash` but implementing `PartialEq`
    /// explicitly or vice versa.
    ///
    /// **Why is this bad?** The implementation of these traits must agree (for
    /// example for use with `HashMap`) so it’s probably a bad idea to use a
    /// default-generated `Hash` implementation with an explicitly defined
    /// `PartialEq`. In particular, the following must hold for any type:
    ///
    /// ```text
    /// k1 == k2 ⇒ hash(k1) == hash(k2)
    /// ```
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    /// ```ignore
    /// #[derive(Hash)]
    /// struct Foo;
    ///
    /// impl PartialEq for Foo {
    ///     ...
    /// }
    /// ```
    pub DERIVE_HASH_XOR_EQ,
    correctness,
    "deriving `Hash` but implementing `PartialEq` explicitly"
}

declare_clippy_lint! {
    /// **What it does:** Checks for deriving `Ord` but implementing `PartialOrd`
    /// explicitly or vice versa.
    ///
    /// **Why is this bad?** The implementation of these traits must agree (for
    /// example for use with `sort`) so it’s probably a bad idea to use a
    /// default-generated `Ord` implementation with an explicitly defined
    /// `PartialOrd`. In particular, the following must hold for any type
    /// implementing `Ord`:
    ///
    /// ```text
    /// k1.cmp(&k2) == k1.partial_cmp(&k2).unwrap()
    /// ```
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    ///
    /// ```rust,ignore
    /// #[derive(Ord, PartialEq, Eq)]
    /// struct Foo;
    ///
    /// impl PartialOrd for Foo {
    ///     ...
    /// }
    /// ```
    /// Use instead:
    /// ```rust,ignore
    /// #[derive(PartialEq, Eq)]
    /// struct Foo;
    ///
    /// impl PartialOrd for Foo {
    ///     fn partial_cmp(&self, other: &Foo) -> Option<Ordering> {
    ///        Some(self.cmp(other))
    ///     }
    /// }
    ///
    /// impl Ord for Foo {
    ///     ...
    /// }
    /// ```
    /// or, if you don't need a custom ordering:
    /// ```rust,ignore
    /// #[derive(Ord, PartialOrd, PartialEq, Eq)]
    /// struct Foo;
    /// ```
    pub DERIVE_ORD_XOR_PARTIAL_ORD,
    correctness,
    "default lint description"
}

declare_clippy_lint! {
    /// **What it does:** Checks for explicit `Clone` implementations for `Copy`
    /// types.
    ///
    /// **Why is this bad?** To avoid surprising behaviour, these traits should
    /// agree and the behaviour of `Copy` cannot be overridden. In almost all
    /// situations a `Copy` type should have a `Clone` implementation that does
    /// nothing more than copy the object, which is what `#[derive(Copy, Clone)]`
    /// gets you.
    ///
    /// **Known problems:** Bounds of generic types are sometimes wrong: https://github.com/rust-lang/rust/issues/26925
    ///
    /// **Example:**
    /// ```rust,ignore
    /// #[derive(Copy)]
    /// struct Foo;
    ///
    /// impl Clone for Foo {
    ///     // ..
    /// }
    /// ```
    pub EXPL_IMPL_CLONE_ON_COPY,
    pedantic,
    "implementing `Clone` explicitly on `Copy` types"
}

declare_clippy_lint! {
    /// **What it does:** Checks for deriving `serde::Deserialize` on a type that
    /// has methods using `unsafe`.
    ///
    /// **Why is this bad?** Deriving `serde::Deserialize` will create a constructor
    /// that may violate invariants hold by another constructor.
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    ///
    /// ```rust,ignore
    /// use serde::Deserialize;
    ///
    /// #[derive(Deserialize)]
    /// pub struct Foo {
    ///     // ..
    /// }
    ///
    /// impl Foo {
    ///     pub fn new() -> Self {
    ///         // setup here ..
    ///     }
    ///
    ///     pub unsafe fn parts() -> (&str, &str) {
    ///         // assumes invariants hold
    ///     }
    /// }
    /// ```
    pub UNSAFE_DERIVE_DESERIALIZE,
    pedantic,
    "deriving `serde::Deserialize` on a type that has methods using `unsafe`"
}

declare_lint_pass!(Derive => [
    EXPL_IMPL_CLONE_ON_COPY,
    DERIVE_HASH_XOR_EQ,
    DERIVE_ORD_XOR_PARTIAL_ORD,
    UNSAFE_DERIVE_DESERIALIZE
]);

impl<'tcx> LateLintPass<'tcx> for Derive {
    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
        if let ItemKind::Impl {
            of_trait: Some(ref trait_ref),
            ..
        } = item.kind
        {
            let ty = cx.tcx.type_of(cx.tcx.hir().local_def_id(item.hir_id));
            let is_automatically_derived = is_automatically_derived(&*item.attrs);

            check_hash_peq(cx, item.span, trait_ref, ty, is_automatically_derived);
            check_ord_partial_ord(cx, item.span, trait_ref, ty, is_automatically_derived);

            if is_automatically_derived {
                check_unsafe_derive_deserialize(cx, item, trait_ref, ty);
            } else {
                check_copy_clone(cx, item, trait_ref, ty);
            }
        }
    }
}

/// Implementation of the `DERIVE_HASH_XOR_EQ` lint.
fn check_hash_peq<'tcx>(
    cx: &LateContext<'tcx>,
    span: Span,
    trait_ref: &TraitRef<'_>,
    ty: Ty<'tcx>,
    hash_is_automatically_derived: bool,
) {
    if_chain! {
        if match_path(&trait_ref.path, &paths::HASH);
        if let Some(peq_trait_def_id) = cx.tcx.lang_items().eq_trait();
        if let Some(def_id) = &trait_ref.trait_def_id();
        if !def_id.is_local();
        then {
            // Look for the PartialEq implementations for `ty`
            cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| {
                let peq_is_automatically_derived = is_automatically_derived(&cx.tcx.get_attrs(impl_id));

                if peq_is_automatically_derived == hash_is_automatically_derived {
                    return;
                }

                let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation");

                // Only care about `impl PartialEq<Foo> for Foo`
                // For `impl PartialEq<B> for A, input_types is [A, B]
                if trait_ref.substs.type_at(1) == ty {
                    let mess = if peq_is_automatically_derived {
                        "you are implementing `Hash` explicitly but have derived `PartialEq`"
                    } else {
                        "you are deriving `Hash` but have implemented `PartialEq` explicitly"
                    };

                    span_lint_and_then(
                        cx,
                        DERIVE_HASH_XOR_EQ,
                        span,
                        mess,
                        |diag| {
                            if let Some(local_def_id) = impl_id.as_local() {
                                let hir_id = cx.tcx.hir().as_local_hir_id(local_def_id);
                                diag.span_note(
                                    cx.tcx.hir().span(hir_id),
                                    "`PartialEq` implemented here"
                                );
                            }
                        }
                    );
                }
            });
        }
    }
}

/// Implementation of the `DERIVE_ORD_XOR_PARTIAL_ORD` lint.
fn check_ord_partial_ord<'tcx>(
    cx: &LateContext<'tcx>,
    span: Span,
    trait_ref: &TraitRef<'_>,
    ty: Ty<'tcx>,
    ord_is_automatically_derived: bool,
) {
    if_chain! {
        if let Some(ord_trait_def_id) = get_trait_def_id(cx, &paths::ORD);
        if let Some(partial_ord_trait_def_id) = cx.tcx.lang_items().partial_ord_trait();
        if let Some(def_id) = &trait_ref.trait_def_id();
        if *def_id == ord_trait_def_id;
        then {
            // Look for the PartialOrd implementations for `ty`
            cx.tcx.for_each_relevant_impl(partial_ord_trait_def_id, ty, |impl_id| {
                let partial_ord_is_automatically_derived = is_automatically_derived(&cx.tcx.get_attrs(impl_id));

                if partial_ord_is_automatically_derived == ord_is_automatically_derived {
                    return;
                }

                let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation");

                // Only care about `impl PartialOrd<Foo> for Foo`
                // For `impl PartialOrd<B> for A, input_types is [A, B]
                if trait_ref.substs.type_at(1) == ty {
                    let mess = if partial_ord_is_automatically_derived {
                        "you are implementing `Ord` explicitly but have derived `PartialOrd`"
                    } else {
                        "you are deriving `Ord` but have implemented `PartialOrd` explicitly"
                    };

                    span_lint_and_then(
                        cx,
                        DERIVE_ORD_XOR_PARTIAL_ORD,
                        span,
                        mess,
                        |diag| {
                            if let Some(local_def_id) = impl_id.as_local() {
                                let hir_id = cx.tcx.hir().as_local_hir_id(local_def_id);
                                diag.span_note(
                                    cx.tcx.hir().span(hir_id),
                                    "`PartialOrd` implemented here"
                                );
                            }
                        }
                    );
                }
            });
        }
    }
}

/// Implementation of the `EXPL_IMPL_CLONE_ON_COPY` lint.
fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &TraitRef<'_>, ty: Ty<'tcx>) {
    if match_path(&trait_ref.path, &paths::CLONE_TRAIT) {
        if !is_copy(cx, ty) {
            return;
        }

        match ty.kind {
            ty::Adt(def, _) if def.is_union() => return,

            // Some types are not Clone by default but could be cloned “by hand” if necessary
            ty::Adt(def, substs) => {
                for variant in &def.variants {
                    for field in &variant.fields {
                        if let ty::FnDef(..) = field.ty(cx.tcx, substs).kind {
                            return;
                        }
                    }
                    for subst in substs {
                        if let ty::subst::GenericArgKind::Type(subst) = subst.unpack() {
                            if let ty::Param(_) = subst.kind {
                                return;
                            }
                        }
                    }
                }
            },
            _ => (),
        }

        span_lint_and_note(
            cx,
            EXPL_IMPL_CLONE_ON_COPY,
            item.span,
            "you are implementing `Clone` explicitly on a `Copy` type",
            Some(item.span),
            "consider deriving `Clone` or removing `Copy`",
        );
    }
}

/// Implementation of the `UNSAFE_DERIVE_DESERIALIZE` lint.
fn check_unsafe_derive_deserialize<'tcx>(
    cx: &LateContext<'tcx>,
    item: &Item<'_>,
    trait_ref: &TraitRef<'_>,
    ty: Ty<'tcx>,
) {
    fn item_from_def_id<'tcx>(cx: &LateContext<'tcx>, def_id: DefId) -> &'tcx Item<'tcx> {
        let hir_id = cx.tcx.hir().as_local_hir_id(def_id.expect_local());
        cx.tcx.hir().expect_item(hir_id)
    }

    fn has_unsafe<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>) -> bool {
        let mut visitor = UnsafeVisitor { cx, has_unsafe: false };
        walk_item(&mut visitor, item);
        visitor.has_unsafe
    }

    if_chain! {
        if match_path(&trait_ref.path, &paths::SERDE_DESERIALIZE);
        if let ty::Adt(def, _) = ty.kind;
        if def.did.is_local();
        if cx.tcx.inherent_impls(def.did)
            .iter()
            .map(|imp_did| item_from_def_id(cx, *imp_did))
            .any(|imp| has_unsafe(cx, imp));
        then {
            span_lint_and_help(
                cx,
                UNSAFE_DERIVE_DESERIALIZE,
                item.span,
                "you are deriving `serde::Deserialize` on a type that has methods using `unsafe`",
                None,
                "consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html"
            );
        }
    }
}

struct UnsafeVisitor<'a, 'tcx> {
    cx: &'a LateContext<'tcx>,
    has_unsafe: bool,
}

impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> {
    type Map = Map<'tcx>;

    fn visit_fn(&mut self, kind: FnKind<'tcx>, decl: &'tcx FnDecl<'_>, body_id: BodyId, span: Span, id: HirId) {
        if self.has_unsafe {
            return;
        }

        if_chain! {
            if let Some(header) = kind.header();
            if let Unsafety::Unsafe = header.unsafety;
            then {
                self.has_unsafe = true;
            }
        }

        walk_fn(self, kind, decl, body_id, span, id);
    }

    fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
        if self.has_unsafe {
            return;
        }

        if let ExprKind::Block(block, _) = expr.kind {
            match block.rules {
                BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)
                | BlockCheckMode::PushUnsafeBlock(UnsafeSource::UserProvided)
                | BlockCheckMode::PopUnsafeBlock(UnsafeSource::UserProvided) => {
                    self.has_unsafe = true;
                },
                _ => {},
            }
        }

        walk_expr(self, expr);
    }

    fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
        NestedVisitorMap::All(self.cx.tcx.hir())
    }
}
<|endoftext|>