Skip to content

Commit 522c8f7

Browse files
authored
Rollup merge of #127581 - fmease:fix-crate_name-validation, r=bjorn3
Fix crate name validation Reject macro calls inside attribute `#![crate_name]` like in `#![crate_name = concat!("na", "me")]`. Prior to #117584, the result of the expansion (here: `"name"`) would actually be properly picked up by the compiler and used as the crate name. However since #117584 / on master, we extract the "value" (i.e., the *literal* string literal) of the `#![crate_name]` much earlier in the pipeline way before macro expansion and **skip**/**ignore** any `#![crate_name]`s "assigned to" a macro call. See also #122001. T-lang has ruled to reject `#![crate_name = MACRO!(...)]` outright very similar to other built-in attributes whose value we need early like `#![crate_type]`. See accepted FCP: #122001 (comment). Note that the check as implemented in this PR is even more "aggressive" compared to the one of `#![crate_type]` by running as early as possible in order to reject `#![crate_name = MACRO!(...)]` even in "non-normal" executions of `rustc`, namely on *print requests* (e.g., `--print=crate-name` and `--print=file-names`). If I were to move the validation step a bit further back close to the `#![crate_type]` one, `--print=crate-name` (etc.) would *not* exit fatally with an error in this kind of situation but happily report an incorrect crate name (i.e., the "crate name" as if `#![crate_name]` didn't exist / deduced from other sources like `--crate-name` or the file name) which would match the behavior on master. Again, see also #122001. I'm mentioning this explicitly because I'm not sure if it was that clear in the FCP'ed issue. I argue that my current approach is the most reasonable one. I know (from reading the code and from past experiments) that various print requests are still quite broken (mostly lack of validation). To the best of my knowledge, there's no print request whose output references/contains a crate *type*, so there's no "inherent need" to move `#![crate_type]`'s validation to happen earlier. --- Fixes #122001. https://github.com/rust-lang/rust/labels/relnotes: Compatibility. Breaking change.
2 parents 608e228 + 9b6fd35 commit 522c8f7

30 files changed

+230
-146
lines changed

Diff for: compiler/rustc_driver_impl/src/lib.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -667,11 +667,12 @@ fn print_crate_info(
667667
return Compilation::Continue;
668668
};
669669
let t_outputs = rustc_interface::util::build_output_filenames(attrs, sess);
670-
let id = rustc_session::output::find_crate_name(sess, attrs);
670+
let crate_name = passes::get_crate_name(sess, attrs);
671671
let crate_types = collect_crate_types(sess, attrs);
672672
for &style in &crate_types {
673-
let fname =
674-
rustc_session::output::filename_for_input(sess, style, id, &t_outputs);
673+
let fname = rustc_session::output::filename_for_input(
674+
sess, style, crate_name, &t_outputs,
675+
);
675676
println_info!("{}", fname.as_path().file_name().unwrap().to_string_lossy());
676677
}
677678
}
@@ -680,8 +681,7 @@ fn print_crate_info(
680681
// no crate attributes, print out an error and exit
681682
return Compilation::Continue;
682683
};
683-
let id = rustc_session::output::find_crate_name(sess, attrs);
684-
println_info!("{id}");
684+
println_info!("{}", passes::get_crate_name(sess, attrs));
685685
}
686686
Cfg => {
687687
let mut cfgs = sess

Diff for: compiler/rustc_expand/src/module.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -183,12 +183,12 @@ pub(crate) fn mod_file_path_from_attr(
183183
let first_path = attrs.iter().find(|at| at.has_name(sym::path))?;
184184
let Some(path_sym) = first_path.value_str() else {
185185
// This check is here mainly to catch attempting to use a macro,
186-
// such as #[path = concat!(...)]. This isn't currently supported
187-
// because otherwise the InvocationCollector would need to defer
188-
// loading a module until the #[path] attribute was expanded, and
189-
// it doesn't support that (and would likely add a bit of
190-
// complexity). Usually bad forms are checked in AstValidator (via
191-
// `check_builtin_attribute`), but by the time that runs the macro
186+
// such as `#[path = concat!(...)]`. This isn't supported because
187+
// otherwise the `InvocationCollector` would need to defer loading
188+
// a module until the `#[path]` attribute was expanded, and it
189+
// doesn't support that (and would likely add a bit of complexity).
190+
// Usually bad forms are checked during semantic analysis via
191+
// `TyCtxt::check_mod_attrs`), but by the time that runs the macro
192192
// is expanded, and it doesn't give an error.
193193
validate_attr::emit_fatal_malformed_builtin_attribute(&sess.psess, first_path, sym::path);
194194
};

Diff for: compiler/rustc_incremental/src/persist/fs.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,9 @@ use rustc_data_structures::{base_n, flock};
117117
use rustc_fs_util::{LinkOrCopy, link_or_copy, try_canonicalize};
118118
use rustc_middle::bug;
119119
use rustc_session::config::CrateType;
120-
use rustc_session::output::{collect_crate_types, find_crate_name};
120+
use rustc_session::output::collect_crate_types;
121121
use rustc_session::{Session, StableCrateId};
122+
use rustc_span::Symbol;
122123
use tracing::debug;
123124

124125
use crate::errors;
@@ -211,7 +212,7 @@ pub fn in_incr_comp_dir(incr_comp_session_dir: &Path, file_name: &str) -> PathBu
211212
/// The garbage collection will take care of it.
212213
///
213214
/// [`rustc_interface::queries::dep_graph`]: ../../rustc_interface/struct.Queries.html#structfield.dep_graph
214-
pub(crate) fn prepare_session_directory(sess: &Session) {
215+
pub(crate) fn prepare_session_directory(sess: &Session, crate_name: Symbol) {
215216
if sess.opts.incremental.is_none() {
216217
return;
217218
}
@@ -221,7 +222,7 @@ pub(crate) fn prepare_session_directory(sess: &Session) {
221222
debug!("prepare_session_directory");
222223

223224
// {incr-comp-dir}/{crate-name-and-disambiguator}
224-
let crate_dir = crate_path(sess);
225+
let crate_dir = crate_path(sess, crate_name);
225226
debug!("crate-dir: {}", crate_dir.display());
226227
create_dir(sess, &crate_dir, "crate");
227228

@@ -594,10 +595,9 @@ fn string_to_timestamp(s: &str) -> Result<SystemTime, &'static str> {
594595
Ok(UNIX_EPOCH + duration)
595596
}
596597

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

600-
let crate_name = find_crate_name(sess, &[]);
601601
let crate_types = collect_crate_types(sess, &[]);
602602
let stable_crate_id = StableCrateId::new(
603603
crate_name,

Diff for: compiler/rustc_incremental/src/persist/load.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use rustc_serialize::Decodable;
1111
use rustc_serialize::opaque::MemDecoder;
1212
use rustc_session::Session;
1313
use rustc_session::config::IncrementalStateAssertion;
14+
use rustc_span::Symbol;
1415
use tracing::{debug, warn};
1516

1617
use super::data::*;
@@ -203,9 +204,9 @@ pub fn load_query_result_cache(sess: &Session) -> Option<OnDiskCache> {
203204

204205
/// Setups the dependency graph by loading an existing graph from disk and set up streaming of a
205206
/// new graph to an incremental session directory.
206-
pub fn setup_dep_graph(sess: &Session) -> DepGraph {
207+
pub fn setup_dep_graph(sess: &Session, crate_name: Symbol) -> DepGraph {
207208
// `load_dep_graph` can only be called after `prepare_session_directory`.
208-
prepare_session_directory(sess);
209+
prepare_session_directory(sess, crate_name);
209210

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

Diff for: compiler/rustc_interface/messages.ftl

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ interface_abi_required_feature_issue = for more information, see issue #116344 <
66
interface_cant_emit_mir =
77
could not emit MIR: {$error}
88
9+
interface_crate_name_does_not_match = `--crate-name` and `#[crate_name]` are required to match, but `{$crate_name}` != `{$attr_crate_name}`
10+
11+
interface_crate_name_invalid = crate names cannot start with a `-`, but `{$crate_name}` has a leading hyphen
12+
913
interface_emoji_identifier =
1014
identifiers cannot contain emoji: `{$ident}`
1115

Diff for: compiler/rustc_interface/src/errors.rs

+15
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,21 @@ use std::path::Path;
44
use rustc_macros::Diagnostic;
55
use rustc_span::{Span, Symbol};
66

7+
#[derive(Diagnostic)]
8+
#[diag(interface_crate_name_does_not_match)]
9+
pub(crate) struct CrateNameDoesNotMatch {
10+
#[primary_span]
11+
pub(crate) span: Span,
12+
pub(crate) crate_name: Symbol,
13+
pub(crate) attr_crate_name: Symbol,
14+
}
15+
16+
#[derive(Diagnostic)]
17+
#[diag(interface_crate_name_invalid)]
18+
pub(crate) struct CrateNameInvalid<'a> {
19+
pub(crate) crate_name: &'a str,
20+
}
21+
722
#[derive(Diagnostic)]
823
#[diag(interface_ferris_identifier)]
924
pub struct FerrisIdentifier {

Diff for: compiler/rustc_interface/src/passes.rs

+84-21
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,12 @@ use rustc_passes::{abi_test, input_stats, layout_test};
2828
use rustc_resolve::Resolver;
2929
use rustc_session::config::{CrateType, Input, OutFileName, OutputFilenames, OutputType};
3030
use rustc_session::cstore::Untracked;
31-
use rustc_session::output::{collect_crate_types, filename_for_input, find_crate_name};
31+
use rustc_session::output::{collect_crate_types, filename_for_input};
3232
use rustc_session::search_paths::PathKind;
3333
use rustc_session::{Limit, Session};
34-
use rustc_span::{ErrorGuaranteed, FileName, SourceFileHash, SourceFileHashAlgorithm, Symbol, sym};
34+
use rustc_span::{
35+
ErrorGuaranteed, FileName, SourceFileHash, SourceFileHashAlgorithm, Span, Symbol, sym,
36+
};
3537
use rustc_target::spec::PanicStrategy;
3638
use rustc_trait_selection::traits;
3739
use tracing::{info, instrument};
@@ -725,8 +727,7 @@ pub fn create_and_enter_global_ctxt<T, F: for<'tcx> FnOnce(TyCtxt<'tcx>) -> T>(
725727

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

728-
// parse `#[crate_name]` even if `--crate-name` was passed, to make sure it matches.
729-
let crate_name = find_crate_name(sess, &pre_configured_attrs);
730+
let crate_name = get_crate_name(sess, &pre_configured_attrs);
730731
let crate_types = collect_crate_types(sess, &pre_configured_attrs);
731732
let stable_crate_id = StableCrateId::new(
732733
crate_name,
@@ -735,7 +736,7 @@ pub fn create_and_enter_global_ctxt<T, F: for<'tcx> FnOnce(TyCtxt<'tcx>) -> T>(
735736
sess.cfg_version,
736737
);
737738
let outputs = util::build_output_filenames(&pre_configured_attrs, sess);
738-
let dep_graph = setup_dep_graph(sess);
739+
let dep_graph = setup_dep_graph(sess, crate_name);
739740

740741
let cstore =
741742
FreezeLock::new(Box::new(CStore::new(compiler.codegen_backend.metadata_loader())) as _);
@@ -1080,23 +1081,85 @@ pub(crate) fn start_codegen<'tcx>(
10801081
codegen
10811082
}
10821083

1083-
fn get_recursion_limit(krate_attrs: &[ast::Attribute], sess: &Session) -> Limit {
1084-
if let Some(attr) = krate_attrs
1085-
.iter()
1086-
.find(|attr| attr.has_name(sym::recursion_limit) && attr.value_str().is_none())
1084+
/// Compute and validate the crate name.
1085+
pub fn get_crate_name(sess: &Session, krate_attrs: &[ast::Attribute]) -> Symbol {
1086+
// We validate *all* occurrences of `#![crate_name]`, pick the first find and
1087+
// if a crate name was passed on the command line via `--crate-name` we enforce
1088+
// that they match.
1089+
// We perform the validation step here instead of later to ensure it gets run
1090+
// in all code paths that require the crate name very early on, namely before
1091+
// macro expansion.
1092+
1093+
let attr_crate_name =
1094+
validate_and_find_value_str_builtin_attr(sym::crate_name, sess, krate_attrs);
1095+
1096+
let validate = |name, span| {
1097+
rustc_session::output::validate_crate_name(sess, name, span);
1098+
name
1099+
};
1100+
1101+
if let Some(crate_name) = &sess.opts.crate_name {
1102+
let crate_name = Symbol::intern(crate_name);
1103+
if let Some((attr_crate_name, span)) = attr_crate_name
1104+
&& attr_crate_name != crate_name
1105+
{
1106+
sess.dcx().emit_err(errors::CrateNameDoesNotMatch {
1107+
span,
1108+
crate_name,
1109+
attr_crate_name,
1110+
});
1111+
}
1112+
return validate(crate_name, None);
1113+
}
1114+
1115+
if let Some((crate_name, span)) = attr_crate_name {
1116+
return validate(crate_name, Some(span));
1117+
}
1118+
1119+
if let Input::File(ref path) = sess.io.input
1120+
&& let Some(file_stem) = path.file_stem().and_then(|s| s.to_str())
10871121
{
1088-
// This is here mainly to check for using a macro, such as
1089-
// #![recursion_limit = foo!()]. That is not supported since that
1090-
// would require expanding this while in the middle of expansion,
1091-
// which needs to know the limit before expanding. Otherwise,
1092-
// validation would normally be caught in AstValidator (via
1093-
// `check_builtin_attribute`), but by the time that runs the macro
1094-
// is expanded, and it doesn't give an error.
1095-
validate_attr::emit_fatal_malformed_builtin_attribute(
1096-
&sess.psess,
1097-
attr,
1098-
sym::recursion_limit,
1099-
);
1122+
if file_stem.starts_with('-') {
1123+
sess.dcx().emit_err(errors::CrateNameInvalid { crate_name: file_stem });
1124+
} else {
1125+
return validate(Symbol::intern(&file_stem.replace('-', "_")), None);
1126+
}
11001127
}
1128+
1129+
sym::rust_out
1130+
}
1131+
1132+
fn get_recursion_limit(krate_attrs: &[ast::Attribute], sess: &Session) -> Limit {
1133+
// We don't permit macro calls inside of the attribute (e.g., #![recursion_limit = `expand!()`])
1134+
// because that would require expanding this while in the middle of expansion, which needs to
1135+
// know the limit before expanding.
1136+
let _ = validate_and_find_value_str_builtin_attr(sym::recursion_limit, sess, krate_attrs);
11011137
rustc_middle::middle::limits::get_recursion_limit(krate_attrs, sess)
11021138
}
1139+
1140+
/// Validate *all* occurrences of the given "[value-str]" built-in attribute and return the first find.
1141+
///
1142+
/// This validator is intended for built-in attributes whose value needs to be known very early
1143+
/// during compilation (namely, before macro expansion) and it mainly exists to reject macro calls
1144+
/// inside of the attributes, such as in `#![name = expand!()]`. Normal attribute validation happens
1145+
/// during semantic analysis via [`TyCtxt::check_mod_attrs`] which happens *after* macro expansion
1146+
/// when such macro calls (here: `expand`) have already been expanded and we can no longer check for
1147+
/// their presence.
1148+
///
1149+
/// [value-str]: ast::Attribute::value_str
1150+
fn validate_and_find_value_str_builtin_attr(
1151+
name: Symbol,
1152+
sess: &Session,
1153+
krate_attrs: &[ast::Attribute],
1154+
) -> Option<(Symbol, Span)> {
1155+
let mut result = None;
1156+
// Validate *all* relevant attributes, not just the first occurrence.
1157+
for attr in ast::attr::filter_by_name(krate_attrs, name) {
1158+
let Some(value) = attr.value_str() else {
1159+
validate_attr::emit_fatal_malformed_builtin_attribute(&sess.psess, attr, name)
1160+
};
1161+
// Choose the first occurrence as our result.
1162+
result.get_or_insert((value, attr.span));
1163+
}
1164+
result
1165+
}

Diff for: compiler/rustc_interface/src/util.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -433,11 +433,11 @@ pub(crate) fn check_attr_crate_type(
433433
}
434434
} else {
435435
// This is here mainly to check for using a macro, such as
436-
// #![crate_type = foo!()]. That is not supported since the
436+
// `#![crate_type = foo!()]`. That is not supported since the
437437
// crate type needs to be known very early in compilation long
438438
// before expansion. Otherwise, validation would normally be
439-
// caught in AstValidator (via `check_builtin_attribute`), but
440-
// by the time that runs the macro is expanded, and it doesn't
439+
// caught during semantic analysis via `TyCtxt::check_mod_attrs`,
440+
// but by the time that runs the macro is expanded, and it doesn't
441441
// give an error.
442442
validate_attr::emit_fatal_malformed_builtin_attribute(
443443
&sess.psess,

Diff for: compiler/rustc_session/messages.ftl

+2-6
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,8 @@ session_cannot_mix_and_match_sanitizers = `-Zsanitizer={$first}` is incompatible
88
session_cli_feature_diagnostic_help =
99
add `-Zcrate-attr="feature({$feature})"` to the command-line options to enable
1010
11-
session_crate_name_does_not_match = `--crate-name` and `#[crate_name]` are required to match, but `{$s}` != `{$name}`
12-
1311
session_crate_name_empty = crate name must not be empty
1412
15-
session_crate_name_invalid = crate names cannot start with a `-`, but `{$s}` has a leading hyphen
16-
1713
session_embed_source_insufficient_dwarf_version = `-Zembed-source=y` requires at least `-Z dwarf-version=5` but DWARF version is {$dwarf_version}
1814
1915
session_embed_source_requires_debug_info = `-Zembed-source=y` requires debug information to be enabled
@@ -52,8 +48,8 @@ session_instrumentation_not_supported = {$us} instrumentation is not supported f
5248
session_int_literal_too_large = integer literal is too large
5349
.note = value exceeds limit of `{$limit}`
5450
55-
session_invalid_character_in_create_name = invalid character `{$character}` in crate name: `{$crate_name}`
56-
session_invalid_character_in_create_name_help = you can either pass `--crate-name` on the command line or add `#![crate_name="…"]` to set the crate name
51+
session_invalid_character_in_crate_name = invalid character {$character} in crate name: `{$crate_name}`
52+
.help = you can either pass `--crate-name` on the command line or add `#![crate_name = "…"]` to set the crate name
5753
5854
session_invalid_float_literal_suffix = invalid suffix `{$suffix}` for float literal
5955
.label = invalid suffix `{$suffix}`

Diff for: compiler/rustc_session/src/errors.rs

+3-24
Original file line numberDiff line numberDiff line change
@@ -212,21 +212,6 @@ pub(crate) struct FileWriteFail<'a> {
212212
pub(crate) err: String,
213213
}
214214

215-
#[derive(Diagnostic)]
216-
#[diag(session_crate_name_does_not_match)]
217-
pub(crate) struct CrateNameDoesNotMatch {
218-
#[primary_span]
219-
pub(crate) span: Span,
220-
pub(crate) s: Symbol,
221-
pub(crate) name: Symbol,
222-
}
223-
224-
#[derive(Diagnostic)]
225-
#[diag(session_crate_name_invalid)]
226-
pub(crate) struct CrateNameInvalid<'a> {
227-
pub(crate) s: &'a str,
228-
}
229-
230215
#[derive(Diagnostic)]
231216
#[diag(session_crate_name_empty)]
232217
pub(crate) struct CrateNameEmpty {
@@ -235,20 +220,14 @@ pub(crate) struct CrateNameEmpty {
235220
}
236221

237222
#[derive(Diagnostic)]
238-
#[diag(session_invalid_character_in_create_name)]
223+
#[diag(session_invalid_character_in_crate_name)]
239224
pub(crate) struct InvalidCharacterInCrateName {
240225
#[primary_span]
241226
pub(crate) span: Option<Span>,
242227
pub(crate) character: char,
243228
pub(crate) crate_name: Symbol,
244-
#[subdiagnostic]
245-
pub(crate) crate_name_help: Option<InvalidCrateNameHelp>,
246-
}
247-
248-
#[derive(Subdiagnostic)]
249-
pub(crate) enum InvalidCrateNameHelp {
250-
#[help(session_invalid_character_in_create_name_help)]
251-
AddCrateName,
229+
#[help]
230+
pub(crate) help: Option<()>,
252231
}
253232

254233
#[derive(Subdiagnostic)]

0 commit comments

Comments
 (0)