Skip to content

Commit

Permalink
Reject macro calls inside of #![crate_name]
Browse files Browse the repository at this point in the history
  • Loading branch information
fmease committed Jul 31, 2024
1 parent 14e180e commit d169eee
Show file tree
Hide file tree
Showing 21 changed files with 194 additions and 116 deletions.
10 changes: 5 additions & 5 deletions compiler/rustc_driver_impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -748,11 +748,12 @@ fn print_crate_info(
return Compilation::Continue;
};
let t_outputs = rustc_interface::util::build_output_filenames(attrs, sess);
let id = rustc_session::output::find_crate_name(sess, attrs);
let crate_name = passes::get_crate_name(sess, attrs);
let crate_types = collect_crate_types(sess, attrs);
for &style in &crate_types {
let fname =
rustc_session::output::filename_for_input(sess, style, id, &t_outputs);
let fname = rustc_session::output::filename_for_input(
sess, style, crate_name, &t_outputs,
);
println_info!("{}", fname.as_path().file_name().unwrap().to_string_lossy());
}
}
Expand All @@ -761,8 +762,7 @@ fn print_crate_info(
// no crate attributes, print out an error and exit
return Compilation::Continue;
};
let id = rustc_session::output::find_crate_name(sess, attrs);
println_info!("{id}");
println_info!("{}", passes::get_crate_name(sess, attrs));
}
Cfg => {
let mut cfgs = sess
Expand Down
12 changes: 6 additions & 6 deletions compiler/rustc_expand/src/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,12 +180,12 @@ fn mod_file_path_from_attr(
let first_path = attrs.iter().find(|at| at.has_name(sym::path))?;
let Some(path_sym) = first_path.value_str() else {
// This check is here mainly to catch attempting to use a macro,
// such as #[path = concat!(...)]. This isn't currently supported
// because otherwise the InvocationCollector would need to defer
// loading a module until the #[path] attribute was expanded, and
// it doesn't support that (and would likely add a bit of
// complexity). Usually bad forms are checked in AstValidator (via
// `check_builtin_attribute`), but by the time that runs the macro
// such as `#[path = concat!(...)]`. This isn't supported because
// otherwise the `InvocationCollector` would need to defer loading
// a module until the `#[path]` attribute was expanded, and it
// doesn't support that (and would likely add a bit of complexity).
// Usually bad forms are checked during semantic analysis via
// `TyCtxt::check_mod_attrs`), but by the time that runs the macro
// is expanded, and it doesn't give an error.
validate_attr::emit_fatal_malformed_builtin_attribute(&sess.psess, first_path, sym::path);
};
Expand Down
13 changes: 8 additions & 5 deletions compiler/rustc_incremental/src/persist/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,9 @@ use rustc_errors::ErrorGuaranteed;
use rustc_fs_util::{link_or_copy, try_canonicalize, LinkOrCopy};
use rustc_middle::bug;
use rustc_session::config::CrateType;
use rustc_session::output::{collect_crate_types, find_crate_name};
use rustc_session::output::collect_crate_types;
use rustc_session::{Session, StableCrateId};
use rustc_span::Symbol;
use tracing::debug;

use crate::errors;
Expand Down Expand Up @@ -212,7 +213,10 @@ pub fn in_incr_comp_dir(incr_comp_session_dir: &Path, file_name: &str) -> PathBu
/// The garbage collection will take care of it.
///
/// [`rustc_interface::queries::dep_graph`]: ../../rustc_interface/struct.Queries.html#structfield.dep_graph
pub(crate) fn prepare_session_directory(sess: &Session) -> Result<(), ErrorGuaranteed> {
pub(crate) fn prepare_session_directory(
sess: &Session,
crate_name: Symbol,
) -> Result<(), ErrorGuaranteed> {
if sess.opts.incremental.is_none() {
return Ok(());
}
Expand All @@ -222,7 +226,7 @@ pub(crate) fn prepare_session_directory(sess: &Session) -> Result<(), ErrorGuara
debug!("prepare_session_directory");

// {incr-comp-dir}/{crate-name-and-disambiguator}
let crate_dir = crate_path(sess);
let crate_dir = crate_path(sess, crate_name);
debug!("crate-dir: {}", crate_dir.display());
create_dir(sess, &crate_dir, "crate")?;

Expand Down Expand Up @@ -605,10 +609,9 @@ fn string_to_timestamp(s: &str) -> Result<SystemTime, &'static str> {
Ok(UNIX_EPOCH + duration)
}

fn crate_path(sess: &Session) -> PathBuf {
fn crate_path(sess: &Session, crate_name: Symbol) -> PathBuf {
let incr_dir = sess.opts.incremental.as_ref().unwrap().clone();

let crate_name = find_crate_name(sess, &[]);
let crate_types = collect_crate_types(sess, &[]);
let stable_crate_id = StableCrateId::new(
crate_name,
Expand Down
6 changes: 3 additions & 3 deletions compiler/rustc_incremental/src/persist/load.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use rustc_serialize::opaque::MemDecoder;
use rustc_serialize::Decodable;
use rustc_session::config::IncrementalStateAssertion;
use rustc_session::Session;
use rustc_span::ErrorGuaranteed;
use rustc_span::{ErrorGuaranteed, Symbol};
use tracing::{debug, warn};

use super::data::*;
Expand Down Expand Up @@ -204,9 +204,9 @@ pub fn load_query_result_cache(sess: &Session) -> Option<OnDiskCache<'_>> {

/// Setups the dependency graph by loading an existing graph from disk and set up streaming of a
/// new graph to an incremental session directory.
pub fn setup_dep_graph(sess: &Session) -> Result<DepGraph, ErrorGuaranteed> {
pub fn setup_dep_graph(sess: &Session, crate_name: Symbol) -> Result<DepGraph, ErrorGuaranteed> {
// `load_dep_graph` can only be called after `prepare_session_directory`.
prepare_session_directory(sess)?;
prepare_session_directory(sess, crate_name)?;

let res = sess.opts.build_dep_graph().then(|| load_dep_graph(sess));

Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_interface/messages.ftl
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
interface_cant_emit_mir =
could not emit MIR: {$error}
interface_crate_name_does_not_match = `--crate-name` and `#[crate_name]` are required to match, but `{$crate_name}` != `{$attr_crate_name}`
interface_crate_name_invalid = crate names cannot start with a `-`, but `{$crate_name}` has a leading hyphen
interface_emoji_identifier =
identifiers cannot contain emoji: `{$ident}`
Expand Down
15 changes: 15 additions & 0 deletions compiler/rustc_interface/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ use std::path::Path;
use rustc_macros::Diagnostic;
use rustc_span::{Span, Symbol};

#[derive(Diagnostic)]
#[diag(interface_crate_name_does_not_match)]
pub(crate) struct CrateNameDoesNotMatch {
#[primary_span]
pub(crate) span: Span,
pub(crate) crate_name: Symbol,
pub(crate) attr_crate_name: Symbol,
}

#[derive(Diagnostic)]
#[diag(interface_crate_name_invalid)]
pub(crate) struct CrateNameInvalid<'a> {
pub(crate) crate_name: &'a str,
}

#[derive(Diagnostic)]
#[diag(interface_ferris_identifier)]
pub struct FerrisIdentifier {
Expand Down
102 changes: 80 additions & 22 deletions compiler/rustc_interface/src/passes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,10 @@ use rustc_resolve::Resolver;
use rustc_session::code_stats::VTableSizeInfo;
use rustc_session::config::{CrateType, Input, OutFileName, OutputFilenames, OutputType};
use rustc_session::cstore::Untracked;
use rustc_session::output::{collect_crate_types, filename_for_input, find_crate_name};
use rustc_session::output::{collect_crate_types, filename_for_input};
use rustc_session::search_paths::PathKind;
use rustc_session::{Limit, Session};
use rustc_span::symbol::{sym, Symbol};
use rustc_span::FileName;
use rustc_span::{sym, FileName, Span, Symbol};
use rustc_target::spec::PanicStrategy;
use rustc_trait_selection::traits;
use tracing::{info, instrument};
Expand Down Expand Up @@ -663,8 +662,7 @@ pub(crate) fn create_global_ctxt<'tcx>(

let pre_configured_attrs = rustc_expand::config::pre_configure_attrs(sess, &krate.attrs);

// parse `#[crate_name]` even if `--crate-name` was passed, to make sure it matches.
let crate_name = find_crate_name(sess, &pre_configured_attrs);
let crate_name = get_crate_name(sess, &pre_configured_attrs);
let crate_types = collect_crate_types(sess, &pre_configured_attrs);
let stable_crate_id = StableCrateId::new(
crate_name,
Expand All @@ -673,7 +671,7 @@ pub(crate) fn create_global_ctxt<'tcx>(
sess.cfg_version,
);
let outputs = util::build_output_filenames(&pre_configured_attrs, sess);
let dep_graph = setup_dep_graph(sess)?;
let dep_graph = setup_dep_graph(sess, crate_name)?;

let cstore =
FreezeLock::new(Box::new(CStore::new(compiler.codegen_backend.metadata_loader())) as _);
Expand Down Expand Up @@ -1047,23 +1045,83 @@ pub(crate) fn start_codegen<'tcx>(
Ok(codegen)
}

fn get_recursion_limit(krate_attrs: &[ast::Attribute], sess: &Session) -> Limit {
if let Some(attr) = krate_attrs
.iter()
.find(|attr| attr.has_name(sym::recursion_limit) && attr.value_str().is_none())
/// Compute and validate the crate name.
pub fn get_crate_name(sess: &Session, krate_attrs: &[ast::Attribute]) -> Symbol {
// We unconditionally validate all `#![crate_name]`s even if a crate name was
// set on the command line via `--crate-name` which we prioritize over the
// crate attributes. We perform the validation here instead of later to ensure
// it gets run in all code paths requiring the crate name very early on.
// Namely, print requests (`--print`).
let attr_crate_name =
validate_and_find_value_str_builtin_attr(sym::crate_name, sess, krate_attrs);

let validate = |name, span| {
rustc_session::output::validate_crate_name(sess, name, span);
name
};

if let Some(crate_name) = &sess.opts.crate_name {
let crate_name = Symbol::intern(crate_name);
if let Some((attr_crate_name, span)) = attr_crate_name
&& attr_crate_name != crate_name
{
sess.dcx().emit_err(errors::CrateNameDoesNotMatch {
span,
crate_name,
attr_crate_name,
});
}
return validate(crate_name, None);
}

if let Some((crate_name, span)) = attr_crate_name {
return validate(crate_name, Some(span));
}

if let Input::File(ref path) = sess.io.input
&& let Some(file_stem) = path.file_stem().and_then(|s| s.to_str())
{
// This is here mainly to check for using a macro, such as
// #![recursion_limit = foo!()]. That is not supported since that
// would require expanding this while in the middle of expansion,
// which needs to know the limit before expanding. Otherwise,
// validation would normally be caught in AstValidator (via
// `check_builtin_attribute`), but by the time that runs the macro
// is expanded, and it doesn't give an error.
validate_attr::emit_fatal_malformed_builtin_attribute(
&sess.psess,
attr,
sym::recursion_limit,
);
if file_stem.starts_with('-') {
sess.dcx().emit_err(errors::CrateNameInvalid { crate_name: file_stem });
} else {
return validate(Symbol::intern(&file_stem.replace('-', "_")), None);
}
}

Symbol::intern("rust_out")
}

fn get_recursion_limit(krate_attrs: &[ast::Attribute], sess: &Session) -> Limit {
// We don't permit macro calls inside of the attribute (e.g., #![recursion_limit = `expand!()`])
// because that would require expanding this while in the middle of expansion, which needs to
// know the limit before expanding.
let _ = validate_and_find_value_str_builtin_attr(sym::recursion_limit, sess, krate_attrs);
rustc_middle::middle::limits::get_recursion_limit(krate_attrs, sess)
}

/// Validate *all* occurrences of the given "[value-str]" built-in attribute and return the first find.
///
/// This validator is intended for built-in attributes whose value needs to be known very early
/// during compilation (namely, before macro expansion) and it mainly exists to reject macro calls
/// inside of the attributes, such as in `#![name = expand!()]`. Normal attribute validation happens
/// during semantic analysis via [`TyCtxt::check_mod_attrs`] which happens *after* macro expansion
/// when such macro calls (here: `expand`) have already been expanded and we can no longer check for
/// their presence.
///
/// [value-str]: ast::Attribute::value_str
fn validate_and_find_value_str_builtin_attr(
name: Symbol,
sess: &Session,
krate_attrs: &[ast::Attribute],
) -> Option<(Symbol, Span)> {
let mut result = None;
// Validate *all* relevant attributes, not just the first occurrence.
for attr in ast::attr::filter_by_name(krate_attrs, name) {
let Some(value) = attr.value_str() else {
validate_attr::emit_fatal_malformed_builtin_attribute(&sess.psess, attr, name)
};
// Choose the first occurrence as our result.
result.get_or_insert((value, attr.span));
}
result
}
6 changes: 3 additions & 3 deletions compiler/rustc_interface/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -416,11 +416,11 @@ pub(crate) fn check_attr_crate_type(
}
} else {
// This is here mainly to check for using a macro, such as
// #![crate_type = foo!()]. That is not supported since the
// `#![crate_type = foo!()]`. That is not supported since the
// crate type needs to be known very early in compilation long
// before expansion. Otherwise, validation would normally be
// caught in AstValidator (via `check_builtin_attribute`), but
// by the time that runs the macro is expanded, and it doesn't
// caught during semantic analysis via `TyCtxt::check_mod_attrs`,
// but by the time that runs the macro is expanded, and it doesn't
// give an error.
validate_attr::emit_fatal_malformed_builtin_attribute(
&sess.psess,
Expand Down
4 changes: 0 additions & 4 deletions compiler/rustc_session/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,8 @@ session_cannot_mix_and_match_sanitizers = `-Zsanitizer={$first}` is incompatible
session_cli_feature_diagnostic_help =
add `-Zcrate-attr="feature({$feature})"` to the command-line options to enable
session_crate_name_does_not_match = `--crate-name` and `#[crate_name]` are required to match, but `{$crate_name}` != `{$attr_crate_name}`
session_crate_name_empty = crate name must not be empty
session_crate_name_invalid = crate names cannot start with a `-`, but `{$s}` has a leading hyphen
session_expr_parentheses_needed = parentheses are required to parse this as an expression
session_failed_to_create_profiler = failed to create profiler: {$err}
Expand Down
15 changes: 0 additions & 15 deletions compiler/rustc_session/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,21 +195,6 @@ pub(crate) struct FileWriteFail<'a> {
pub(crate) err: String,
}

#[derive(Diagnostic)]
#[diag(session_crate_name_does_not_match)]
pub(crate) struct CrateNameDoesNotMatch {
#[primary_span]
pub(crate) span: Span,
pub(crate) crate_name: Symbol,
pub(crate) attr_crate_name: Symbol,
}

#[derive(Diagnostic)]
#[diag(session_crate_name_invalid)]
pub(crate) struct CrateNameInvalid<'a> {
pub(crate) s: &'a str,
}

#[derive(Diagnostic)]
#[diag(session_crate_name_empty)]
pub(crate) struct CrateNameEmpty {
Expand Down
56 changes: 3 additions & 53 deletions compiler/rustc_session/src/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,13 @@

use std::path::Path;

use rustc_ast::{self as ast, attr};
use rustc_ast as ast;
use rustc_errors::FatalError;
use rustc_span::symbol::sym;
use rustc_span::{Span, Symbol};

use crate::config::{self, CrateType, Input, OutFileName, OutputFilenames, OutputType};
use crate::errors::{
self, CrateNameDoesNotMatch, CrateNameEmpty, CrateNameInvalid, FileIsNotWriteable,
InvalidCharacterInCrateName,
};
use crate::config::{self, CrateType, OutFileName, OutputFilenames, OutputType};
use crate::errors::{self, CrateNameEmpty, FileIsNotWriteable, InvalidCharacterInCrateName};
use crate::Session;

pub fn out_filename(
Expand Down Expand Up @@ -51,53 +48,6 @@ fn is_writeable(p: &Path) -> bool {
}
}

/// Find and [validate] the crate name.
///
/// [validate]: validate_crate_name
pub fn find_crate_name(sess: &Session, attrs: &[ast::Attribute]) -> Symbol {
let validate = |name, span| {
validate_crate_name(sess, name, span);
name
};

// Look in attributes 100% of the time to make sure the attribute is marked
// as used. After doing this, however, we still prioritize a crate name from
// the command line over one found in the #[crate_name] attribute. If we
// find both we ensure that they're the same later on as well.
let attr_crate_name =
attr::find_by_name(attrs, sym::crate_name).and_then(|at| at.value_str().map(|s| (at, s)));

if let Some(crate_name) = &sess.opts.crate_name {
let crate_name = Symbol::intern(crate_name);
if let Some((attr, attr_crate_name)) = attr_crate_name
&& attr_crate_name != crate_name
{
sess.dcx().emit_err(CrateNameDoesNotMatch {
span: attr.span,
crate_name,
attr_crate_name,
});
}
return validate(crate_name, None);
}

if let Some((attr, crate_name)) = attr_crate_name {
return validate(crate_name, Some(attr.span));
}

if let Input::File(ref path) = sess.io.input
&& let Some(s) = path.file_stem().and_then(|s| s.to_str())
{
if s.starts_with('-') {
sess.dcx().emit_err(CrateNameInvalid { s });
} else {
return validate(Symbol::intern(&s.replace('-', "_")), None);
}
}

Symbol::intern("rust_out")
}

/// Validate the given crate name.
///
/// Note that this validation is more permissive than identifier parsing. It considers
Expand Down
Loading

0 comments on commit d169eee

Please sign in to comment.