Skip to content

Commit 358ee54

Browse files
committed
Auto merge of #8814 - ehuss:warn-feature-syntax, r=alexcrichton
Add a future-compatibility warning on allowed feature name characters. This adds a restriction on the valid syntax of a feature name. An warning is issued if a feature does not match the new validation, with the intent that it will be an error in the future. The new restriction is: * The first character must be a [Unicode XID start character](https://unicode.org/reports/tr31/) (most letters), a digit, or `_`. * Subsequent characters must be a [Unicode XID continue character](https://unicode.org/reports/tr31/) (a digit, `_`, or most letters), `-`, or `+`. The changes around passing in `config` to `Summary` can mostly be reverted when this is changed to an error. I'm a little concerned that we don't have a mechanism to silence the warning. Should we add one?
2 parents 963bfe4 + b731190 commit 358ee54

File tree

6 files changed

+210
-24
lines changed

6 files changed

+210
-24
lines changed

crates/resolver-tests/src/lib.rs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,15 @@ use proptest::string::string_regex;
2424
use varisat::{self, ExtendFormula};
2525

2626
pub fn resolve(deps: Vec<Dependency>, registry: &[Summary]) -> CargoResult<Vec<PackageId>> {
27-
resolve_with_config(deps, registry, None)
27+
resolve_with_config(deps, registry, &Config::default().unwrap())
2828
}
2929

3030
pub fn resolve_and_validated(
3131
deps: Vec<Dependency>,
3232
registry: &[Summary],
3333
sat_resolve: Option<SatResolve>,
3434
) -> CargoResult<Vec<PackageId>> {
35-
let resolve = resolve_with_config_raw(deps.clone(), registry, None);
35+
let resolve = resolve_with_config_raw(deps.clone(), registry, &Config::default().unwrap());
3636

3737
match resolve {
3838
Err(e) => {
@@ -109,7 +109,7 @@ pub fn resolve_and_validated(
109109
pub fn resolve_with_config(
110110
deps: Vec<Dependency>,
111111
registry: &[Summary],
112-
config: Option<&Config>,
112+
config: &Config,
113113
) -> CargoResult<Vec<PackageId>> {
114114
let resolve = resolve_with_config_raw(deps, registry, config)?;
115115
Ok(resolve.sort())
@@ -118,7 +118,7 @@ pub fn resolve_with_config(
118118
pub fn resolve_with_config_raw(
119119
deps: Vec<Dependency>,
120120
registry: &[Summary],
121-
config: Option<&Config>,
121+
config: &Config,
122122
) -> CargoResult<Resolve> {
123123
struct MyRegistry<'a> {
124124
list: &'a [Summary],
@@ -170,15 +170,22 @@ pub fn resolve_with_config_raw(
170170
list: registry,
171171
used: HashSet::new(),
172172
};
173-
let summary = Summary::new(pkg_id("root"), deps, &BTreeMap::new(), None::<&String>).unwrap();
173+
let summary = Summary::new(
174+
config,
175+
pkg_id("root"),
176+
deps,
177+
&BTreeMap::new(),
178+
None::<&String>,
179+
)
180+
.unwrap();
174181
let opts = ResolveOpts::everything();
175182
let start = Instant::now();
176183
let resolve = resolver::resolve(
177184
&[(summary, opts)],
178185
&[],
179186
&mut registry,
180187
&HashSet::new(),
181-
config,
188+
Some(config),
182189
true,
183190
);
184191

@@ -564,7 +571,14 @@ pub fn pkg_dep<T: ToPkgId>(name: T, dep: Vec<Dependency>) -> Summary {
564571
} else {
565572
None
566573
};
567-
Summary::new(name.to_pkgid(), dep, &BTreeMap::new(), link).unwrap()
574+
Summary::new(
575+
&Config::default().unwrap(),
576+
name.to_pkgid(),
577+
dep,
578+
&BTreeMap::new(),
579+
link,
580+
)
581+
.unwrap()
568582
}
569583

570584
pub fn pkg_id(name: &str) -> PackageId {
@@ -585,14 +599,22 @@ pub fn pkg_loc(name: &str, loc: &str) -> Summary {
585599
} else {
586600
None
587601
};
588-
Summary::new(pkg_id_loc(name, loc), Vec::new(), &BTreeMap::new(), link).unwrap()
602+
Summary::new(
603+
&Config::default().unwrap(),
604+
pkg_id_loc(name, loc),
605+
Vec::new(),
606+
&BTreeMap::new(),
607+
link,
608+
)
609+
.unwrap()
589610
}
590611

591612
pub fn remove_dep(sum: &Summary, ind: usize) -> Summary {
592613
let mut deps = sum.dependencies().to_vec();
593614
deps.remove(ind);
594615
// note: more things will need to be copied over in the future, but it works for now.
595616
Summary::new(
617+
&Config::default().unwrap(),
596618
sum.package_id(),
597619
deps,
598620
&BTreeMap::new(),

crates/resolver-tests/tests/resolve.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ proptest! {
8787
let mres = resolve_with_config(
8888
vec![dep_req(&this.name(), &format!("={}", this.version()))],
8989
&reg,
90-
Some(&config),
90+
&config,
9191
);
9292

9393
prop_assert_eq!(
@@ -584,7 +584,7 @@ fn test_resolving_minimum_version_with_transitive_deps() {
584584
let res = resolve_with_config(
585585
vec![dep_req("foo", "1.0.0"), dep_req("bar", "1.0.0")],
586586
&reg,
587-
Some(&config),
587+
&config,
588588
)
589589
.unwrap();
590590

src/cargo/core/summary.rs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::core::{Dependency, PackageId, SourceId};
22
use crate::util::interning::InternedString;
3-
use crate::util::CargoResult;
3+
use crate::util::{CargoResult, Config};
44
use anyhow::bail;
55
use semver::Version;
66
use std::collections::{BTreeMap, HashMap, HashSet};
@@ -31,6 +31,7 @@ struct Inner {
3131

3232
impl Summary {
3333
pub fn new(
34+
config: &Config,
3435
pkg_id: PackageId,
3536
dependencies: Vec<Dependency>,
3637
features: &BTreeMap<InternedString, Vec<InternedString>>,
@@ -49,7 +50,8 @@ impl Summary {
4950
)
5051
}
5152
}
52-
let (feature_map, has_namespaced_features) = build_feature_map(features, &dependencies)?;
53+
let (feature_map, has_namespaced_features) =
54+
build_feature_map(config, pkg_id, features, &dependencies)?;
5355
Ok(Summary {
5456
inner: Rc::new(Inner {
5557
package_id: pkg_id,
@@ -161,6 +163,8 @@ impl Hash for Summary {
161163
/// included a `dep:` prefixed namespaced feature (used for gating on
162164
/// nightly).
163165
fn build_feature_map(
166+
config: &Config,
167+
pkg_id: PackageId,
164168
features: &BTreeMap<InternedString, Vec<InternedString>>,
165169
dependencies: &[Dependency],
166170
) -> CargoResult<(FeatureMap, bool)> {
@@ -223,6 +227,7 @@ fn build_feature_map(
223227
feature
224228
);
225229
}
230+
validate_feature_name(config, pkg_id, feature)?;
226231
for fv in fvs {
227232
// Find data for the referenced dependency...
228233
let dep_data = {
@@ -400,3 +405,34 @@ impl fmt::Display for FeatureValue {
400405
}
401406

402407
pub type FeatureMap = BTreeMap<InternedString, Vec<FeatureValue>>;
408+
409+
fn validate_feature_name(config: &Config, pkg_id: PackageId, name: &str) -> CargoResult<()> {
410+
let mut chars = name.chars();
411+
const FUTURE: &str = "This was previously accepted but is being phased out; \
412+
it will become a hard error in a future release.\n\
413+
For more information, see issue #8813 <https://github.com/rust-lang/cargo/issues/8813>, \
414+
and please leave a comment if this will be a problem for your project.";
415+
if let Some(ch) = chars.next() {
416+
if !(unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_' || ch.is_digit(10)) {
417+
config.shell().warn(&format!(
418+
"invalid character `{}` in feature `{}` in package {}, \
419+
the first character must be a Unicode XID start character or digit \
420+
(most letters or `_` or `0` to `9`)\n\
421+
{}",
422+
ch, name, pkg_id, FUTURE
423+
))?;
424+
}
425+
}
426+
for ch in chars {
427+
if !(unicode_xid::UnicodeXID::is_xid_continue(ch) || ch == '-' || ch == '+') {
428+
config.shell().warn(&format!(
429+
"invalid character `{}` in feature `{}` in package {}, \
430+
characters must be Unicode XID characters or `+` \
431+
(numbers, `+`, `-`, `_`, or most letters)\n\
432+
{}",
433+
ch, name, pkg_id, FUTURE
434+
))?;
435+
}
436+
}
437+
Ok(())
438+
}

src/cargo/sources/registry/index.rs

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ impl<'cfg> RegistryIndex<'cfg> {
270270
'a: 'b,
271271
{
272272
let source_id = self.source_id;
273+
let config = self.config;
273274
let namespaced_features = self.config.cli_unstable().namespaced_features;
274275

275276
// First up actually parse what summaries we have available. If Cargo
@@ -289,13 +290,15 @@ impl<'cfg> RegistryIndex<'cfg> {
289290
.versions
290291
.iter_mut()
291292
.filter_map(move |(k, v)| if req.matches(k) { Some(v) } else { None })
292-
.filter_map(move |maybe| match maybe.parse(raw_data, source_id) {
293-
Ok(summary) => Some(summary),
294-
Err(e) => {
295-
info!("failed to parse `{}` registry package: {}", name, e);
296-
None
297-
}
298-
})
293+
.filter_map(
294+
move |maybe| match maybe.parse(config, raw_data, source_id) {
295+
Ok(summary) => Some(summary),
296+
Err(e) => {
297+
info!("failed to parse `{}` registry package: {}", name, e);
298+
None
299+
}
300+
},
301+
)
299302
.filter(move |is| is.summary.unstable_gate(namespaced_features).is_ok()))
300303
}
301304

@@ -521,7 +524,7 @@ impl Summaries {
521524
// allow future cargo implementations to break the
522525
// interpretation of each line here and older cargo will simply
523526
// ignore the new lines.
524-
let summary = match IndexSummary::parse(line, source_id) {
527+
let summary = match IndexSummary::parse(config, line, source_id) {
525528
Ok(summary) => summary,
526529
Err(e) => {
527530
log::info!("failed to parse {:?} registry package: {}", relative, e);
@@ -684,12 +687,17 @@ impl MaybeIndexSummary {
684687
/// Does nothing if this is already `Parsed`, and otherwise the `raw_data`
685688
/// passed in is sliced with the bounds in `Unparsed` and then actually
686689
/// parsed.
687-
fn parse(&mut self, raw_data: &[u8], source_id: SourceId) -> CargoResult<&IndexSummary> {
690+
fn parse(
691+
&mut self,
692+
config: &Config,
693+
raw_data: &[u8],
694+
source_id: SourceId,
695+
) -> CargoResult<&IndexSummary> {
688696
let (start, end) = match self {
689697
MaybeIndexSummary::Unparsed { start, end } => (*start, *end),
690698
MaybeIndexSummary::Parsed(summary) => return Ok(summary),
691699
};
692-
let summary = IndexSummary::parse(&raw_data[start..end], source_id)?;
700+
let summary = IndexSummary::parse(config, &raw_data[start..end], source_id)?;
693701
*self = MaybeIndexSummary::Parsed(summary);
694702
match self {
695703
MaybeIndexSummary::Unparsed { .. } => unreachable!(),
@@ -709,7 +717,7 @@ impl IndexSummary {
709717
/// a package.
710718
///
711719
/// The `line` provided is expected to be valid JSON.
712-
fn parse(line: &[u8], source_id: SourceId) -> CargoResult<IndexSummary> {
720+
fn parse(config: &Config, line: &[u8], source_id: SourceId) -> CargoResult<IndexSummary> {
713721
let RegistryPackage {
714722
name,
715723
vers,
@@ -725,7 +733,7 @@ impl IndexSummary {
725733
.into_iter()
726734
.map(|dep| dep.into_dep(source_id))
727735
.collect::<CargoResult<Vec<_>>>()?;
728-
let mut summary = Summary::new(pkgid, deps, &features, links)?;
736+
let mut summary = Summary::new(config, pkgid, deps, &features, links)?;
729737
summary.set_checksum(cksum);
730738
Ok(IndexSummary {
731739
summary,

src/cargo/util/toml/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1191,6 +1191,7 @@ impl TomlManifest {
11911191
let empty_features = BTreeMap::new();
11921192

11931193
let summary = Summary::new(
1194+
config,
11941195
pkgid,
11951196
deps,
11961197
me.features.as_ref().unwrap_or(&empty_features),

0 commit comments

Comments
 (0)