Skip to content

Commit c7361b1

Browse files
committed
Make RustTarget parsing more permissive
1 parent 3300618 commit c7361b1

File tree

1 file changed

+114
-26
lines changed

1 file changed

+114
-26
lines changed

bindgen/features.rs

Lines changed: 114 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ macro_rules! define_rust_targets {
4444
}
4545

4646
impl RustTarget {
47-
fn minor(self) -> Option<u64> {
47+
const fn minor(self) -> Option<u64> {
4848
match self {
4949
$( Self::$variant => Some($minor),)*
5050
Self::Nightly => None
@@ -136,15 +136,15 @@ define_rust_targets! {
136136
Stable_1_0(0) => {},
137137
}
138138

139-
/// Latest stable release of Rust
139+
/// Latest stable release of Rust that is supported by bindgen
140140
pub const LATEST_STABLE_RUST: RustTarget = {
141141
// FIXME: replace all this code by
142142
// ```
143143
// RustTarget::stable_releases()
144144
// .into_iter()
145145
// .max_by_key(|(_, m)| m)
146146
// .map(|(t, _)| t)
147-
// .unwrap_or(RustTarget::Nightly)
147+
// .unwrap()
148148
// ```
149149
// once those operations can be used in constants.
150150
let targets = RustTarget::stable_releases();
@@ -170,6 +170,42 @@ pub const LATEST_STABLE_RUST: RustTarget = {
170170
}
171171
};
172172

173+
/// Earliest stable release of Rust that is supported by bindgen
174+
pub const EARLIEST_STABLE_RUST: RustTarget = {
175+
// FIXME: replace all this code by
176+
// ```
177+
// RustTarget::stable_releases()
178+
// .into_iter()
179+
// .min_by_key(|(_, m)| m)
180+
// .map(|(t, _)| t)
181+
// .unwrap_or(LATEST_STABLE_RUST)
182+
// ```
183+
// once those operations can be used in constants.
184+
let targets = RustTarget::stable_releases();
185+
186+
let mut i = 0;
187+
let mut earliest_target = None;
188+
let Some(mut earliest_minor) = LATEST_STABLE_RUST.minor() else {
189+
unreachable!()
190+
};
191+
192+
while i < targets.len() {
193+
let (target, minor) = targets[i];
194+
195+
if earliest_minor > minor {
196+
earliest_minor = minor;
197+
earliest_target = Some(target);
198+
}
199+
200+
i += 1;
201+
}
202+
203+
match earliest_target {
204+
Some(target) => target,
205+
None => unreachable!(),
206+
}
207+
};
208+
173209
impl Default for RustTarget {
174210
fn default() -> Self {
175211
LATEST_STABLE_RUST
@@ -193,28 +229,62 @@ impl Ord for RustTarget {
193229
}
194230
}
195231

232+
fn invalid_input<T>(input: &str, msg: impl std::fmt::Display) -> io::Result<T> {
233+
Err(io::Error::new(
234+
io::ErrorKind::InvalidInput,
235+
format!("\"{input}\" is not a valid Rust target, {msg}"),
236+
))
237+
}
238+
196239
impl FromStr for RustTarget {
197240
type Err = io::Error;
198241

199-
fn from_str(s: &str) -> Result<Self, Self::Err> {
200-
if s == "nightly" {
242+
fn from_str(input: &str) -> Result<Self, Self::Err> {
243+
if input == "nightly" {
201244
return Ok(Self::Nightly);
202245
}
203246

204-
if let Some(("1", str_minor)) = s.split_once('.') {
205-
if let Ok(minor) = str_minor.parse::<u64>() {
206-
for (target, target_minor) in Self::stable_releases() {
207-
if minor == target_minor {
208-
return Ok(target);
209-
}
210-
}
211-
}
247+
let Some((major_str, tail)) = input.split_once('.') else {
248+
return invalid_input(input, "accepted values are of the form \"1.71\", \"1.71.1\" or \"nightly\"." );
249+
};
250+
251+
if major_str != "1" {
252+
return invalid_input(
253+
input,
254+
"The largest major version of Rust released is \"1\"",
255+
);
212256
}
213257

214-
Err(io::Error::new(
215-
io::ErrorKind::InvalidInput,
216-
"Got an invalid Rust target. Accepted values are of the form \"1.71\" or \"nightly\"."
217-
))
258+
// We ignore the patch version number as they only include backwards compatible bug fixes.
259+
let (minor, _patch) = match tail.split_once('.') {
260+
Some((minor_str, patch_str)) => {
261+
let Ok(minor) = minor_str.parse::<u64>() else {
262+
return invalid_input(input, "the minor version number must be an unsigned 64-bit integer");
263+
};
264+
let Ok(patch) = patch_str.parse::<u64>() else {
265+
return invalid_input(input, "the patch version number must be an unsigned 64-bit integer");
266+
};
267+
(minor, patch)
268+
}
269+
None => {
270+
let Ok(minor) = tail.parse::<u64>() else {
271+
return invalid_input(input, "the minor version number must be an unsigned 64-bit integer");
272+
};
273+
(minor, 0)
274+
}
275+
};
276+
277+
let Some(target) = Self::stable_releases()
278+
.iter()
279+
.filter(|(_, target_minor)| minor >= *target_minor)
280+
.max_by_key(|(_, target_minor)| target_minor)
281+
.map(|(target, _)| target)
282+
.cloned()
283+
else {
284+
return invalid_input(input, format!("the earliest Rust target supported by bindgen is {EARLIEST_STABLE_RUST}"));
285+
};
286+
287+
Ok(target)
218288
}
219289
}
220290

@@ -282,19 +352,37 @@ mod test {
282352
}
283353

284354
fn test_target(target_str: &str, target: RustTarget) {
285-
let target_string = target.to_string();
286-
assert_eq!(target_str, target_string);
287-
assert_eq!(target, RustTarget::from_str(target_str).unwrap());
355+
assert_eq!(
356+
target,
357+
target_str.parse::<RustTarget>().unwrap(),
358+
"{target_str}"
359+
);
360+
}
361+
362+
fn test_invalid_target(target_str: &str) {
363+
assert!(target_str.parse::<RustTarget>().is_err(), "{}", target_str);
288364
}
289365

290366
#[test]
291-
fn str_to_target() {
292-
test_target("1.0", RustTarget::Stable_1_0);
293-
test_target("1.17", RustTarget::Stable_1_17);
294-
test_target("1.19", RustTarget::Stable_1_19);
295-
test_target("1.21", RustTarget::Stable_1_21);
296-
test_target("1.25", RustTarget::Stable_1_25);
367+
fn valid_targets() {
297368
test_target("1.71", RustTarget::Stable_1_71);
369+
test_target("1.71.0", RustTarget::Stable_1_71);
370+
test_target("1.71.1", RustTarget::Stable_1_71);
371+
test_target("1.72", RustTarget::Stable_1_71);
372+
test_target("1.73", RustTarget::Stable_1_73);
373+
test_target("1.18446744073709551615", LATEST_STABLE_RUST);
298374
test_target("nightly", RustTarget::Nightly);
299375
}
376+
377+
#[test]
378+
fn invalid_targets() {
379+
test_invalid_target("2.0");
380+
test_invalid_target("1.cat");
381+
test_invalid_target("1.0.cat");
382+
test_invalid_target("1.18446744073709551616");
383+
test_invalid_target("1.0.18446744073709551616");
384+
test_invalid_target("1.-1.0");
385+
test_invalid_target("1.0.-1");
386+
test_invalid_target("beta");
387+
}
300388
}

0 commit comments

Comments
 (0)