Skip to content

Commit 1c7fcb7

Browse files
authored
Makes deserialization of config and external annotation files strict (#181)
## What Changed? Denies unknown fields for parsing of build config and external annotation files ## Why Does It Need To? Reduces the chances of misconfiguration by a user, for example from a typo in in the external annotation files. ## Checklist - [x] Above description has been filled out so that upon quash merge we have a good record of what changed. - [ ] New functions, methods, types are documented. Old documentation is updated if necessary - [ ] Documentation in Notion has been updated - [ ] Tests for new behaviors are provided - [ ] New test suites (if any) ave been added to the CI tests (in `.github/workflows/rust.yml`) either as compiler test or integration test. *Or* justification for their omission from CI has been provided in this PR description.
1 parent 12578dd commit 1c7fcb7

3 files changed

Lines changed: 139 additions & 11 deletions

File tree

‎crates/paralegal-flow/src/ann/db.rs‎

Lines changed: 134 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use crate::{
1919
},
2020
Either, HashMap, HashSet,
2121
};
22+
use anyhow::Context;
2223
use flowistry::mir::FlowistryInput;
2324
use flowistry_pdg_construction::{
2425
body_cache::{local_or_remote_paths, BodyCache},
@@ -40,6 +41,7 @@ use rustc_serialize::Decodable;
4041

4142
use rustc_span::Span;
4243
use rustc_utils::cache::Cache;
44+
use serde::Deserialize;
4345
use serde_json::json;
4446

4547
use std::{
@@ -865,24 +867,67 @@ impl ExternalAnnotationEntry {
865867

866868
type RawExternalMarkers = HashMap<String, Vec<ExternalAnnotationEntry>>;
867869

870+
fn check_format(from_toml: &toml::Value) -> anyhow::Result<()> {
871+
use anyhow::bail;
872+
let Some(table) = from_toml.as_table() else {
873+
bail!("External annotations must be a table");
874+
};
875+
for (key, v) in table {
876+
let Some(arr) = v.as_array() else {
877+
bail!("External annotation entry for `{key}` must be an array");
878+
};
879+
for (i, entry) in arr.iter().enumerate() {
880+
let Some(e) = entry.as_table() else {
881+
bail!("External annotation entry for `{key}.[{i}]` must be a table");
882+
};
883+
for k in e.keys() {
884+
if k != "marker"
885+
&& k != "markers"
886+
&& k != "on_argument"
887+
&& k != "on_return"
888+
&& k != "refinements"
889+
{
890+
bail!("External annotation entry for `{key}.[{i}]` may only have `marker`, `markers`, `on_argument`, `on_return` or `refinements` fields");
891+
}
892+
}
893+
}
894+
}
895+
Ok(())
896+
}
897+
898+
fn parse_external_marker_file(s: &str) -> anyhow::Result<RawExternalMarkers> {
899+
let from_toml = toml::from_str(s)?;
900+
check_format(&from_toml)?;
901+
902+
Ok(RawExternalMarkers::deserialize(
903+
serde::de::IntoDeserializer::into_deserializer(from_toml),
904+
)?)
905+
}
906+
868907
/// Given the TOML of external annotations we have parsed, resolve the paths
869908
/// (keys of the map) to [`DefId`]s.
870909
fn resolve_external_markers(opts: &Args, tcx: TyCtxt) -> ExternalMarkers {
871910
//let relaxed = opts.relaxed();
872911
let relaxed = false;
873912
if let Some(annotation_file) = opts.marker_control().external_annotations() {
874-
let from_toml: RawExternalMarkers = toml::from_str(
875-
&std::fs::read_to_string(annotation_file).unwrap_or_else(|_| {
876-
panic!(
877-
"Could not open file {}",
878-
annotation_file
879-
.canonicalize()
880-
.unwrap_or_else(|_| annotation_file.to_path_buf())
881-
.display()
913+
let data = std::fs::read_to_string(annotation_file).unwrap_or_else(|_| {
914+
panic!(
915+
"Could not open file {}",
916+
annotation_file
917+
.canonicalize()
918+
.unwrap_or_else(|_| annotation_file.to_path_buf())
919+
.display()
920+
)
921+
});
922+
let from_toml = parse_external_marker_file(&data)
923+
.with_context(|| {
924+
anyhow::anyhow!(
925+
"When parsing external annotation file {}",
926+
annotation_file.display()
882927
)
883-
}),
884-
)
885-
.unwrap();
928+
})
929+
.unwrap();
930+
886931
let new_map: ExternalMarkers = from_toml
887932
.iter()
888933
.filter_map(|(path, entries)| {
@@ -902,3 +947,81 @@ fn resolve_external_markers(opts: &Args, tcx: TyCtxt) -> ExternalMarkers {
902947
HashMap::new()
903948
}
904949
}
950+
951+
#[cfg(test)]
952+
mod test {
953+
use crate::ann::db::parse_external_marker_file;
954+
955+
#[test]
956+
fn test_parse_marker_file() {
957+
let examples = [
958+
(
959+
"[[\"std::vec::Vec\"]]
960+
marker = \"test\"",
961+
true,
962+
),
963+
(
964+
"[[\"std::vec::Vec\"]]
965+
marker = \"test\"
966+
on_return = true",
967+
true,
968+
),
969+
(
970+
"[[\"std::vec::Vec\"]]
971+
marker = \"test\"
972+
on_argument = [0]",
973+
true,
974+
),
975+
(
976+
"[[\"std::vec::Vec\"]]
977+
marker = \"test\"
978+
on_argument = [0]
979+
on_return = true",
980+
true,
981+
),
982+
// Technically this should also fail, but we don't check this
983+
// property during deserialization, but later
984+
// (
985+
// "[[\"std::vec::Vec\"]]
986+
// on_argument = [0]",
987+
// false,
988+
// ),
989+
(
990+
"[[\"std::vec::Vec\"]]
991+
marker = \"test\"
992+
on-argument = [0]
993+
on_return = true",
994+
false,
995+
),
996+
(
997+
"[[\"std::vec::Vec\"]]
998+
marker = \"test\"
999+
on_argument = [0]
1000+
on-return = true",
1001+
false,
1002+
),
1003+
(
1004+
"[[\"std::vec::Vec\"]]
1005+
marker = \"test\"
1006+
on_argument = [0]
1007+
on_return = true
1008+
extra = []",
1009+
false,
1010+
),
1011+
];
1012+
1013+
for (input, expected) in examples {
1014+
let ret = parse_external_marker_file(input);
1015+
assert_eq!(
1016+
ret.is_ok(),
1017+
expected,
1018+
"Expected {input} to {}, but {}",
1019+
if expected { "succeed" } else { "fail" },
1020+
match ret {
1021+
Ok(_) => "succeeded".to_string(),
1022+
Err(e) => format!("failed with error: {}", e),
1023+
}
1024+
);
1025+
}
1026+
}
1027+
}

‎crates/paralegal-flow/src/ann/mod.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use crate::{discover::AttrMatchT, sym_vec, utils::MetaItemMatch};
3434
#[derive(
3535
PartialEq, Eq, Debug, Clone, Deserialize, Serialize, strum::EnumIs, Encodable, Decodable,
3636
)]
37+
#[serde(deny_unknown_fields)]
3738
pub enum Annotation {
3839
Marker(MarkerAnnotation),
3940
OType(#[serde(with = "rustc_proxies::DefId")] TypeId),
@@ -71,6 +72,7 @@ pub type VerificationHash = u128;
7172
#[derive(
7273
PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Serialize, Deserialize, Encodable, Decodable,
7374
)]
75+
#[serde(deny_unknown_fields)]
7476
pub struct ExceptionAnnotation {
7577
/// The value of the verification hash we found in the annotation. Is `None`
7678
/// if there was no verification hash in the annotation.

‎crates/paralegal-flow/src/args.rs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,7 @@ impl DumpArgs {
664664

665665
/// Dependency specific configuration
666666
#[derive(serde::Serialize, serde::Deserialize, Default, Debug)]
667+
#[serde(deny_unknown_fields)]
667668
pub struct DepConfig {
668669
#[serde(default, alias = "rust-features")]
669670
/// Additional rust features to enable
@@ -672,6 +673,7 @@ pub struct DepConfig {
672673

673674
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
674675
#[serde(tag = "mode", rename_all = "kebab-case")]
676+
#[serde(deny_unknown_fields)]
675677
pub enum Stub {
676678
#[serde(rename_all = "kebab-case")]
677679
/// Replaces the result of a call to a higher-order function with a call to
@@ -685,6 +687,7 @@ pub enum Stub {
685687
/// Additional configuration for the build process/rustc
686688
#[derive(serde::Deserialize, serde::Serialize, Default, Debug)]
687689
#[serde(rename_all = "kebab-case")]
690+
#[serde(deny_unknown_fields)]
688691
pub struct BuildConfig {
689692
/// Dependency specific configuration
690693
#[serde(default)]

0 commit comments

Comments
 (0)