<commit_before>use std::io::Write;
use std::path::Path;
use std::path::PathBuf;

use cargo_edit::{
    colorize_stderr, find, manifest_from_pkgid, upgrade_requirement, workspace_members,
    LocalManifest,
};
use clap::Args;
use termcolor::{BufferWriter, Color, ColorSpec, WriteColor};

use crate::errors::*;
use crate::version::BumpLevel;
use crate::version::TargetVersion;

/// Change a package's version in the local manifest file (i.e. Cargo.toml).
#[derive(Debug, Args)]
#[clap(version)]
#[clap(group = clap::ArgGroup::new("ver").multiple(false))]
pub struct VersionArgs {
    /// Version to change manifests to
    #[clap(parse(try_from_str), group = "ver")]
    target: Option<semver::Version>,

    /// Increment manifest version
    #[clap(
        long,
        possible_values(crate::version::BumpLevel::variants()),
        group = "ver"
    )]
    bump: Option<BumpLevel>,

    /// Specify the version metadata field (e.g. a wrapped libraries version)
    #[clap(short, long)]
    pub metadata: Option<String>,

    /// Path to the manifest to upgrade
    #[clap(
        long,
        value_name = "PATH",
        parse(from_os_str),
        conflicts_with = "pkgid"
    )]
    manifest_path: Option<PathBuf>,

    /// Package id of the crate to change the version of.
    #[clap(
        long = "package",
        short = 'p',
        value_name = "PKGID",
        conflicts_with = "manifest-path",
        conflicts_with = "all",
        conflicts_with = "workspace"
    )]
    pkgid: Option<String>,

    /// Modify all packages in the workspace.
    #[clap(
        long,
        help = "[deprecated in favor of `--workspace`]",
        conflicts_with = "workspace",
        conflicts_with = "pkgid"
    )]
    all: bool,

    /// Modify all packages in the workspace.
    #[clap(long, conflicts_with = "all", conflicts_with = "pkgid")]
    workspace: bool,

    /// Print changes to be made without making them.
    #[clap(long)]
    dry_run: bool,

    /// Crates to exclude and not modify.
    #[clap(long)]
    exclude: Vec<String>,

    /// Unstable (nightly-only) flags
    #[clap(short = 'Z', value_name = "FLAG", global = true, arg_enum)]
    unstable_features: Vec<UnstableOptions>,
}

impl VersionArgs {
    pub fn exec(self) -> CargoResult<()> {
        exec(self)
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, clap::ArgEnum)]
enum UnstableOptions {}

/// Main processing function. Allows us to return a `Result` so that `main` can print pretty error
/// messages.
fn exec(args: VersionArgs) -> CargoResult<()> {
    let VersionArgs {
        target,
        bump,
        metadata,
        manifest_path,
        pkgid,
        all,
        dry_run,
        workspace,
        exclude,
        unstable_features: _,
    } = args;

    let target = match (target, bump) {
        (None, None) => TargetVersion::Relative(BumpLevel::Release),
        (None, Some(level)) => TargetVersion::Relative(level),
        (Some(version), None) => TargetVersion::Absolute(version),
        (Some(_), Some(_)) => unreachable!("clap groups should prevent this"),
    };

    if all {
        deprecated_message("The flag `--all` has been deprecated in favor of `--workspace`")?;
    }
    let all = workspace || all;
    let manifests = if all {
        Manifests::get_all(manifest_path.as_deref())
    } else if let Some(ref pkgid) = pkgid {
        Manifests::get_pkgid(manifest_path.as_deref(), pkgid)
    } else {
        Manifests::get_local_one(manifest_path.as_deref())
    }?;

    if dry_run {
        dry_run_message()?;
    }

    let workspace_members = workspace_members(manifest_path.as_deref())?;

    for (mut manifest, package) in manifests.0 {
        if exclude.contains(&package.name) {
            continue;
        }
        let current = &package.version;
        let next = target.bump(current, metadata.as_deref())?;
        if let Some(next) = next {
            manifest.set_package_version(&next);

            upgrade_message(package.name.as_str(), current, &next)?;
            if !dry_run {
                manifest.write()?;
            }

            let crate_root =
                dunce::canonicalize(manifest.path.parent().expect("at least a parent"))?;
            for member in workspace_members.iter() {
                let mut dep_manifest = LocalManifest::try_new(member.manifest_path.as_std_path())?;
                let mut changed = false;
                let dep_crate_root = dep_manifest
                    .path
                    .parent()
                    .expect("at least a parent")
                    .to_owned();
                for dep in dep_manifest
                    .get_dependency_tables_mut()
                    .flat_map(|t| t.iter_mut().filter_map(|(_, d)| d.as_table_like_mut()))
                    .filter(|d| {
                        if !d.contains_key("version") {
                            return false;
                        }
                        match d.get("path").and_then(|i| i.as_str()).and_then(|relpath| {
                            dunce::canonicalize(dep_crate_root.join(relpath)).ok()
                        }) {
                            Some(dep_path) => dep_path == crate_root.as_path(),
                            None => false,
                        }
                    })
                {
                    let old_req = dep
                        .get("version")
                        .expect("filter ensures this")
                        .as_str()
                        .unwrap_or("*");
                    if let Some(new_req) = upgrade_requirement(old_req, &next)? {
                        upgrade_dependent_message(member.name.as_str(), old_req, &new_req)?;
                        dep.insert("version", toml_edit::value(new_req));
                        changed = true;
                    }
                }
                if changed && !dry_run {
                    dep_manifest.write()?;
                }
            }
        }
    }

    Ok(())
}

/// A collection of manifests.
struct Manifests(Vec<(LocalManifest, cargo_metadata::Package)>);

impl Manifests {
    /// Get all manifests in the workspace.
    fn get_all(manifest_path: Option<&Path>) -> CargoResult<Self> {
        let mut cmd = cargo_metadata::MetadataCommand::new();
        cmd.no_deps();
        if let Some(path) = manifest_path {
            cmd.manifest_path(path);
        }
        let result = cmd
            .exec()
            .with_context(|| "Failed to get workspace metadata")?;
        result
            .packages
            .into_iter()
            .map(|package| {
                Ok((
                    LocalManifest::try_new(Path::new(&package.manifest_path))?,
                    package,
                ))
            })
            .collect::<CargoResult<Vec<_>>>()
            .map(Manifests)
    }

    fn get_pkgid(manifest_path: Option<&Path>, pkgid: &str) -> CargoResult<Self> {
        let package = manifest_from_pkgid(manifest_path, pkgid)?;
        let manifest = LocalManifest::try_new(Path::new(&package.manifest_path))?;
        Ok(Manifests(vec![(manifest, package)]))
    }

    /// Get the manifest specified by the manifest path. Try to make an educated guess if no path is
    /// provided.
    fn get_local_one(manifest_path: Option<&Path>) -> CargoResult<Self> {
        let resolved_manifest_path: String = find(manifest_path)?.to_string_lossy().into();

        let manifest = LocalManifest::find(manifest_path)?;

        let mut cmd = cargo_metadata::MetadataCommand::new();
        cmd.no_deps();
        if let Some(path) = manifest_path {
            cmd.manifest_path(path);
        }
        let result = cmd.exec().with_context(|| "Invalid manifest")?;
        let packages = result.packages;
        let package = packages
            .iter()
            .find(|p| p.manifest_path == resolved_manifest_path)
            // If we have successfully got metadata, but our manifest path does not correspond to a
            // package, we must have been called against a virtual manifest.
            .with_context(|| {
                "Found virtual manifest, but this command requires running against an \
                 actual package in this workspace. Try adding `--workspace`."
            })?;

        Ok(Manifests(vec![(manifest, package.to_owned())]))
    }
}

fn dry_run_message() -> CargoResult<()> {
    let colorchoice = colorize_stderr();
    let bufwtr = BufferWriter::stderr(colorchoice);
    let mut buffer = bufwtr.buffer();
    buffer
        .set_color(ColorSpec::new().set_fg(Some(Color::Cyan)).set_bold(true))
        .with_context(|| "Failed to set output colour")?;
    write!(&mut buffer, "Starting dry run. ").with_context(|| "Failed to write dry run message")?;
    buffer
        .set_color(&ColorSpec::new())
        .with_context(|| "Failed to clear output colour")?;
    writeln!(&mut buffer, "Changes will not be saved.")
        .with_context(|| "Failed to write dry run message")?;
    bufwtr
        .print(&buffer)
        .with_context(|| "Failed to print dry run message")
}

fn deprecated_message(message: &str) -> CargoResult<()> {
    let colorchoice = colorize_stderr();
    let bufwtr = BufferWriter::stderr(colorchoice);
    let mut buffer = bufwtr.buffer();
    buffer
        .set_color(ColorSpec::new().set_fg(Some(Color::Red)).set_bold(true))
        .with_context(|| "Failed to set output colour")?;
    writeln!(&mut buffer, "{}", message).with_context(|| "Failed to write dry run message")?;
    buffer
        .set_color(&ColorSpec::new())
        .with_context(|| "Failed to clear output colour")?;
    bufwtr
        .print(&buffer)
        .with_context(|| "Failed to print dry run message")
}

fn upgrade_message(name: &str, from: &semver::Version, to: &semver::Version) -> CargoResult<()> {
    let colorchoice = colorize_stderr();
    let bufwtr = BufferWriter::stderr(colorchoice);
    let mut buffer = bufwtr.buffer();
    buffer
        .set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true))
        .with_context(|| "Failed to print dry run message")?;
    write!(&mut buffer, "{:>12}", "Upgraded").with_context(|| "Failed to print dry run message")?;
    buffer
        .reset()
        .with_context(|| "Failed to print dry run message")?;
    writeln!(&mut buffer, " {} from {} to {}", name, from, to)
        .with_context(|| "Failed to print dry run message")?;
    bufwtr
        .print(&buffer)
        .with_context(|| "Failed to print dry run message")
}

fn upgrade_dependent_message(name: &str, old_req: &str, new_req: &str) -> CargoResult<()> {
    let colorchoice = colorize_stderr();
    let bufwtr = BufferWriter::stderr(colorchoice);
    let mut buffer = bufwtr.buffer();
    buffer
        .set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true))
        .with_context(|| "Failed to print dry run message")?;
    write!(&mut buffer, "{:>16}", "Updated dependency")
        .with_context(|| "Failed to print dry run message")?;
    buffer
        .reset()
        .with_context(|| "Failed to print dry run message")?;
    writeln!(&mut buffer, " {} from {} to {}", name, old_req, new_req)
        .with_context(|| "Failed to print dry run message")?;
    bufwtr
        .print(&buffer)
        .with_context(|| "Failed to print dry run message")
}
<commit_msg>fix(set-version): Don't overwrite dependency updates<commit_after>use std::io::Write;
use std::path::Path;
use std::path::PathBuf;

use cargo_edit::{
    colorize_stderr, find, manifest_from_pkgid, upgrade_requirement, workspace_members,
    LocalManifest,
};
use clap::Args;
use termcolor::{BufferWriter, Color, ColorSpec, WriteColor};

use crate::errors::*;
use crate::version::BumpLevel;
use crate::version::TargetVersion;

/// Change a package's version in the local manifest file (i.e. Cargo.toml).
#[derive(Debug, Args)]
#[clap(version)]
#[clap(group = clap::ArgGroup::new("ver").multiple(false))]
pub struct VersionArgs {
    /// Version to change manifests to
    #[clap(parse(try_from_str), group = "ver")]
    target: Option<semver::Version>,

    /// Increment manifest version
    #[clap(
        long,
        possible_values(crate::version::BumpLevel::variants()),
        group = "ver"
    )]
    bump: Option<BumpLevel>,

    /// Specify the version metadata field (e.g. a wrapped libraries version)
    #[clap(short, long)]
    pub metadata: Option<String>,

    /// Path to the manifest to upgrade
    #[clap(
        long,
        value_name = "PATH",
        parse(from_os_str),
        conflicts_with = "pkgid"
    )]
    manifest_path: Option<PathBuf>,

    /// Package id of the crate to change the version of.
    #[clap(
        long = "package",
        short = 'p',
        value_name = "PKGID",
        conflicts_with = "manifest-path",
        conflicts_with = "all",
        conflicts_with = "workspace"
    )]
    pkgid: Option<String>,

    /// Modify all packages in the workspace.
    #[clap(
        long,
        help = "[deprecated in favor of `--workspace`]",
        conflicts_with = "workspace",
        conflicts_with = "pkgid"
    )]
    all: bool,

    /// Modify all packages in the workspace.
    #[clap(long, conflicts_with = "all", conflicts_with = "pkgid")]
    workspace: bool,

    /// Print changes to be made without making them.
    #[clap(long)]
    dry_run: bool,

    /// Crates to exclude and not modify.
    #[clap(long)]
    exclude: Vec<String>,

    /// Unstable (nightly-only) flags
    #[clap(short = 'Z', value_name = "FLAG", global = true, arg_enum)]
    unstable_features: Vec<UnstableOptions>,
}

impl VersionArgs {
    pub fn exec(self) -> CargoResult<()> {
        exec(self)
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, clap::ArgEnum)]
enum UnstableOptions {}

/// Main processing function. Allows us to return a `Result` so that `main` can print pretty error
/// messages.
fn exec(args: VersionArgs) -> CargoResult<()> {
    let VersionArgs {
        target,
        bump,
        metadata,
        manifest_path,
        pkgid,
        all,
        dry_run,
        workspace,
        exclude,
        unstable_features: _,
    } = args;

    let target = match (target, bump) {
        (None, None) => TargetVersion::Relative(BumpLevel::Release),
        (None, Some(level)) => TargetVersion::Relative(level),
        (Some(version), None) => TargetVersion::Absolute(version),
        (Some(_), Some(_)) => unreachable!("clap groups should prevent this"),
    };

    if all {
        deprecated_message("The flag `--all` has been deprecated in favor of `--workspace`")?;
    }
    let all = workspace || all;
    let manifests = if all {
        Manifests::get_all(manifest_path.as_deref())
    } else if let Some(ref pkgid) = pkgid {
        Manifests::get_pkgid(manifest_path.as_deref(), pkgid)
    } else {
        Manifests::get_local_one(manifest_path.as_deref())
    }?;

    if dry_run {
        dry_run_message()?;
    }

    let workspace_members = workspace_members(manifest_path.as_deref())?;

    for package in manifests.0 {
        if exclude.contains(&package.name) {
            continue;
        }
        let current = &package.version;
        let next = target.bump(current, metadata.as_deref())?;
        if let Some(next) = next {
            {
                let mut manifest = LocalManifest::try_new(Path::new(&package.manifest_path))?;
                manifest.set_package_version(&next);

                upgrade_message(package.name.as_str(), current, &next)?;
                if !dry_run {
                    manifest.write()?;
                }
            }

            let crate_root =
                dunce::canonicalize(package.manifest_path.parent().expect("at least a parent"))?;
            for member in workspace_members.iter() {
                let mut dep_manifest = LocalManifest::try_new(member.manifest_path.as_std_path())?;
                let mut changed = false;
                let dep_crate_root = dep_manifest
                    .path
                    .parent()
                    .expect("at least a parent")
                    .to_owned();
                for dep in dep_manifest
                    .get_dependency_tables_mut()
                    .flat_map(|t| t.iter_mut().filter_map(|(_, d)| d.as_table_like_mut()))
                    .filter(|d| {
                        if !d.contains_key("version") {
                            return false;
                        }
                        match d.get("path").and_then(|i| i.as_str()).and_then(|relpath| {
                            dunce::canonicalize(dep_crate_root.join(relpath)).ok()
                        }) {
                            Some(dep_path) => dep_path == crate_root.as_path(),
                            None => false,
                        }
                    })
                {
                    let old_req = dep
                        .get("version")
                        .expect("filter ensures this")
                        .as_str()
                        .unwrap_or("*");
                    if let Some(new_req) = upgrade_requirement(old_req, &next)? {
                        upgrade_dependent_message(member.name.as_str(), old_req, &new_req)?;
                        dep.insert("version", toml_edit::value(new_req));
                        changed = true;
                    }
                }
                if changed && !dry_run {
                    dep_manifest.write()?;
                }
            }
        }
    }

    Ok(())
}

/// A collection of manifests.
struct Manifests(Vec<cargo_metadata::Package>);

impl Manifests {
    /// Get all manifests in the workspace.
    fn get_all(manifest_path: Option<&Path>) -> CargoResult<Self> {
        let mut cmd = cargo_metadata::MetadataCommand::new();
        cmd.no_deps();
        if let Some(path) = manifest_path {
            cmd.manifest_path(path);
        }
        let result = cmd
            .exec()
            .with_context(|| "Failed to get workspace metadata")?;
        Ok(Self(result.packages))
    }

    fn get_pkgid(manifest_path: Option<&Path>, pkgid: &str) -> CargoResult<Self> {
        let package = manifest_from_pkgid(manifest_path, pkgid)?;
        Ok(Manifests(vec![package]))
    }

    /// Get the manifest specified by the manifest path. Try to make an educated guess if no path is
    /// provided.
    fn get_local_one(manifest_path: Option<&Path>) -> CargoResult<Self> {
        let resolved_manifest_path: String = find(manifest_path)?.to_string_lossy().into();

        let mut cmd = cargo_metadata::MetadataCommand::new();
        cmd.no_deps();
        if let Some(path) = manifest_path {
            cmd.manifest_path(path);
        }
        let result = cmd.exec().with_context(|| "Invalid manifest")?;
        let packages = result.packages;
        let package = packages
            .iter()
            .find(|p| p.manifest_path == resolved_manifest_path)
            // If we have successfully got metadata, but our manifest path does not correspond to a
            // package, we must have been called against a virtual manifest.
            .with_context(|| {
                "Found virtual manifest, but this command requires running against an \
                 actual package in this workspace. Try adding `--workspace`."
            })?;

        Ok(Manifests(vec![package.to_owned()]))
    }
}

fn dry_run_message() -> CargoResult<()> {
    let colorchoice = colorize_stderr();
    let bufwtr = BufferWriter::stderr(colorchoice);
    let mut buffer = bufwtr.buffer();
    buffer
        .set_color(ColorSpec::new().set_fg(Some(Color::Cyan)).set_bold(true))
        .with_context(|| "Failed to set output colour")?;
    write!(&mut buffer, "Starting dry run. ").with_context(|| "Failed to write dry run message")?;
    buffer
        .set_color(&ColorSpec::new())
        .with_context(|| "Failed to clear output colour")?;
    writeln!(&mut buffer, "Changes will not be saved.")
        .with_context(|| "Failed to write dry run message")?;
    bufwtr
        .print(&buffer)
        .with_context(|| "Failed to print dry run message")
}

fn deprecated_message(message: &str) -> CargoResult<()> {
    let colorchoice = colorize_stderr();
    let bufwtr = BufferWriter::stderr(colorchoice);
    let mut buffer = bufwtr.buffer();
    buffer
        .set_color(ColorSpec::new().set_fg(Some(Color::Red)).set_bold(true))
        .with_context(|| "Failed to set output colour")?;
    writeln!(&mut buffer, "{}", message).with_context(|| "Failed to write dry run message")?;
    buffer
        .set_color(&ColorSpec::new())
        .with_context(|| "Failed to clear output colour")?;
    bufwtr
        .print(&buffer)
        .with_context(|| "Failed to print dry run message")
}

fn upgrade_message(name: &str, from: &semver::Version, to: &semver::Version) -> CargoResult<()> {
    let colorchoice = colorize_stderr();
    let bufwtr = BufferWriter::stderr(colorchoice);
    let mut buffer = bufwtr.buffer();
    buffer
        .set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true))
        .with_context(|| "Failed to print dry run message")?;
    write!(&mut buffer, "{:>12}", "Upgraded").with_context(|| "Failed to print dry run message")?;
    buffer
        .reset()
        .with_context(|| "Failed to print dry run message")?;
    writeln!(&mut buffer, " {} from {} to {}", name, from, to)
        .with_context(|| "Failed to print dry run message")?;
    bufwtr
        .print(&buffer)
        .with_context(|| "Failed to print dry run message")
}

fn upgrade_dependent_message(name: &str, old_req: &str, new_req: &str) -> CargoResult<()> {
    let colorchoice = colorize_stderr();
    let bufwtr = BufferWriter::stderr(colorchoice);
    let mut buffer = bufwtr.buffer();
    buffer
        .set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true))
        .with_context(|| "Failed to print dry run message")?;
    write!(&mut buffer, "{:>16}", "Updated dependency")
        .with_context(|| "Failed to print dry run message")?;
    buffer
        .reset()
        .with_context(|| "Failed to print dry run message")?;
    writeln!(&mut buffer, " {} from {} to {}", name, old_req, new_req)
        .with_context(|| "Failed to print dry run message")?;
    bufwtr
        .print(&buffer)
        .with_context(|| "Failed to print dry run message")
}
<|endoftext|>


<commit_before>//! Implements a command for uploading react-native projects.
use std::env;
use std::fs;
use std::path::PathBuf;
use std::process;

use anyhow::{bail, Result};
use chrono::Duration;
use clap::{Arg, ArgMatches, Command};
use if_chain::if_chain;
use log::info;
use serde::{Deserialize, Serialize};

use crate::api::{Api, NewRelease};
use crate::config::Config;
use crate::utils::args::ArgExt;
use crate::utils::file_search::ReleaseFileSearch;
use crate::utils::file_upload::UploadContext;
use crate::utils::fs::TempFile;
use crate::utils::sourcemaps::SourceMapProcessor;
use crate::utils::system::propagate_exit_status;
use crate::utils::xcode::{InfoPlist, MayDetach};

#[derive(Serialize, Deserialize, Default, Debug)]
struct SourceMapReport {
    bundle_path: Option<PathBuf>,
    sourcemap_path: Option<PathBuf>,
}

pub fn make_command(command: Command) -> Command {
    command
        .about("Upload react-native projects in a Xcode build step.")
        .org_arg()
        .project_arg(false)
        .arg(Arg::new("force").long("force").short('f').help(
            "Force the script to run, even in debug configuration.{n}This rarely \
             does what you want because the default build script does not actually \
             produce any information that the sentry build tool could pick up on.",
        ))
        .arg(Arg::new("allow_fetch").long("allow-fetch").help(
            "Enable sourcemap fetching from the packager.{n}If this is enabled \
             the react native packager needs to run and sourcemaps are downloade \
             from it if the simulator platform is detected.",
        ))
        .arg(
            Arg::new("fetch_from")
                .long("fetch-from")
                .value_name("URL")
                .help(
                    "Set the URL to fetch sourcemaps from.{n}\
                     The default is http://127.0.0.1:8081/, where the react-native \
                     packager runs by default.",
                ),
        )
        .arg(Arg::new("force_foreground").long("force-foreground").help(
            "Wait for the process to finish.{n}\
                     By default part of the build process will when triggered from Xcode \
                     detach and continue in the background.  When an error happens, \
                     a dialog is shown.  If this parameter is passed, Xcode will wait \
                     for the process to finish before the build finishes and output \
                     will be shown in the Xcode build output.",
        ))
        .arg(
            Arg::new("build_script")
                .value_name("BUILD_SCRIPT")
                .index(1)
                .help(
                    "Optional path to the build script.{n}\
                     This is the path to the `react-native-xcode.sh` script you want \
                     to use.  By default the bundled build script is used.",
                ),
        )
        .arg(
            Arg::new("dist")
                .long("dist")
                .value_name("DISTRIBUTION")
                .multiple_occurrences(true)
                .help("The names of the distributions to publish. Can be supplied multiple times."),
        )
        .arg(
            Arg::new("args")
                .value_name("ARGS")
                .multiple_occurrences(true)
                .last(true)
                .help("Optional arguments to pass to the build script."),
        )
        .arg(
            Arg::new("wait")
                .long("wait")
                .help("Wait for the server to fully process uploaded files."),
        )
}

fn find_node() -> String {
    if let Ok(path) = env::var("NODE_BINARY") {
        if !path.is_empty() {
            return path;
        }
    }
    "node".into()
}

pub fn execute(matches: &ArgMatches) -> Result<()> {
    let config = Config::current();
    let (org, project) = config.get_org_and_project(matches)?;
    let should_wrap = matches.is_present("force")
        || match env::var("CONFIGURATION") {
            Ok(config) => !&config.contains("Debug"),
            Err(_) => bail!("Need to run this from Xcode"),
        };
    let base = env::current_dir()?;
    let script = if let Some(path) = matches.value_of("build_script") {
        base.join(path)
    } else {
        base.join("../node_modules/react-native/scripts/react-native-xcode.sh")
    }
    .canonicalize()?;

    info!(
        "Issuing a command for Organization: {} Project: {}",
        org, project
    );

    // if we allow fetching and we detect a simulator run, then we need to switch
    // to simulator mode.
    let fetch_url;
    if_chain! {
        if matches.is_present("allow_fetch");
        if let Ok(val) = env::var("PLATFORM_NAME");
        if val.ends_with("simulator");
        then {
            let url = matches.value_of("fetch_from").unwrap_or("http://127.0.0.1:8081/");
            info!("Fetching sourcemaps from {}", url);
            fetch_url = Some(url);
        } else {
            info!("Using react-native build script at {}", base.display());
            fetch_url = None;
        }
    }

    // in case we are in debug mode we directly dispatch to the script
    // and exit out early.
    if !should_wrap && fetch_url.is_none() {
        info!("Running in debug mode, skipping script wrapping.");
        let rv = process::Command::new(&script).spawn()?.wait()?;
        propagate_exit_status(rv);
        return Ok(());
    }

    info!("Parsing Info.plist");
    let plist = match InfoPlist::discover_from_env()? {
        Some(plist) => plist,
        None => bail!("Could not find info.plist"),
    };
    info!("Parse result from Info.plist: {:?}", &plist);
    let report_file = TempFile::create()?;
    let node = find_node();
    info!("Using node interpreter '{}'", &node);

    MayDetach::wrap("React native symbol handling", |md| {
        let bundle_path;
        let sourcemap_path;
        let bundle_url;
        let sourcemap_url;
        let bundle_file;
        let sourcemap_file;

        // If we have a fetch URL we need to fetch them from there now.  In that
        // case we do indeed fetch it right from the running packager and then
        // store it in temporary files for later consumption.
        if let Some(url) = fetch_url {
            if !matches.is_present("force_foreground") {
                md.may_detach()?;
            }
            let api = Api::current();
            let url = url.trim_end_matches('/');
            bundle_file = TempFile::create()?;
            bundle_path = bundle_file.path().to_path_buf();
            bundle_url = "~/index.ios.bundle".to_string();
            sourcemap_file = TempFile::create()?;
            sourcemap_path = sourcemap_file.path().to_path_buf();
            sourcemap_url = "~/index.ios.map".to_string();

            // wait up to 10 seconds for the server to be up.
            if !api.wait_until_available(url, Duration::seconds(10))? {
                bail!("Error: react-native packager did not respond in time");
            }

            api.download(
                &format!("{}/index.ios.bundle?platform=ios&dev=true", url),
                &mut bundle_file.open()?,
            )?;
            api.download(
                &format!("{}/index.ios.map?platform=ios&dev=true", url),
                &mut sourcemap_file.open()?,
            )?;

        // This is the case where we need to hook into the release process to
        // collect sourcemaps when they are generated.
        //
        // this invokes via an indirection of sentry-cli our wrap_call() below.
        // What is happening behind the scenes is that we switch out NODE_BINARY
        // for ourselves which is what the react-native build script normally
        // invokes.  Because we export __SENTRY_RN_WRAP_XCODE_CALL=1, the main
        // sentry-cli script will invoke our wrap_call() function below.
        //
        // That will then attempt to figure out that a react-native bundle is
        // happening to the build script, parse out the arguments, add additional
        // arguments if needed and then report the parsed arguments to a temporary
        // JSON file we load back below.
        //
        // With that we we then have all the information we need to invoke the
        // upload process.
        } else {
            let rv = process::Command::new(&script)
                .env("NODE_BINARY", env::current_exe()?.to_str().unwrap())
                .env("SENTRY_RN_REAL_NODE_BINARY", &node)
                .env(
                    "SENTRY_RN_SOURCEMAP_REPORT",
                    report_file.path().to_str().unwrap(),
                )
                .env("__SENTRY_RN_WRAP_XCODE_CALL", "1")
                .spawn()?
                .wait()?;
            propagate_exit_status(rv);

            if !matches.is_present("force_foreground") {
                md.may_detach()?;
            }
            let mut f = fs::File::open(report_file.path())?;
            let report: SourceMapReport = serde_json::from_reader(&mut f).unwrap_or_else(|_| {
                let format_err = format!(
                    "File {} doesn't contain a valid JSON data.",
                    report_file.path().display()
                );
                panic!("{}", format_err);
            });
            if report.bundle_path.is_none() || report.sourcemap_path.is_none() {
                println!("Warning: build produced no sourcemaps.");
                return Ok(());
            }

            bundle_path = report.bundle_path.unwrap();
            bundle_url = format!("~/{}", bundle_path.file_name().unwrap().to_string_lossy());
            sourcemap_path = report.sourcemap_path.unwrap();
            sourcemap_url = format!(
                "~/{}",
                sourcemap_path.file_name().unwrap().to_string_lossy()
            );
        }

        // now that we have all the data, we can now process and upload the
        // sourcemaps.
        println!("Processing react-native sourcemaps for Sentry upload.");
        info!("  bundle path: {}", bundle_path.display());
        info!("  sourcemap path: {}", sourcemap_path.display());

        let mut processor = SourceMapProcessor::new();
        processor.add(&bundle_url, ReleaseFileSearch::collect_file(bundle_path)?)?;
        processor.add(
            &sourcemap_url,
            ReleaseFileSearch::collect_file(sourcemap_path)?,
        )?;
        processor.rewrite(&[base.parent().unwrap().to_str().unwrap()])?;
        processor.add_sourcemap_references()?;

        let dist = env::var("SENTRY_DIST").unwrap_or_else(|_| plist.build().to_string());
        let release_name = env::var("SENTRY_RELEASE").unwrap_or(format!(
            "{}@{}+{}",
            plist.bundle_id(),
            plist.version(),
            dist
        ));

        let api = Api::current();
        let release = api.new_release(
            &org,
            &NewRelease {
                version: release_name,
                projects: vec![project.to_string()],
                ..Default::default()
            },
        )?;

        match matches.values_of("dist") {
            None => {
                processor.upload(&UploadContext {
                    org: &org,
                    project: Some(&project),
                    release: &release.version,
                    dist: Some(&dist),
                    wait: matches.is_present("wait"),
                })?;
            }
            Some(dists) => {
                for dist in dists {
                    processor.upload(&UploadContext {
                        org: &org,
                        project: Some(&project),
                        release: &release.version,
                        dist: Some(dist),
                        wait: matches.is_present("wait"),
                    })?;
                }
            }
        }

        Ok(())
    })
}

pub fn wrap_call() -> Result<()> {
    let mut args: Vec<_> = env::args().skip(1).collect();
    let mut bundle_path = None;
    let mut sourcemap_path = None;

    if args.len() > 1 && (args[1] == "bundle" || args[1] == "ram-bundle") {
        let mut iter = args.iter().fuse();
        while let Some(item) = iter.next() {
            if item == "--sourcemap-output" {
                sourcemap_path = iter.next().cloned();
            } else if let Some(rest) = item.strip_prefix("--sourcemap-output=") {
                sourcemap_path = Some(rest.to_string());
            } else if item == "--bundle-output" {
                bundle_path = iter.next().cloned();
            } else if let Some(rest) = item.strip_prefix("--bundle-output=") {
                bundle_path = Some(rest.to_string());
            }
        }
    }

    let mut sourcemap_report = SourceMapReport::default();

    if sourcemap_path.is_none() && bundle_path.is_some() {
        let mut path = env::temp_dir();
        let mut map_path = PathBuf::from(bundle_path.clone().unwrap());
        map_path.set_extension("jsbundle.map");
        path.push(map_path.file_name().unwrap());
        sourcemap_report.sourcemap_path = Some(PathBuf::from(&path));
        args.push("--sourcemap-output".into());
        args.push(path.into_os_string().into_string().unwrap());
    } else if let Some(path) = sourcemap_path {
        sourcemap_report.sourcemap_path = Some(PathBuf::from(path));
    }

    sourcemap_report.bundle_path = bundle_path.map(PathBuf::from);

    let rv = process::Command::new(env::var("SENTRY_RN_REAL_NODE_BINARY").unwrap())
        .args(&args)
        .spawn()?
        .wait()?;
    propagate_exit_status(rv);

    let mut f = fs::File::create(env::var("SENTRY_RN_SOURCEMAP_REPORT").unwrap())?;
    serde_json::to_writer(&mut f, &sourcemap_report)?;

    Ok(())
}
<commit_msg>fix: Correctly parse react-native xcode command arguments<commit_after>//! Implements a command for uploading react-native projects.
use std::env;
use std::fs;
use std::path::PathBuf;
use std::process;

use anyhow::{bail, Result};
use chrono::Duration;
use clap::{Arg, ArgMatches, Command};
use if_chain::if_chain;
use log::info;
use serde::{Deserialize, Serialize};

use crate::api::{Api, NewRelease};
use crate::config::Config;
use crate::utils::args::ArgExt;
use crate::utils::file_search::ReleaseFileSearch;
use crate::utils::file_upload::UploadContext;
use crate::utils::fs::TempFile;
use crate::utils::sourcemaps::SourceMapProcessor;
use crate::utils::system::propagate_exit_status;
use crate::utils::xcode::{InfoPlist, MayDetach};

#[derive(Serialize, Deserialize, Default, Debug)]
struct SourceMapReport {
    bundle_path: Option<PathBuf>,
    sourcemap_path: Option<PathBuf>,
}

pub fn make_command(command: Command) -> Command {
    command
        .about("Upload react-native projects in a Xcode build step.")
        .org_arg()
        .project_arg(false)
        .arg(Arg::new("force").long("force").short('f').help(
            "Force the script to run, even in debug configuration.{n}This rarely \
             does what you want because the default build script does not actually \
             produce any information that the sentry build tool could pick up on.",
        ))
        .arg(Arg::new("allow_fetch").long("allow-fetch").help(
            "Enable sourcemap fetching from the packager.{n}If this is enabled \
             the react native packager needs to run and sourcemaps are downloade \
             from it if the simulator platform is detected.",
        ))
        .arg(
            Arg::new("fetch_from")
                .long("fetch-from")
                .value_name("URL")
                .help(
                    "Set the URL to fetch sourcemaps from.{n}\
                     The default is http://127.0.0.1:8081/, where the react-native \
                     packager runs by default.",
                ),
        )
        .arg(Arg::new("force_foreground").long("force-foreground").help(
            "Wait for the process to finish.{n}\
                     By default part of the build process will when triggered from Xcode \
                     detach and continue in the background.  When an error happens, \
                     a dialog is shown.  If this parameter is passed, Xcode will wait \
                     for the process to finish before the build finishes and output \
                     will be shown in the Xcode build output.",
        ))
        .arg(
            Arg::new("build_script")
                .value_name("BUILD_SCRIPT")
                .index(1)
                .help(
                    "Optional path to the build script.{n}\
                     This is the path to the `react-native-xcode.sh` script you want \
                     to use.  By default the bundled build script is used.",
                ),
        )
        .arg(
            Arg::new("dist")
                .long("dist")
                .value_name("DISTRIBUTION")
                .multiple_occurrences(true)
                .help("The names of the distributions to publish. Can be supplied multiple times."),
        )
        .arg(
            Arg::new("args")
                .value_name("ARGS")
                .takes_value(true)
                .multiple_values(true)
                .index(2)
                .last(true)
                .help("Optional arguments to pass to the build script."),
        )
        .arg(
            Arg::new("wait")
                .long("wait")
                .help("Wait for the server to fully process uploaded files."),
        )
}

fn find_node() -> String {
    if let Ok(path) = env::var("NODE_BINARY") {
        if !path.is_empty() {
            return path;
        }
    }
    "node".into()
}

pub fn execute(matches: &ArgMatches) -> Result<()> {
    let config = Config::current();
    let (org, project) = config.get_org_and_project(matches)?;
    let should_wrap = matches.is_present("force")
        || match env::var("CONFIGURATION") {
            Ok(config) => !&config.contains("Debug"),
            Err(_) => bail!("Need to run this from Xcode"),
        };
    let base = env::current_dir()?;
    let script = if let Some(path) = matches.value_of("build_script") {
        base.join(path)
    } else {
        base.join("../node_modules/react-native/scripts/react-native-xcode.sh")
    }
    .canonicalize()?;

    info!(
        "Issuing a command for Organization: {} Project: {}",
        org, project
    );

    // if we allow fetching and we detect a simulator run, then we need to switch
    // to simulator mode.
    let fetch_url;
    if_chain! {
        if matches.is_present("allow_fetch");
        if let Ok(val) = env::var("PLATFORM_NAME");
        if val.ends_with("simulator");
        then {
            let url = matches.value_of("fetch_from").unwrap_or("http://127.0.0.1:8081/");
            info!("Fetching sourcemaps from {}", url);
            fetch_url = Some(url);
        } else {
            info!("Using react-native build script at {}", base.display());
            fetch_url = None;
        }
    }

    // in case we are in debug mode we directly dispatch to the script
    // and exit out early.
    if !should_wrap && fetch_url.is_none() {
        info!("Running in debug mode, skipping script wrapping.");
        let rv = process::Command::new(&script).spawn()?.wait()?;
        propagate_exit_status(rv);
        return Ok(());
    }

    info!("Parsing Info.plist");
    let plist = match InfoPlist::discover_from_env()? {
        Some(plist) => plist,
        None => bail!("Could not find info.plist"),
    };
    info!("Parse result from Info.plist: {:?}", &plist);
    let report_file = TempFile::create()?;
    let node = find_node();
    info!("Using node interpreter '{}'", &node);

    MayDetach::wrap("React native symbol handling", |md| {
        let bundle_path;
        let sourcemap_path;
        let bundle_url;
        let sourcemap_url;
        let bundle_file;
        let sourcemap_file;

        // If we have a fetch URL we need to fetch them from there now.  In that
        // case we do indeed fetch it right from the running packager and then
        // store it in temporary files for later consumption.
        if let Some(url) = fetch_url {
            if !matches.is_present("force_foreground") {
                md.may_detach()?;
            }
            let api = Api::current();
            let url = url.trim_end_matches('/');
            bundle_file = TempFile::create()?;
            bundle_path = bundle_file.path().to_path_buf();
            bundle_url = "~/index.ios.bundle".to_string();
            sourcemap_file = TempFile::create()?;
            sourcemap_path = sourcemap_file.path().to_path_buf();
            sourcemap_url = "~/index.ios.map".to_string();

            // wait up to 10 seconds for the server to be up.
            if !api.wait_until_available(url, Duration::seconds(10))? {
                bail!("Error: react-native packager did not respond in time");
            }

            api.download(
                &format!("{}/index.ios.bundle?platform=ios&dev=true", url),
                &mut bundle_file.open()?,
            )?;
            api.download(
                &format!("{}/index.ios.map?platform=ios&dev=true", url),
                &mut sourcemap_file.open()?,
            )?;

        // This is the case where we need to hook into the release process to
        // collect sourcemaps when they are generated.
        //
        // this invokes via an indirection of sentry-cli our wrap_call() below.
        // What is happening behind the scenes is that we switch out NODE_BINARY
        // for ourselves which is what the react-native build script normally
        // invokes.  Because we export __SENTRY_RN_WRAP_XCODE_CALL=1, the main
        // sentry-cli script will invoke our wrap_call() function below.
        //
        // That will then attempt to figure out that a react-native bundle is
        // happening to the build script, parse out the arguments, add additional
        // arguments if needed and then report the parsed arguments to a temporary
        // JSON file we load back below.
        //
        // With that we we then have all the information we need to invoke the
        // upload process.
        } else {
            let rv = process::Command::new(&script)
                .env("NODE_BINARY", env::current_exe()?.to_str().unwrap())
                .env("SENTRY_RN_REAL_NODE_BINARY", &node)
                .env(
                    "SENTRY_RN_SOURCEMAP_REPORT",
                    report_file.path().to_str().unwrap(),
                )
                .env("__SENTRY_RN_WRAP_XCODE_CALL", "1")
                .spawn()?
                .wait()?;
            propagate_exit_status(rv);

            if !matches.is_present("force_foreground") {
                md.may_detach()?;
            }
            let mut f = fs::File::open(report_file.path())?;
            let report: SourceMapReport = serde_json::from_reader(&mut f).unwrap_or_else(|_| {
                let format_err = format!(
                    "File {} doesn't contain a valid JSON data.",
                    report_file.path().display()
                );
                panic!("{}", format_err);
            });
            if report.bundle_path.is_none() || report.sourcemap_path.is_none() {
                println!("Warning: build produced no sourcemaps.");
                return Ok(());
            }

            bundle_path = report.bundle_path.unwrap();
            bundle_url = format!("~/{}", bundle_path.file_name().unwrap().to_string_lossy());
            sourcemap_path = report.sourcemap_path.unwrap();
            sourcemap_url = format!(
                "~/{}",
                sourcemap_path.file_name().unwrap().to_string_lossy()
            );
        }

        // now that we have all the data, we can now process and upload the
        // sourcemaps.
        println!("Processing react-native sourcemaps for Sentry upload.");
        info!("  bundle path: {}", bundle_path.display());
        info!("  sourcemap path: {}", sourcemap_path.display());

        let mut processor = SourceMapProcessor::new();
        processor.add(&bundle_url, ReleaseFileSearch::collect_file(bundle_path)?)?;
        processor.add(
            &sourcemap_url,
            ReleaseFileSearch::collect_file(sourcemap_path)?,
        )?;
        processor.rewrite(&[base.parent().unwrap().to_str().unwrap()])?;
        processor.add_sourcemap_references()?;

        let dist = env::var("SENTRY_DIST").unwrap_or_else(|_| plist.build().to_string());
        let release_name = env::var("SENTRY_RELEASE").unwrap_or(format!(
            "{}@{}+{}",
            plist.bundle_id(),
            plist.version(),
            dist
        ));

        let api = Api::current();
        let release = api.new_release(
            &org,
            &NewRelease {
                version: release_name,
                projects: vec![project.to_string()],
                ..Default::default()
            },
        )?;

        match matches.values_of("dist") {
            None => {
                processor.upload(&UploadContext {
                    org: &org,
                    project: Some(&project),
                    release: &release.version,
                    dist: Some(&dist),
                    wait: matches.is_present("wait"),
                })?;
            }
            Some(dists) => {
                for dist in dists {
                    processor.upload(&UploadContext {
                        org: &org,
                        project: Some(&project),
                        release: &release.version,
                        dist: Some(dist),
                        wait: matches.is_present("wait"),
                    })?;
                }
            }
        }

        Ok(())
    })
}

pub fn wrap_call() -> Result<()> {
    let mut args: Vec<_> = env::args().skip(1).collect();
    let mut bundle_path = None;
    let mut sourcemap_path = None;

    if args.len() > 1 && (args[1] == "bundle" || args[1] == "ram-bundle") {
        let mut iter = args.iter().fuse();
        while let Some(item) = iter.next() {
            if item == "--sourcemap-output" {
                sourcemap_path = iter.next().cloned();
            } else if let Some(rest) = item.strip_prefix("--sourcemap-output=") {
                sourcemap_path = Some(rest.to_string());
            } else if item == "--bundle-output" {
                bundle_path = iter.next().cloned();
            } else if let Some(rest) = item.strip_prefix("--bundle-output=") {
                bundle_path = Some(rest.to_string());
            }
        }
    }

    let mut sourcemap_report = SourceMapReport::default();

    if sourcemap_path.is_none() && bundle_path.is_some() {
        let mut path = env::temp_dir();
        let mut map_path = PathBuf::from(bundle_path.clone().unwrap());
        map_path.set_extension("jsbundle.map");
        path.push(map_path.file_name().unwrap());
        sourcemap_report.sourcemap_path = Some(PathBuf::from(&path));
        args.push("--sourcemap-output".into());
        args.push(path.into_os_string().into_string().unwrap());
    } else if let Some(path) = sourcemap_path {
        sourcemap_report.sourcemap_path = Some(PathBuf::from(path));
    }

    sourcemap_report.bundle_path = bundle_path.map(PathBuf::from);

    let rv = process::Command::new(env::var("SENTRY_RN_REAL_NODE_BINARY").unwrap())
        .args(&args)
        .spawn()?
        .wait()?;
    propagate_exit_status(rv);

    let mut f = fs::File::create(env::var("SENTRY_RN_SOURCEMAP_REPORT").unwrap())?;
    serde_json::to_writer(&mut f, &sourcemap_report)?;

    Ok(())
}
<|endoftext|>


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

use fmt;
use from_str::from_str;
use libc::exit;
use option::{Some, None, Option};
use io;
use io::stdio::StdWriter;
use io::buffered::LineBufferedWriter;
use rt::crate_map::{ModEntry, CrateMap, iter_crate_map, get_crate_map};
use str::StrSlice;
use vec::ImmutableVector;
#[cfg(test)] use cast::transmute;

struct LogDirective {
    name: Option<~str>,
    level: u32
}

static MAX_LOG_LEVEL: u32 = 255;
static DEFAULT_LOG_LEVEL: u32 = 1;
static log_level_names : &'static[&'static str] = &'static["error", "warn", "info", "debug"];

/// Parse an individual log level that is either a number or a symbolic log level
fn parse_log_level(level: &str) -> Option<u32> {
    let num = from_str::<u32>(level);
    let mut log_level;
    match num {
        Some(num) => {
            if num < MAX_LOG_LEVEL {
                log_level = Some(num);
            } else {
                log_level = Some(MAX_LOG_LEVEL);
            }
        }
        _ => {
            let position = log_level_names.iter().position(|&name| name == level);
            match position {
                Some(position) => {
                    log_level = Some(::cmp::min(MAX_LOG_LEVEL, (position + 1) as u32))
                },
                _ => {
                    log_level = None;
                }
            }
        }
    }
    log_level
}

/// Parse a logging specification string (e.g: "crate1,crate2::mod3,crate3::x=1")
/// and return a vector with log directives.
/// Valid log levels are 0-255, with the most likely ones being 1-4 (defined in std::).
/// Also supports string log levels of error, warn, info, and debug
fn parse_logging_spec(spec: ~str) -> ~[LogDirective]{
    let mut dirs = ~[];
    for s in spec.split(',') {
        let parts: ~[&str] = s.split('=').collect();
        let mut log_level;
        let mut name = Some(parts[0].to_owned());
        match parts.len() {
            1 => {
                //if the single argument is a log-level string or number,
                //treat that as a global fallback
                let possible_log_level = parse_log_level(parts[0]);
                match possible_log_level {
                    Some(num) => {
                        name = None;
                        log_level = num;
                    },
                    _ => {
                        log_level = MAX_LOG_LEVEL
                    }
                }
            }
            2 => {
                let possible_log_level = parse_log_level(parts[1]);
                match possible_log_level {
                    Some(num) => {
                        log_level = num;
                    },
                    _ => {
                        rterrln!("warning: invalid logging spec '{}', \
                                  ignoring it", parts[1]);
                        continue
                    }
                }
            },
            _ => {
                rterrln!("warning: invalid logging spec '{}', \
                          ignoring it", s);
                continue
            }
        }
        let dir = LogDirective {name: name, level: log_level};
        dirs.push(dir);
    }
    return dirs;
}

/// Set the log level of an entry in the crate map depending on the vector
/// of log directives
fn update_entry(dirs: &[LogDirective], entry: &ModEntry) -> u32 {
    let mut new_lvl: u32 = DEFAULT_LOG_LEVEL;
    let mut longest_match = -1i;
    for dir in dirs.iter() {
        match dir.name {
            None => {
                if longest_match == -1 {
                    longest_match = 0;
                    new_lvl = dir.level;
                }
            }
            Some(ref dir_name) => {
                let name = entry.name;
                let len = dir_name.len() as int;
                if name.starts_with(*dir_name) &&
                    len >= longest_match {
                    longest_match = len;
                    new_lvl = dir.level;
                }
            }
        };
    }
    unsafe { *entry.log_level = new_lvl; }
    if longest_match >= 0 { return 1; } else { return 0; }
}

/// Set log level for every entry in crate_map according to the sepecification
/// in settings
fn update_log_settings(crate_map: &CrateMap, settings: ~str) {
    let mut dirs = ~[];
    if settings.len() > 0 {
        if settings == ~"::help" || settings == ~"?" {
            rterrln!("\nCrate log map:\n");
            iter_crate_map(crate_map, |entry| rterrln!(" {}", entry.name));
            unsafe { exit(1); }
        }
        dirs = parse_logging_spec(settings);
    }

    let mut n_matches: u32 = 0;
    iter_crate_map(crate_map, |entry| {
        let m = update_entry(dirs, entry);
        n_matches += m;
    });

    if n_matches < (dirs.len() as u32) {
        rterrln!("warning: got {} RUST_LOG specs but only matched\n\
                  {} of them. You may have mistyped a RUST_LOG spec. \n\
                  Use RUST_LOG=::help to see the list of crates and modules.\n",
                 dirs.len(), n_matches);
    }
}

pub trait Logger {
    fn log(&mut self, args: &fmt::Arguments);
}

/// This logger emits output to the stderr of the process, and contains a lazily
/// initialized event-loop driven handle to the stream.
pub struct StdErrLogger {
    priv handle: LineBufferedWriter<StdWriter>,
}

impl StdErrLogger {
    pub fn new() -> StdErrLogger {
        StdErrLogger { handle: LineBufferedWriter::new(io::stderr()) }
    }
}

impl Logger for StdErrLogger {
    fn log(&mut self, args: &fmt::Arguments) {
        fmt::writeln(&mut self.handle as &mut io::Writer, args);
    }
}

/// Configure logging by traversing the crate map and setting the
/// per-module global logging flags based on the logging spec
pub fn init() {
    use os;

    let log_spec = os::getenv("RUST_LOG");
    match get_crate_map() {
        Some(crate_map) => {
            match log_spec {
                Some(spec) => {
                    update_log_settings(crate_map, spec);
                }
                None => {
                    update_log_settings(crate_map, ~"");
                }
            }
        },
        _ => {
            match log_spec {
                Some(_) => {
                    rterrln!("warning: RUST_LOG set, but no crate map found.");
                },
                None => {}
            }
        }
    }
}

// Tests for parse_logging_spec()
#[test]
fn parse_logging_spec_valid() {
    let dirs = parse_logging_spec(~"crate1::mod1=1,crate1::mod2,crate2=4");
    assert_eq!(dirs.len(), 3);
    assert!(dirs[0].name == Some(~"crate1::mod1"));
    assert_eq!(dirs[0].level, 1);

    assert!(dirs[1].name == Some(~"crate1::mod2"));
    assert_eq!(dirs[1].level, MAX_LOG_LEVEL);

    assert!(dirs[2].name == Some(~"crate2"));
    assert_eq!(dirs[2].level, 4);
}

#[test]
fn parse_logging_spec_invalid_crate() {
    // test parse_logging_spec with multiple = in specification
    let dirs = parse_logging_spec(~"crate1::mod1=1=2,crate2=4");
    assert_eq!(dirs.len(), 1);
    assert!(dirs[0].name == Some(~"crate2"));
    assert_eq!(dirs[0].level, 4);
}

#[test]
fn parse_logging_spec_invalid_log_level() {
    // test parse_logging_spec with 'noNumber' as log level
    let dirs = parse_logging_spec(~"crate1::mod1=noNumber,crate2=4");
    assert_eq!(dirs.len(), 1);
    assert!(dirs[0].name == Some(~"crate2"));
    assert_eq!(dirs[0].level, 4);
}

#[test]
fn parse_logging_spec_string_log_level() {
    // test parse_logging_spec with 'warn' as log level
    let dirs = parse_logging_spec(~"crate1::mod1=wrong,crate2=warn");
    assert_eq!(dirs.len(), 1);
    assert!(dirs[0].name == Some(~"crate2"));
    assert_eq!(dirs[0].level, 2);
}

#[test]
fn parse_logging_spec_global() {
    // test parse_logging_spec with no crate
    let dirs = parse_logging_spec(~"warn,crate2=4");
    assert_eq!(dirs.len(), 2);
    assert!(dirs[0].name == None);
    assert_eq!(dirs[0].level, 2);
    assert!(dirs[1].name == Some(~"crate2"));
    assert_eq!(dirs[1].level, 4);
}

// Tests for update_entry
#[test]
fn update_entry_match_full_path() {
    let dirs = ~[LogDirective {name: Some(~"crate1::mod1"), level: 2 },
                 LogDirective {name: Some(~"crate2"), level: 3}];
    let level = &mut 0;
    unsafe {
        let entry= &ModEntry {name:"crate1::mod1", log_level: level};
        let m = update_entry(dirs, transmute(entry));
        assert!(*entry.log_level == 2);
        assert!(m == 1);
    }
}

#[test]
fn update_entry_no_match() {
    let dirs = ~[LogDirective {name: Some(~"crate1::mod1"), level: 2 },
                 LogDirective {name: Some(~"crate2"), level: 3}];
    let level = &mut 0;
    unsafe {
        let entry= &ModEntry {name: "crate3::mod1", log_level: level};
        let m = update_entry(dirs, transmute(entry));
        assert!(*entry.log_level == DEFAULT_LOG_LEVEL);
        assert!(m == 0);
    }
}

#[test]
fn update_entry_match_beginning() {
    let dirs = ~[LogDirective {name: Some(~"crate1::mod1"), level: 2 },
                 LogDirective {name: Some(~"crate2"), level: 3}];
    let level = &mut 0;
    unsafe {
        let entry= &ModEntry {name: "crate2::mod1", log_level: level};
        let m = update_entry(dirs, transmute(entry));
        assert!(*entry.log_level == 3);
        assert!(m == 1);
    }
}

#[test]
fn update_entry_match_beginning_longest_match() {
    let dirs = ~[LogDirective {name: Some(~"crate1::mod1"), level: 2 },
                 LogDirective {name: Some(~"crate2"), level: 3},
                 LogDirective {name: Some(~"crate2::mod"), level: 4}];
    let level = &mut 0;
    unsafe {
        let entry = &ModEntry {name: "crate2::mod1", log_level: level};
        let m = update_entry(dirs, transmute(entry));
        assert!(*entry.log_level == 4);
        assert!(m == 1);
    }
}

#[test]
fn update_entry_match_default() {
    let dirs = ~[LogDirective {name: Some(~"crate1::mod1"), level: 2 },
                 LogDirective {name: None, level: 3}
                ];
    let level = &mut 0;
    unsafe {
        let entry= &ModEntry {name: "crate1::mod1", log_level: level};
        let m = update_entry(dirs, transmute(entry));
        assert!(*entry.log_level == 2);
        assert!(m == 1);
        let entry= &ModEntry {name: "crate2::mod2", log_level: level};
        let m = update_entry(dirs, transmute(entry));
        assert!(*entry.log_level == 3);
        assert!(m == 1);
    }
}
<commit_msg>std: print RUST_LOG=::help in sorted order.<commit_after>// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use fmt;
use from_str::from_str;
use libc::exit;
use option::{Some, None, Option};
use io;
use io::stdio::StdWriter;
use io::buffered::LineBufferedWriter;
use rt::crate_map::{ModEntry, CrateMap, iter_crate_map, get_crate_map};
use str::StrSlice;
use vec::{ImmutableVector, MutableTotalOrdVector};
#[cfg(test)] use cast::transmute;

struct LogDirective {
    name: Option<~str>,
    level: u32
}

static MAX_LOG_LEVEL: u32 = 255;
static DEFAULT_LOG_LEVEL: u32 = 1;
static log_level_names : &'static[&'static str] = &'static["error", "warn", "info", "debug"];

/// Parse an individual log level that is either a number or a symbolic log level
fn parse_log_level(level: &str) -> Option<u32> {
    let num = from_str::<u32>(level);
    let mut log_level;
    match num {
        Some(num) => {
            if num < MAX_LOG_LEVEL {
                log_level = Some(num);
            } else {
                log_level = Some(MAX_LOG_LEVEL);
            }
        }
        _ => {
            let position = log_level_names.iter().position(|&name| name == level);
            match position {
                Some(position) => {
                    log_level = Some(::cmp::min(MAX_LOG_LEVEL, (position + 1) as u32))
                },
                _ => {
                    log_level = None;
                }
            }
        }
    }
    log_level
}

/// Parse a logging specification string (e.g: "crate1,crate2::mod3,crate3::x=1")
/// and return a vector with log directives.
/// Valid log levels are 0-255, with the most likely ones being 1-4 (defined in std::).
/// Also supports string log levels of error, warn, info, and debug
fn parse_logging_spec(spec: ~str) -> ~[LogDirective]{
    let mut dirs = ~[];
    for s in spec.split(',') {
        let parts: ~[&str] = s.split('=').collect();
        let mut log_level;
        let mut name = Some(parts[0].to_owned());
        match parts.len() {
            1 => {
                //if the single argument is a log-level string or number,
                //treat that as a global fallback
                let possible_log_level = parse_log_level(parts[0]);
                match possible_log_level {
                    Some(num) => {
                        name = None;
                        log_level = num;
                    },
                    _ => {
                        log_level = MAX_LOG_LEVEL
                    }
                }
            }
            2 => {
                let possible_log_level = parse_log_level(parts[1]);
                match possible_log_level {
                    Some(num) => {
                        log_level = num;
                    },
                    _ => {
                        rterrln!("warning: invalid logging spec '{}', \
                                  ignoring it", parts[1]);
                        continue
                    }
                }
            },
            _ => {
                rterrln!("warning: invalid logging spec '{}', \
                          ignoring it", s);
                continue
            }
        }
        let dir = LogDirective {name: name, level: log_level};
        dirs.push(dir);
    }
    return dirs;
}

/// Set the log level of an entry in the crate map depending on the vector
/// of log directives
fn update_entry(dirs: &[LogDirective], entry: &ModEntry) -> u32 {
    let mut new_lvl: u32 = DEFAULT_LOG_LEVEL;
    let mut longest_match = -1i;
    for dir in dirs.iter() {
        match dir.name {
            None => {
                if longest_match == -1 {
                    longest_match = 0;
                    new_lvl = dir.level;
                }
            }
            Some(ref dir_name) => {
                let name = entry.name;
                let len = dir_name.len() as int;
                if name.starts_with(*dir_name) &&
                    len >= longest_match {
                    longest_match = len;
                    new_lvl = dir.level;
                }
            }
        };
    }
    unsafe { *entry.log_level = new_lvl; }
    if longest_match >= 0 { return 1; } else { return 0; }
}

/// Set log level for every entry in crate_map according to the sepecification
/// in settings
fn update_log_settings(crate_map: &CrateMap, settings: ~str) {
    let mut dirs = ~[];
    if settings.len() > 0 {
        if settings == ~"::help" || settings == ~"?" {
            rterrln!("\nCrate log map:\n");

            let mut entries = ~[];
            iter_crate_map(crate_map, |entry| entries.push(entry.name.to_owned()));
            entries.sort();

            for name in entries.iter() {
                rterrln!(" {}", *name);
            }
            unsafe { exit(1); }
        }
        dirs = parse_logging_spec(settings);
    }

    let mut n_matches: u32 = 0;
    iter_crate_map(crate_map, |entry| {
        let m = update_entry(dirs, entry);
        n_matches += m;
    });

    if n_matches < (dirs.len() as u32) {
        rterrln!("warning: got {} RUST_LOG specs but only matched\n\
                  {} of them. You may have mistyped a RUST_LOG spec. \n\
                  Use RUST_LOG=::help to see the list of crates and modules.\n",
                 dirs.len(), n_matches);
    }
}

pub trait Logger {
    fn log(&mut self, args: &fmt::Arguments);
}

/// This logger emits output to the stderr of the process, and contains a lazily
/// initialized event-loop driven handle to the stream.
pub struct StdErrLogger {
    priv handle: LineBufferedWriter<StdWriter>,
}

impl StdErrLogger {
    pub fn new() -> StdErrLogger {
        StdErrLogger { handle: LineBufferedWriter::new(io::stderr()) }
    }
}

impl Logger for StdErrLogger {
    fn log(&mut self, args: &fmt::Arguments) {
        fmt::writeln(&mut self.handle as &mut io::Writer, args);
    }
}

/// Configure logging by traversing the crate map and setting the
/// per-module global logging flags based on the logging spec
pub fn init() {
    use os;

    let log_spec = os::getenv("RUST_LOG");
    match get_crate_map() {
        Some(crate_map) => {
            match log_spec {
                Some(spec) => {
                    update_log_settings(crate_map, spec);
                }
                None => {
                    update_log_settings(crate_map, ~"");
                }
            }
        },
        _ => {
            match log_spec {
                Some(_) => {
                    rterrln!("warning: RUST_LOG set, but no crate map found.");
                },
                None => {}
            }
        }
    }
}

// Tests for parse_logging_spec()
#[test]
fn parse_logging_spec_valid() {
    let dirs = parse_logging_spec(~"crate1::mod1=1,crate1::mod2,crate2=4");
    assert_eq!(dirs.len(), 3);
    assert!(dirs[0].name == Some(~"crate1::mod1"));
    assert_eq!(dirs[0].level, 1);

    assert!(dirs[1].name == Some(~"crate1::mod2"));
    assert_eq!(dirs[1].level, MAX_LOG_LEVEL);

    assert!(dirs[2].name == Some(~"crate2"));
    assert_eq!(dirs[2].level, 4);
}

#[test]
fn parse_logging_spec_invalid_crate() {
    // test parse_logging_spec with multiple = in specification
    let dirs = parse_logging_spec(~"crate1::mod1=1=2,crate2=4");
    assert_eq!(dirs.len(), 1);
    assert!(dirs[0].name == Some(~"crate2"));
    assert_eq!(dirs[0].level, 4);
}

#[test]
fn parse_logging_spec_invalid_log_level() {
    // test parse_logging_spec with 'noNumber' as log level
    let dirs = parse_logging_spec(~"crate1::mod1=noNumber,crate2=4");
    assert_eq!(dirs.len(), 1);
    assert!(dirs[0].name == Some(~"crate2"));
    assert_eq!(dirs[0].level, 4);
}

#[test]
fn parse_logging_spec_string_log_level() {
    // test parse_logging_spec with 'warn' as log level
    let dirs = parse_logging_spec(~"crate1::mod1=wrong,crate2=warn");
    assert_eq!(dirs.len(), 1);
    assert!(dirs[0].name == Some(~"crate2"));
    assert_eq!(dirs[0].level, 2);
}

#[test]
fn parse_logging_spec_global() {
    // test parse_logging_spec with no crate
    let dirs = parse_logging_spec(~"warn,crate2=4");
    assert_eq!(dirs.len(), 2);
    assert!(dirs[0].name == None);
    assert_eq!(dirs[0].level, 2);
    assert!(dirs[1].name == Some(~"crate2"));
    assert_eq!(dirs[1].level, 4);
}

// Tests for update_entry
#[test]
fn update_entry_match_full_path() {
    let dirs = ~[LogDirective {name: Some(~"crate1::mod1"), level: 2 },
                 LogDirective {name: Some(~"crate2"), level: 3}];
    let level = &mut 0;
    unsafe {
        let entry= &ModEntry {name:"crate1::mod1", log_level: level};
        let m = update_entry(dirs, transmute(entry));
        assert!(*entry.log_level == 2);
        assert!(m == 1);
    }
}

#[test]
fn update_entry_no_match() {
    let dirs = ~[LogDirective {name: Some(~"crate1::mod1"), level: 2 },
                 LogDirective {name: Some(~"crate2"), level: 3}];
    let level = &mut 0;
    unsafe {
        let entry= &ModEntry {name: "crate3::mod1", log_level: level};
        let m = update_entry(dirs, transmute(entry));
        assert!(*entry.log_level == DEFAULT_LOG_LEVEL);
        assert!(m == 0);
    }
}

#[test]
fn update_entry_match_beginning() {
    let dirs = ~[LogDirective {name: Some(~"crate1::mod1"), level: 2 },
                 LogDirective {name: Some(~"crate2"), level: 3}];
    let level = &mut 0;
    unsafe {
        let entry= &ModEntry {name: "crate2::mod1", log_level: level};
        let m = update_entry(dirs, transmute(entry));
        assert!(*entry.log_level == 3);
        assert!(m == 1);
    }
}

#[test]
fn update_entry_match_beginning_longest_match() {
    let dirs = ~[LogDirective {name: Some(~"crate1::mod1"), level: 2 },
                 LogDirective {name: Some(~"crate2"), level: 3},
                 LogDirective {name: Some(~"crate2::mod"), level: 4}];
    let level = &mut 0;
    unsafe {
        let entry = &ModEntry {name: "crate2::mod1", log_level: level};
        let m = update_entry(dirs, transmute(entry));
        assert!(*entry.log_level == 4);
        assert!(m == 1);
    }
}

#[test]
fn update_entry_match_default() {
    let dirs = ~[LogDirective {name: Some(~"crate1::mod1"), level: 2 },
                 LogDirective {name: None, level: 3}
                ];
    let level = &mut 0;
    unsafe {
        let entry= &ModEntry {name: "crate1::mod1", log_level: level};
        let m = update_entry(dirs, transmute(entry));
        assert!(*entry.log_level == 2);
        assert!(m == 1);
        let entry= &ModEntry {name: "crate2::mod2", log_level: level};
        let m = update_entry(dirs, transmute(entry));
        assert!(*entry.log_level == 3);
        assert!(m == 1);
    }
}
<|endoftext|>


<commit_before>//! This pass checks HIR bodies that may be evaluated at compile-time (e.g., `const`, `static`,
//! `const fn`) for structured control flow (e.g. `if`, `while`), which is forbidden in a const
//! context.
//!
//! By the time the MIR const-checker runs, these high-level constructs have been lowered to
//! control-flow primitives (e.g., `Goto`, `SwitchInt`), making it tough to properly attribute
//! errors. We still look for those primitives in the MIR const-checker to ensure nothing slips
//! through, but errors for structured control flow in a `const` should be emitted here.

use rustc::hir::def_id::DefId;
use rustc::hir::intravisit::{Visitor, NestedVisitorMap};
use rustc::hir::map::Map;
use rustc::hir;
use rustc::session::Session;
use rustc::ty::TyCtxt;
use rustc::ty::query::Providers;
use syntax::ast::Mutability;
use syntax::span_err;
use syntax_pos::Span;
use rustc_error_codes::*;

use std::fmt;

#[derive(Copy, Clone)]
enum ConstKind {
    Static,
    StaticMut,
    ConstFn,
    Const,
    AnonConst,
}

impl ConstKind {
    fn for_body(body: &hir::Body, hir_map: &Map<'_>) -> Option<Self> {
        let is_const_fn = |id| hir_map.fn_sig_by_hir_id(id).unwrap().header.is_const();

        let owner = hir_map.body_owner(body.id());
        let const_kind = match hir_map.body_owner_kind(owner) {
            hir::BodyOwnerKind::Const => Self::Const,
            hir::BodyOwnerKind::Static(Mutability::Mutable) => Self::StaticMut,
            hir::BodyOwnerKind::Static(Mutability::Immutable) => Self::Static,

            hir::BodyOwnerKind::Fn if is_const_fn(owner) => Self::ConstFn,
            hir::BodyOwnerKind::Fn | hir::BodyOwnerKind::Closure => return None,
        };

        Some(const_kind)
    }
}

impl fmt::Display for ConstKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let s = match self {
            Self::Static => "static",
            Self::StaticMut => "static mut",
            Self::Const | Self::AnonConst => "const",
            Self::ConstFn => "const fn",
        };

        write!(f, "{}", s)
    }
}

fn check_mod_const_bodies(tcx: TyCtxt<'_>, module_def_id: DefId) {
    let mut vis = CheckConstVisitor::new(tcx);
    tcx.hir().visit_item_likes_in_module(module_def_id, &mut vis.as_deep_visitor());
}

pub(crate) fn provide(providers: &mut Providers<'_>) {
    *providers = Providers {
        check_mod_const_bodies,
        ..*providers
    };
}

#[derive(Copy, Clone)]
struct CheckConstVisitor<'tcx> {
    sess: &'tcx Session,
    hir_map: &'tcx Map<'tcx>,
    const_kind: Option<ConstKind>,
}

impl<'tcx> CheckConstVisitor<'tcx> {
    fn new(tcx: TyCtxt<'tcx>) -> Self {
        CheckConstVisitor {
            sess: &tcx.sess,
            hir_map: tcx.hir(),
            const_kind: None,
        }
    }

    /// Emits an error when an unsupported expression is found in a const context.
    fn const_check_violated(&self, bad_op: &str, span: Span) {
        if self.sess.opts.debugging_opts.unleash_the_miri_inside_of_you {
            self.sess.span_warn(span, "skipping const checks");
            return;
        }

        let const_kind = self.const_kind
            .expect("`const_check_violated` may only be called inside a const context");

        span_err!(self.sess, span, E0744, "`{}` is not allowed in a `{}`", bad_op, const_kind);
    }

    /// Saves the parent `const_kind` before calling `f` and restores it afterwards.
    fn recurse_into(&mut self, kind: Option<ConstKind>, f: impl FnOnce(&mut Self)) {
        let parent_kind = self.const_kind;
        self.const_kind = kind;
        f(self);
        self.const_kind = parent_kind;
    }
}

impl<'tcx> Visitor<'tcx> for CheckConstVisitor<'tcx> {
    fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
        NestedVisitorMap::OnlyBodies(&self.hir_map)
    }

    fn visit_anon_const(&mut self, anon: &'tcx hir::AnonConst) {
        let kind = Some(ConstKind::AnonConst);
        self.recurse_into(kind, |this| hir::intravisit::walk_anon_const(this, anon));
    }

    fn visit_body(&mut self, body: &'tcx hir::Body) {
        let kind = ConstKind::for_body(body, self.hir_map);
        self.recurse_into(kind, |this| hir::intravisit::walk_body(this, body));
    }

    fn visit_expr(&mut self, e: &'tcx hir::Expr) {
        match &e.kind {
            // Skip the following checks if we are not currently in a const context.
            _ if self.const_kind.is_none() => {}

            hir::ExprKind::Loop(_, _, source) => {
                self.const_check_violated(source.name(), e.span);
            }

            hir::ExprKind::Match(_, _, source) if !self.tcx.features().const_if_match => {
                use hir::MatchSource::*;

                let op = match source {
                    Normal => Some("match"),
                    IfDesugar { .. } | IfLetDesugar { .. } => Some("if"),
                    TryDesugar => Some("?"),
                    AwaitDesugar => Some(".await"),

                    // These are handled by `ExprKind::Loop` above.
                    WhileDesugar | WhileLetDesugar | ForLoopDesugar => None,
                };

                if let Some(op) = op {
                    self.const_check_violated(op, e.span);
                }
            }

            _ => {},
        }

        hir::intravisit::walk_expr(self, e);
    }
}
<commit_msg>Hold a `TyCtxt` in the HIR const-checker<commit_after>//! This pass checks HIR bodies that may be evaluated at compile-time (e.g., `const`, `static`,
//! `const fn`) for structured control flow (e.g. `if`, `while`), which is forbidden in a const
//! context.
//!
//! By the time the MIR const-checker runs, these high-level constructs have been lowered to
//! control-flow primitives (e.g., `Goto`, `SwitchInt`), making it tough to properly attribute
//! errors. We still look for those primitives in the MIR const-checker to ensure nothing slips
//! through, but errors for structured control flow in a `const` should be emitted here.

use rustc::hir::def_id::DefId;
use rustc::hir::intravisit::{Visitor, NestedVisitorMap};
use rustc::hir::map::Map;
use rustc::hir;
use rustc::ty::TyCtxt;
use rustc::ty::query::Providers;
use syntax::ast::Mutability;
use syntax::span_err;
use syntax_pos::Span;
use rustc_error_codes::*;

use std::fmt;

#[derive(Copy, Clone)]
enum ConstKind {
    Static,
    StaticMut,
    ConstFn,
    Const,
    AnonConst,
}

impl ConstKind {
    fn for_body(body: &hir::Body, hir_map: &Map<'_>) -> Option<Self> {
        let is_const_fn = |id| hir_map.fn_sig_by_hir_id(id).unwrap().header.is_const();

        let owner = hir_map.body_owner(body.id());
        let const_kind = match hir_map.body_owner_kind(owner) {
            hir::BodyOwnerKind::Const => Self::Const,
            hir::BodyOwnerKind::Static(Mutability::Mutable) => Self::StaticMut,
            hir::BodyOwnerKind::Static(Mutability::Immutable) => Self::Static,

            hir::BodyOwnerKind::Fn if is_const_fn(owner) => Self::ConstFn,
            hir::BodyOwnerKind::Fn | hir::BodyOwnerKind::Closure => return None,
        };

        Some(const_kind)
    }
}

impl fmt::Display for ConstKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let s = match self {
            Self::Static => "static",
            Self::StaticMut => "static mut",
            Self::Const | Self::AnonConst => "const",
            Self::ConstFn => "const fn",
        };

        write!(f, "{}", s)
    }
}

fn check_mod_const_bodies(tcx: TyCtxt<'_>, module_def_id: DefId) {
    let mut vis = CheckConstVisitor::new(tcx);
    tcx.hir().visit_item_likes_in_module(module_def_id, &mut vis.as_deep_visitor());
}

pub(crate) fn provide(providers: &mut Providers<'_>) {
    *providers = Providers {
        check_mod_const_bodies,
        ..*providers
    };
}

#[derive(Copy, Clone)]
struct CheckConstVisitor<'tcx> {
    tcx: TyCtxt<'tcx>,
    const_kind: Option<ConstKind>,
}

impl<'tcx> CheckConstVisitor<'tcx> {
    fn new(tcx: TyCtxt<'tcx>) -> Self {
        CheckConstVisitor {
            tcx,
            const_kind: None,
        }
    }

    /// Emits an error when an unsupported expression is found in a const context.
    fn const_check_violated(&self, bad_op: &str, span: Span) {
        if self.tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you {
            self.tcx.sess.span_warn(span, "skipping const checks");
            return;
        }

        let const_kind = self.const_kind
            .expect("`const_check_violated` may only be called inside a const context");

        span_err!(self.tcx.sess, span, E0744, "`{}` is not allowed in a `{}`", bad_op, const_kind);
    }

    /// Saves the parent `const_kind` before calling `f` and restores it afterwards.
    fn recurse_into(&mut self, kind: Option<ConstKind>, f: impl FnOnce(&mut Self)) {
        let parent_kind = self.const_kind;
        self.const_kind = kind;
        f(self);
        self.const_kind = parent_kind;
    }
}

impl<'tcx> Visitor<'tcx> for CheckConstVisitor<'tcx> {
    fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
        NestedVisitorMap::OnlyBodies(&self.tcx.hir())
    }

    fn visit_anon_const(&mut self, anon: &'tcx hir::AnonConst) {
        let kind = Some(ConstKind::AnonConst);
        self.recurse_into(kind, |this| hir::intravisit::walk_anon_const(this, anon));
    }

    fn visit_body(&mut self, body: &'tcx hir::Body) {
        let kind = ConstKind::for_body(body, self.tcx.hir());
        self.recurse_into(kind, |this| hir::intravisit::walk_body(this, body));
    }

    fn visit_expr(&mut self, e: &'tcx hir::Expr) {
        match &e.kind {
            // Skip the following checks if we are not currently in a const context.
            _ if self.const_kind.is_none() => {}

            hir::ExprKind::Loop(_, _, source) => {
                self.const_check_violated(source.name(), e.span);
            }

            hir::ExprKind::Match(_, _, source) if !self.tcx.features().const_if_match => {
                use hir::MatchSource::*;

                let op = match source {
                    Normal => Some("match"),
                    IfDesugar { .. } | IfLetDesugar { .. } => Some("if"),
                    TryDesugar => Some("?"),
                    AwaitDesugar => Some(".await"),

                    // These are handled by `ExprKind::Loop` above.
                    WhileDesugar | WhileLetDesugar | ForLoopDesugar => None,
                };

                if let Some(op) = op {
                    self.const_check_violated(op, e.span);
                }
            }

            _ => {},
        }

        hir::intravisit::walk_expr(self, e);
    }
}
<|endoftext|>