Auto merge of #85775 - adamrk:warn-unused-target-fields, r=nagisa

Emit warnings for unused fields in custom targets.

Add a warning which lists any fields in a custom target `json` file that aren't used. Currently unrecognized fields are ignored so, for example, a typo in the `json` will silently produce a target which isn't the one intended.
This commit is contained in:
bors 2021-06-21 06:56:51 +00:00
commit d789de67dc
7 changed files with 235 additions and 91 deletions

View file

@ -1114,6 +1114,15 @@ impl Json {
} }
} }
/// If the Json value is an Object, deletes the value associated with the
/// provided key from the Object and returns it. Otherwise, returns None.
pub fn remove_key(&mut self, key: &str) -> Option<Json> {
match *self {
Json::Object(ref mut map) => map.remove(key),
_ => None,
}
}
/// Attempts to get a nested Json Object for each key in `keys`. /// Attempts to get a nested Json Object for each key in `keys`.
/// If any key is found not to exist, `find_path` will return `None`. /// If any key is found not to exist, `find_path` will return `None`.
/// Otherwise, it will return the Json value associated with the final key. /// Otherwise, it will return the Json value associated with the final key.

View file

@ -12,7 +12,7 @@ use rustc_data_structures::fx::FxHashSet;
use rustc_data_structures::impl_stable_hash_via_hash; use rustc_data_structures::impl_stable_hash_via_hash;
use rustc_target::abi::{Align, TargetDataLayout}; use rustc_target::abi::{Align, TargetDataLayout};
use rustc_target::spec::{SplitDebuginfo, Target, TargetTriple}; use rustc_target::spec::{SplitDebuginfo, Target, TargetTriple, TargetWarnings};
use rustc_serialize::json; use rustc_serialize::json;
@ -899,9 +899,11 @@ pub(super) fn build_target_config(
target_override: Option<Target>, target_override: Option<Target>,
sysroot: &PathBuf, sysroot: &PathBuf,
) -> Target { ) -> Target {
let target_result = let target_result = target_override.map_or_else(
target_override.map_or_else(|| Target::search(&opts.target_triple, sysroot), Ok); || Target::search(&opts.target_triple, sysroot),
let target = target_result.unwrap_or_else(|e| { |t| Ok((t, TargetWarnings::empty())),
);
let (target, target_warnings) = target_result.unwrap_or_else(|e| {
early_error( early_error(
opts.error_format, opts.error_format,
&format!( &format!(
@ -911,6 +913,9 @@ pub(super) fn build_target_config(
), ),
) )
}); });
for warning in target_warnings.warning_messages() {
early_warn(opts.error_format, &warning)
}
if !matches!(target.pointer_width, 16 | 32 | 64) { if !matches!(target.pointer_width, 16 | 32 | 64) {
early_error( early_error(

View file

@ -1284,9 +1284,12 @@ pub fn build_session(
let target_cfg = config::build_target_config(&sopts, target_override, &sysroot); let target_cfg = config::build_target_config(&sopts, target_override, &sysroot);
let host_triple = TargetTriple::from_triple(config::host_triple()); let host_triple = TargetTriple::from_triple(config::host_triple());
let host = Target::search(&host_triple, &sysroot).unwrap_or_else(|e| { let (host, target_warnings) = Target::search(&host_triple, &sysroot).unwrap_or_else(|e| {
early_error(sopts.error_format, &format!("Error loading host specification: {}", e)) early_error(sopts.error_format, &format!("Error loading host specification: {}", e))
}); });
for warning in target_warnings.warning_messages() {
early_warn(sopts.error_format, &warning)
}
let loader = file_loader.unwrap_or_else(|| Box::new(RealFileLoader)); let loader = file_loader.unwrap_or_else(|| Box::new(RealFileLoader));
let hash_kind = sopts.debugging_opts.src_hash_algorithm.unwrap_or_else(|| { let hash_kind = sopts.debugging_opts.src_hash_algorithm.unwrap_or_else(|| {

View file

@ -27,6 +27,9 @@ pub mod abi;
pub mod asm; pub mod asm;
pub mod spec; pub mod spec;
#[cfg(test)]
mod tests;
/// Requirements for a `StableHashingContext` to be used in this crate. /// Requirements for a `StableHashingContext` to be used in this crate.
/// This is a hack to allow using the `HashStable_Generic` derive macro /// This is a hack to allow using the `HashStable_Generic` derive macro
/// instead of implementing everything in `rustc_middle`. /// instead of implementing everything in `rustc_middle`.

View file

@ -906,6 +906,38 @@ supported_targets! {
("bpfel-unknown-none", bpfel_unknown_none), ("bpfel-unknown-none", bpfel_unknown_none),
} }
/// Warnings encountered when parsing the target `json`.
///
/// Includes fields that weren't recognized and fields that don't have the expected type.
#[derive(Debug, PartialEq)]
pub struct TargetWarnings {
unused_fields: Vec<String>,
incorrect_type: Vec<String>,
}
impl TargetWarnings {
pub fn empty() -> Self {
Self { unused_fields: Vec::new(), incorrect_type: Vec::new() }
}
pub fn warning_messages(&self) -> Vec<String> {
let mut warnings = vec![];
if !self.unused_fields.is_empty() {
warnings.push(format!(
"target json file contains unused fields: {}",
self.unused_fields.join(", ")
));
}
if !self.incorrect_type.is_empty() {
warnings.push(format!(
"target json file contains fields whose value doesn't have the correct json type: {}",
self.incorrect_type.join(", ")
));
}
warnings
}
}
/// Everything `rustc` knows about how to compile for a specific target. /// Everything `rustc` knows about how to compile for a specific target.
/// ///
/// Every field here must be specified, and has no default value. /// Every field here must be specified, and has no default value.
@ -1448,7 +1480,7 @@ impl Target {
} }
/// Loads a target descriptor from a JSON object. /// Loads a target descriptor from a JSON object.
pub fn from_json(obj: Json) -> Result<Target, String> { pub fn from_json(mut obj: Json) -> Result<(Target, TargetWarnings), String> {
// While ugly, this code must remain this way to retain // While ugly, this code must remain this way to retain
// compatibility with existing JSON fields and the internal // compatibility with existing JSON fields and the internal
// expected naming of the Target and TargetOptions structs. // expected naming of the Target and TargetOptions structs.
@ -1456,10 +1488,9 @@ impl Target {
// are round-tripped through this code to catch cases where // are round-tripped through this code to catch cases where
// the JSON parser is not updated to match the structs. // the JSON parser is not updated to match the structs.
let get_req_field = |name: &str| { let mut get_req_field = |name: &str| {
obj.find(name) obj.remove_key(name)
.and_then(Json::as_string) .and_then(|j| Json::as_string(&j).map(str::to_string))
.map(str::to_string)
.ok_or_else(|| format!("Field {} in target specification is required", name)) .ok_or_else(|| format!("Field {} in target specification is required", name))
}; };
@ -1473,28 +1504,30 @@ impl Target {
options: Default::default(), options: Default::default(),
}; };
let mut incorrect_type = vec![];
macro_rules! key { macro_rules! key {
($key_name:ident) => ( { ($key_name:ident) => ( {
let name = (stringify!($key_name)).replace("_", "-"); let name = (stringify!($key_name)).replace("_", "-");
if let Some(s) = obj.find(&name).and_then(Json::as_string) { if let Some(s) = obj.remove_key(&name).and_then(|j| Json::as_string(&j).map(str::to_string)) {
base.$key_name = s.to_string(); base.$key_name = s;
} }
} ); } );
($key_name:ident = $json_name:expr) => ( { ($key_name:ident = $json_name:expr) => ( {
let name = $json_name; let name = $json_name;
if let Some(s) = obj.find(&name).and_then(Json::as_string) { if let Some(s) = obj.remove_key(&name).and_then(|j| Json::as_string(&j).map(str::to_string)) {
base.$key_name = s.to_string(); base.$key_name = s;
} }
} ); } );
($key_name:ident, bool) => ( { ($key_name:ident, bool) => ( {
let name = (stringify!($key_name)).replace("_", "-"); let name = (stringify!($key_name)).replace("_", "-");
if let Some(s) = obj.find(&name).and_then(Json::as_boolean) { if let Some(s) = obj.remove_key(&name).and_then(|j| Json::as_boolean(&j)) {
base.$key_name = s; base.$key_name = s;
} }
} ); } );
($key_name:ident, Option<u32>) => ( { ($key_name:ident, Option<u32>) => ( {
let name = (stringify!($key_name)).replace("_", "-"); let name = (stringify!($key_name)).replace("_", "-");
if let Some(s) = obj.find(&name).and_then(Json::as_u64) { if let Some(s) = obj.remove_key(&name).and_then(|j| Json::as_u64(&j)) {
if s < 1 || s > 5 { if s < 1 || s > 5 {
return Err("Not a valid DWARF version number".to_string()); return Err("Not a valid DWARF version number".to_string());
} }
@ -1503,13 +1536,13 @@ impl Target {
} ); } );
($key_name:ident, Option<u64>) => ( { ($key_name:ident, Option<u64>) => ( {
let name = (stringify!($key_name)).replace("_", "-"); let name = (stringify!($key_name)).replace("_", "-");
if let Some(s) = obj.find(&name).and_then(Json::as_u64) { if let Some(s) = obj.remove_key(&name).and_then(|j| Json::as_u64(&j)) {
base.$key_name = Some(s); base.$key_name = Some(s);
} }
} ); } );
($key_name:ident, MergeFunctions) => ( { ($key_name:ident, MergeFunctions) => ( {
let name = (stringify!($key_name)).replace("_", "-"); let name = (stringify!($key_name)).replace("_", "-");
obj.find(&name[..]).and_then(|o| o.as_string().and_then(|s| { obj.remove_key(&name[..]).and_then(|o| o.as_string().and_then(|s| {
match s.parse::<MergeFunctions>() { match s.parse::<MergeFunctions>() {
Ok(mergefunc) => base.$key_name = mergefunc, Ok(mergefunc) => base.$key_name = mergefunc,
_ => return Some(Err(format!("'{}' is not a valid value for \ _ => return Some(Err(format!("'{}' is not a valid value for \
@ -1522,7 +1555,7 @@ impl Target {
} ); } );
($key_name:ident, RelocModel) => ( { ($key_name:ident, RelocModel) => ( {
let name = (stringify!($key_name)).replace("_", "-"); let name = (stringify!($key_name)).replace("_", "-");
obj.find(&name[..]).and_then(|o| o.as_string().and_then(|s| { obj.remove_key(&name[..]).and_then(|o| o.as_string().and_then(|s| {
match s.parse::<RelocModel>() { match s.parse::<RelocModel>() {
Ok(relocation_model) => base.$key_name = relocation_model, Ok(relocation_model) => base.$key_name = relocation_model,
_ => return Some(Err(format!("'{}' is not a valid relocation model. \ _ => return Some(Err(format!("'{}' is not a valid relocation model. \
@ -1534,7 +1567,7 @@ impl Target {
} ); } );
($key_name:ident, CodeModel) => ( { ($key_name:ident, CodeModel) => ( {
let name = (stringify!($key_name)).replace("_", "-"); let name = (stringify!($key_name)).replace("_", "-");
obj.find(&name[..]).and_then(|o| o.as_string().and_then(|s| { obj.remove_key(&name[..]).and_then(|o| o.as_string().and_then(|s| {
match s.parse::<CodeModel>() { match s.parse::<CodeModel>() {
Ok(code_model) => base.$key_name = Some(code_model), Ok(code_model) => base.$key_name = Some(code_model),
_ => return Some(Err(format!("'{}' is not a valid code model. \ _ => return Some(Err(format!("'{}' is not a valid code model. \
@ -1546,7 +1579,7 @@ impl Target {
} ); } );
($key_name:ident, TlsModel) => ( { ($key_name:ident, TlsModel) => ( {
let name = (stringify!($key_name)).replace("_", "-"); let name = (stringify!($key_name)).replace("_", "-");
obj.find(&name[..]).and_then(|o| o.as_string().and_then(|s| { obj.remove_key(&name[..]).and_then(|o| o.as_string().and_then(|s| {
match s.parse::<TlsModel>() { match s.parse::<TlsModel>() {
Ok(tls_model) => base.$key_name = tls_model, Ok(tls_model) => base.$key_name = tls_model,
_ => return Some(Err(format!("'{}' is not a valid TLS model. \ _ => return Some(Err(format!("'{}' is not a valid TLS model. \
@ -1558,7 +1591,7 @@ impl Target {
} ); } );
($key_name:ident, PanicStrategy) => ( { ($key_name:ident, PanicStrategy) => ( {
let name = (stringify!($key_name)).replace("_", "-"); let name = (stringify!($key_name)).replace("_", "-");
obj.find(&name[..]).and_then(|o| o.as_string().and_then(|s| { obj.remove_key(&name[..]).and_then(|o| o.as_string().and_then(|s| {
match s { match s {
"unwind" => base.$key_name = PanicStrategy::Unwind, "unwind" => base.$key_name = PanicStrategy::Unwind,
"abort" => base.$key_name = PanicStrategy::Abort, "abort" => base.$key_name = PanicStrategy::Abort,
@ -1571,7 +1604,7 @@ impl Target {
} ); } );
($key_name:ident, RelroLevel) => ( { ($key_name:ident, RelroLevel) => ( {
let name = (stringify!($key_name)).replace("_", "-"); let name = (stringify!($key_name)).replace("_", "-");
obj.find(&name[..]).and_then(|o| o.as_string().and_then(|s| { obj.remove_key(&name[..]).and_then(|o| o.as_string().and_then(|s| {
match s.parse::<RelroLevel>() { match s.parse::<RelroLevel>() {
Ok(level) => base.$key_name = level, Ok(level) => base.$key_name = level,
_ => return Some(Err(format!("'{}' is not a valid value for \ _ => return Some(Err(format!("'{}' is not a valid value for \
@ -1583,7 +1616,7 @@ impl Target {
} ); } );
($key_name:ident, SplitDebuginfo) => ( { ($key_name:ident, SplitDebuginfo) => ( {
let name = (stringify!($key_name)).replace("_", "-"); let name = (stringify!($key_name)).replace("_", "-");
obj.find(&name[..]).and_then(|o| o.as_string().and_then(|s| { obj.remove_key(&name[..]).and_then(|o| o.as_string().and_then(|s| {
match s.parse::<SplitDebuginfo>() { match s.parse::<SplitDebuginfo>() {
Ok(level) => base.$key_name = level, Ok(level) => base.$key_name = level,
_ => return Some(Err(format!("'{}' is not a valid value for \ _ => return Some(Err(format!("'{}' is not a valid value for \
@ -1595,23 +1628,31 @@ impl Target {
} ); } );
($key_name:ident, list) => ( { ($key_name:ident, list) => ( {
let name = (stringify!($key_name)).replace("_", "-"); let name = (stringify!($key_name)).replace("_", "-");
if let Some(v) = obj.find(&name).and_then(Json::as_array) { if let Some(j) = obj.remove_key(&name){
if let Some(v) = Json::as_array(&j) {
base.$key_name = v.iter() base.$key_name = v.iter()
.map(|a| a.as_string().unwrap().to_string()) .map(|a| a.as_string().unwrap().to_string())
.collect(); .collect();
} else {
incorrect_type.push(name)
}
} }
} ); } );
($key_name:ident, opt_list) => ( { ($key_name:ident, opt_list) => ( {
let name = (stringify!($key_name)).replace("_", "-"); let name = (stringify!($key_name)).replace("_", "-");
if let Some(v) = obj.find(&name).and_then(Json::as_array) { if let Some(j) = obj.remove_key(&name) {
if let Some(v) = Json::as_array(&j) {
base.$key_name = Some(v.iter() base.$key_name = Some(v.iter()
.map(|a| a.as_string().unwrap().to_string()) .map(|a| a.as_string().unwrap().to_string())
.collect()); .collect());
} else {
incorrect_type.push(name)
}
} }
} ); } );
($key_name:ident, optional) => ( { ($key_name:ident, optional) => ( {
let name = (stringify!($key_name)).replace("_", "-"); let name = (stringify!($key_name)).replace("_", "-");
if let Some(o) = obj.find(&name[..]) { if let Some(o) = obj.remove_key(&name[..]) {
base.$key_name = o base.$key_name = o
.as_string() .as_string()
.map(|s| s.to_string() ); .map(|s| s.to_string() );
@ -1619,7 +1660,7 @@ impl Target {
} ); } );
($key_name:ident, LldFlavor) => ( { ($key_name:ident, LldFlavor) => ( {
let name = (stringify!($key_name)).replace("_", "-"); let name = (stringify!($key_name)).replace("_", "-");
obj.find(&name[..]).and_then(|o| o.as_string().and_then(|s| { obj.remove_key(&name[..]).and_then(|o| o.as_string().and_then(|s| {
if let Some(flavor) = LldFlavor::from_str(&s) { if let Some(flavor) = LldFlavor::from_str(&s) {
base.$key_name = flavor; base.$key_name = flavor;
} else { } else {
@ -1633,7 +1674,7 @@ impl Target {
} ); } );
($key_name:ident, LinkerFlavor) => ( { ($key_name:ident, LinkerFlavor) => ( {
let name = (stringify!($key_name)).replace("_", "-"); let name = (stringify!($key_name)).replace("_", "-");
obj.find(&name[..]).and_then(|o| o.as_string().and_then(|s| { obj.remove_key(&name[..]).and_then(|o| o.as_string().and_then(|s| {
match LinkerFlavor::from_str(s) { match LinkerFlavor::from_str(s) {
Some(linker_flavor) => base.$key_name = linker_flavor, Some(linker_flavor) => base.$key_name = linker_flavor,
_ => return Some(Err(format!("'{}' is not a valid value for linker-flavor. \ _ => return Some(Err(format!("'{}' is not a valid value for linker-flavor. \
@ -1644,7 +1685,7 @@ impl Target {
} ); } );
($key_name:ident, StackProbeType) => ( { ($key_name:ident, StackProbeType) => ( {
let name = (stringify!($key_name)).replace("_", "-"); let name = (stringify!($key_name)).replace("_", "-");
obj.find(&name[..]).and_then(|o| match StackProbeType::from_json(o) { obj.remove_key(&name[..]).and_then(|o| match StackProbeType::from_json(&o) {
Ok(v) => { Ok(v) => {
base.$key_name = v; base.$key_name = v;
Some(Ok(())) Some(Ok(()))
@ -1656,7 +1697,8 @@ impl Target {
} ); } );
($key_name:ident, SanitizerSet) => ( { ($key_name:ident, SanitizerSet) => ( {
let name = (stringify!($key_name)).replace("_", "-"); let name = (stringify!($key_name)).replace("_", "-");
obj.find(&name[..]).and_then(|o| o.as_array()).and_then(|a| { if let Some(o) = obj.remove_key(&name[..]) {
if let Some(a) = o.as_array() {
for s in a { for s in a {
base.$key_name |= match s.as_string() { base.$key_name |= match s.as_string() {
Some("address") => SanitizerSet::ADDRESS, Some("address") => SanitizerSet::ADDRESS,
@ -1664,17 +1706,20 @@ impl Target {
Some("memory") => SanitizerSet::MEMORY, Some("memory") => SanitizerSet::MEMORY,
Some("thread") => SanitizerSet::THREAD, Some("thread") => SanitizerSet::THREAD,
Some("hwaddress") => SanitizerSet::HWADDRESS, Some("hwaddress") => SanitizerSet::HWADDRESS,
Some(s) => return Some(Err(format!("unknown sanitizer {}", s))), Some(s) => return Err(format!("unknown sanitizer {}", s)),
_ => return Some(Err(format!("not a string: {:?}", s))), _ => return Err(format!("not a string: {:?}", s)),
}; };
} }
Some(Ok(())) } else {
}).unwrap_or(Ok(())) incorrect_type.push(name)
}
}
Ok::<(), String>(())
} ); } );
($key_name:ident, crt_objects_fallback) => ( { ($key_name:ident, crt_objects_fallback) => ( {
let name = (stringify!($key_name)).replace("_", "-"); let name = (stringify!($key_name)).replace("_", "-");
obj.find(&name[..]).and_then(|o| o.as_string().and_then(|s| { obj.remove_key(&name[..]).and_then(|o| o.as_string().and_then(|s| {
match s.parse::<CrtObjectsFallback>() { match s.parse::<CrtObjectsFallback>() {
Ok(fallback) => base.$key_name = Some(fallback), Ok(fallback) => base.$key_name = Some(fallback),
_ => return Some(Err(format!("'{}' is not a valid CRT objects fallback. \ _ => return Some(Err(format!("'{}' is not a valid CRT objects fallback. \
@ -1685,7 +1730,7 @@ impl Target {
} ); } );
($key_name:ident, link_objects) => ( { ($key_name:ident, link_objects) => ( {
let name = (stringify!($key_name)).replace("_", "-"); let name = (stringify!($key_name)).replace("_", "-");
if let Some(val) = obj.find(&name[..]) { if let Some(val) = obj.remove_key(&name[..]) {
let obj = val.as_object().ok_or_else(|| format!("{}: expected a \ let obj = val.as_object().ok_or_else(|| format!("{}: expected a \
JSON object with fields per CRT object kind.", name))?; JSON object with fields per CRT object kind.", name))?;
let mut args = CrtObjects::new(); let mut args = CrtObjects::new();
@ -1713,7 +1758,7 @@ impl Target {
} ); } );
($key_name:ident, link_args) => ( { ($key_name:ident, link_args) => ( {
let name = (stringify!($key_name)).replace("_", "-"); let name = (stringify!($key_name)).replace("_", "-");
if let Some(val) = obj.find(&name[..]) { if let Some(val) = obj.remove_key(&name[..]) {
let obj = val.as_object().ok_or_else(|| format!("{}: expected a \ let obj = val.as_object().ok_or_else(|| format!("{}: expected a \
JSON object with fields per linker-flavor.", name))?; JSON object with fields per linker-flavor.", name))?;
let mut args = LinkArgs::new(); let mut args = LinkArgs::new();
@ -1740,7 +1785,8 @@ impl Target {
} ); } );
($key_name:ident, env) => ( { ($key_name:ident, env) => ( {
let name = (stringify!($key_name)).replace("_", "-"); let name = (stringify!($key_name)).replace("_", "-");
if let Some(a) = obj.find(&name[..]).and_then(|o| o.as_array()) { if let Some(o) = obj.remove_key(&name[..]) {
if let Some(a) = o.as_array() {
for o in a { for o in a {
if let Some(s) = o.as_string() { if let Some(s) = o.as_string() {
let p = s.split('=').collect::<Vec<_>>(); let p = s.split('=').collect::<Vec<_>>();
@ -1751,11 +1797,14 @@ impl Target {
} }
} }
} }
} else {
incorrect_type.push(name)
}
} }
} ); } );
($key_name:ident, Option<Abi>) => ( { ($key_name:ident, Option<Abi>) => ( {
let name = (stringify!($key_name)).replace("_", "-"); let name = (stringify!($key_name)).replace("_", "-");
obj.find(&name[..]).and_then(|o| o.as_string().and_then(|s| { obj.remove_key(&name[..]).and_then(|o| o.as_string().and_then(|s| {
match lookup_abi(s) { match lookup_abi(s) {
Some(abi) => base.$key_name = Some(abi), Some(abi) => base.$key_name = Some(abi),
_ => return Some(Err(format!("'{}' is not a valid value for abi", s))), _ => return Some(Err(format!("'{}' is not a valid value for abi", s))),
@ -1764,20 +1813,26 @@ impl Target {
})).unwrap_or(Ok(())) })).unwrap_or(Ok(()))
} ); } );
($key_name:ident, TargetFamilies) => ( { ($key_name:ident, TargetFamilies) => ( {
let value = obj.find("target-family"); if let Some(value) = obj.remove_key("target-family") {
if let Some(v) = value.and_then(Json::as_array) { if let Some(v) = Json::as_array(&value) {
base.$key_name = v.iter() base.$key_name = v.iter()
.map(|a| a.as_string().unwrap().to_string()) .map(|a| a.as_string().unwrap().to_string())
.collect(); .collect();
} else if let Some(v) = value.and_then(Json::as_string) { } else if let Some(v) = Json::as_string(&value) {
base.$key_name = vec![v.to_string()]; base.$key_name = vec![v.to_string()];
} }
}
} ); } );
} }
if let Some(s) = obj.find("target-endian").and_then(Json::as_string) { if let Some(j) = obj.remove_key("target-endian") {
if let Some(s) = Json::as_string(&j) {
base.endian = s.parse()?; base.endian = s.parse()?;
} else {
incorrect_type.push("target-endian".to_string())
} }
}
key!(is_builtin, bool); key!(is_builtin, bool);
key!(c_int_width = "target-c-int-width"); key!(c_int_width = "target-c-int-width");
key!(os); key!(os);
@ -1877,7 +1932,8 @@ impl Target {
// NB: The old name is deprecated, but support for it is retained for // NB: The old name is deprecated, but support for it is retained for
// compatibility. // compatibility.
for name in ["abi-blacklist", "unsupported-abis"].iter() { for name in ["abi-blacklist", "unsupported-abis"].iter() {
if let Some(array) = obj.find(name).and_then(Json::as_array) { if let Some(j) = obj.remove_key(name) {
if let Some(array) = Json::as_array(&j) {
for name in array.iter().filter_map(|abi| abi.as_string()) { for name in array.iter().filter_map(|abi| abi.as_string()) {
match lookup_abi(name) { match lookup_abi(name) {
Some(abi) => { Some(abi) => {
@ -1901,8 +1957,14 @@ impl Target {
} }
} }
} }
}
Ok(base) // Each field should have been read using `Json::remove_key` so any keys remaining are unused.
let remaining_keys = obj.as_object().ok_or("Expected JSON object for target")?.keys();
Ok((
base,
TargetWarnings { unused_fields: remaining_keys.cloned().collect(), incorrect_type },
))
} }
/// Search for a JSON file specifying the given target triple. /// Search for a JSON file specifying the given target triple.
@ -1914,12 +1976,15 @@ impl Target {
/// ///
/// The error string could come from any of the APIs called, including filesystem access and /// The error string could come from any of the APIs called, including filesystem access and
/// JSON decoding. /// JSON decoding.
pub fn search(target_triple: &TargetTriple, sysroot: &PathBuf) -> Result<Target, String> { pub fn search(
target_triple: &TargetTriple,
sysroot: &PathBuf,
) -> Result<(Target, TargetWarnings), String> {
use rustc_serialize::json; use rustc_serialize::json;
use std::env; use std::env;
use std::fs; use std::fs;
fn load_file(path: &Path) -> Result<Target, String> { fn load_file(path: &Path) -> Result<(Target, TargetWarnings), String> {
let contents = fs::read(path).map_err(|e| e.to_string())?; let contents = fs::read(path).map_err(|e| e.to_string())?;
let obj = json::from_reader(&mut &contents[..]).map_err(|e| e.to_string())?; let obj = json::from_reader(&mut &contents[..]).map_err(|e| e.to_string())?;
Target::from_json(obj) Target::from_json(obj)
@ -1929,7 +1994,7 @@ impl Target {
TargetTriple::TargetTriple(ref target_triple) => { TargetTriple::TargetTriple(ref target_triple) => {
// check if triple is in list of built-in targets // check if triple is in list of built-in targets
if let Some(t) = load_builtin(target_triple) { if let Some(t) = load_builtin(target_triple) {
return Ok(t); return Ok((t, TargetWarnings::empty()));
} }
// search for a file named `target_triple`.json in RUST_TARGET_PATH // search for a file named `target_triple`.json in RUST_TARGET_PATH

View file

@ -3,7 +3,7 @@ use super::super::*;
// Test target self-consistency and JSON encoding/decoding roundtrip. // Test target self-consistency and JSON encoding/decoding roundtrip.
pub(super) fn test_target(target: Target) { pub(super) fn test_target(target: Target) {
target.check_consistency(); target.check_consistency();
assert_eq!(Target::from_json(target.to_json()), Ok(target)); assert_eq!(Target::from_json(target.to_json()).map(|(j, _)| j), Ok(target));
} }
impl Target { impl Target {

View file

@ -0,0 +1,59 @@
use crate::spec::Target;
use rustc_serialize::json::Json;
use std::str::FromStr;
#[test]
fn report_unused_fields() {
let json = Json::from_str(
r#"
{
"arch": "powerpc64",
"data-layout": "e-m:e-i64:64-n32:64",
"llvm-target": "powerpc64le-elf",
"target-pointer-width": "64",
"code-mode": "foo"
}
"#,
)
.unwrap();
let warnings = Target::from_json(json).unwrap().1;
assert_eq!(warnings.warning_messages().len(), 1);
assert!(warnings.warning_messages().join("\n").contains("code-mode"));
}
#[test]
fn report_incorrect_json_type() {
let json = Json::from_str(
r#"
{
"arch": "powerpc64",
"data-layout": "e-m:e-i64:64-n32:64",
"llvm-target": "powerpc64le-elf",
"target-pointer-width": "64",
"link-env-remove": "foo"
}
"#,
)
.unwrap();
let warnings = Target::from_json(json).unwrap().1;
assert_eq!(warnings.warning_messages().len(), 1);
assert!(warnings.warning_messages().join("\n").contains("link-env-remove"));
}
#[test]
fn no_warnings_for_valid_target() {
let json = Json::from_str(
r#"
{
"arch": "powerpc64",
"data-layout": "e-m:e-i64:64-n32:64",
"llvm-target": "powerpc64le-elf",
"target-pointer-width": "64",
"link-env-remove": ["foo"]
}
"#,
)
.unwrap();
let warnings = Target::from_json(json).unwrap().1;
assert_eq!(warnings.warning_messages().len(), 0);
}