| use core::fmt; |
| use std::str::FromStr; |
| |
| #[cfg(feature = "nightly")] |
| use rustc_macros::{Decodable_NoContext, Encodable_NoContext, StableHash}; |
| |
| #[derive(Default, Clone, Copy, PartialEq, Eq, Hash)] |
| #[cfg_attr(feature = "nightly", derive(Encodable_NoContext, Decodable_NoContext, StableHash))] |
| pub struct SanitizerSet(u16); |
| bitflags::bitflags! { |
| impl SanitizerSet: u16 { |
| const ADDRESS = 1 << 0; |
| const LEAK = 1 << 1; |
| const MEMORY = 1 << 2; |
| const THREAD = 1 << 3; |
| const HWADDRESS = 1 << 4; |
| const CFI = 1 << 5; |
| const MEMTAG = 1 << 6; |
| const SHADOWCALLSTACK = 1 << 7; |
| const KCFI = 1 << 8; |
| const KERNELADDRESS = 1 << 9; |
| const KERNELHWADDRESS = 1 << 10; |
| const SAFESTACK = 1 << 11; |
| const DATAFLOW = 1 << 12; |
| const REALTIME = 1 << 13; |
| } |
| } |
| |
| #[cfg(feature = "nightly")] |
| rustc_data_structures::external_bitflags_debug! { SanitizerSet } |
| |
| impl SanitizerSet { |
| // Taken from LLVM's sanitizer compatibility logic: |
| // https://github.com/llvm/llvm-project/blob/release/18.x/clang/lib/Driver/SanitizerArgs.cpp#L512 |
| const MUTUALLY_EXCLUSIVE: &'static [(SanitizerSet, SanitizerSet)] = &[ |
| (SanitizerSet::ADDRESS, SanitizerSet::MEMORY), |
| (SanitizerSet::ADDRESS, SanitizerSet::THREAD), |
| (SanitizerSet::ADDRESS, SanitizerSet::HWADDRESS), |
| (SanitizerSet::ADDRESS, SanitizerSet::MEMTAG), |
| (SanitizerSet::ADDRESS, SanitizerSet::KERNELADDRESS), |
| (SanitizerSet::ADDRESS, SanitizerSet::KERNELHWADDRESS), |
| (SanitizerSet::ADDRESS, SanitizerSet::SAFESTACK), |
| (SanitizerSet::LEAK, SanitizerSet::MEMORY), |
| (SanitizerSet::LEAK, SanitizerSet::THREAD), |
| (SanitizerSet::LEAK, SanitizerSet::KERNELADDRESS), |
| (SanitizerSet::LEAK, SanitizerSet::KERNELHWADDRESS), |
| (SanitizerSet::LEAK, SanitizerSet::SAFESTACK), |
| (SanitizerSet::MEMORY, SanitizerSet::THREAD), |
| (SanitizerSet::MEMORY, SanitizerSet::HWADDRESS), |
| (SanitizerSet::MEMORY, SanitizerSet::KERNELADDRESS), |
| (SanitizerSet::MEMORY, SanitizerSet::KERNELHWADDRESS), |
| (SanitizerSet::MEMORY, SanitizerSet::SAFESTACK), |
| (SanitizerSet::THREAD, SanitizerSet::HWADDRESS), |
| (SanitizerSet::THREAD, SanitizerSet::KERNELADDRESS), |
| (SanitizerSet::THREAD, SanitizerSet::KERNELHWADDRESS), |
| (SanitizerSet::THREAD, SanitizerSet::SAFESTACK), |
| (SanitizerSet::HWADDRESS, SanitizerSet::MEMTAG), |
| (SanitizerSet::HWADDRESS, SanitizerSet::KERNELADDRESS), |
| (SanitizerSet::HWADDRESS, SanitizerSet::KERNELHWADDRESS), |
| (SanitizerSet::HWADDRESS, SanitizerSet::SAFESTACK), |
| (SanitizerSet::CFI, SanitizerSet::KCFI), |
| (SanitizerSet::MEMTAG, SanitizerSet::KERNELADDRESS), |
| (SanitizerSet::MEMTAG, SanitizerSet::KERNELHWADDRESS), |
| (SanitizerSet::KERNELADDRESS, SanitizerSet::KERNELHWADDRESS), |
| (SanitizerSet::KERNELADDRESS, SanitizerSet::SAFESTACK), |
| (SanitizerSet::KERNELHWADDRESS, SanitizerSet::SAFESTACK), |
| ]; |
| |
| /// Return sanitizer's name |
| /// |
| /// Returns none if the flags is a set of sanitizers numbering not exactly one. |
| pub fn as_str(self) -> Option<&'static str> { |
| Some(match self { |
| SanitizerSet::ADDRESS => "address", |
| SanitizerSet::CFI => "cfi", |
| SanitizerSet::DATAFLOW => "dataflow", |
| SanitizerSet::KCFI => "kcfi", |
| SanitizerSet::KERNELADDRESS => "kernel-address", |
| SanitizerSet::KERNELHWADDRESS => "kernel-hwaddress", |
| SanitizerSet::LEAK => "leak", |
| SanitizerSet::MEMORY => "memory", |
| SanitizerSet::MEMTAG => "memtag", |
| SanitizerSet::SAFESTACK => "safestack", |
| SanitizerSet::SHADOWCALLSTACK => "shadow-call-stack", |
| SanitizerSet::THREAD => "thread", |
| SanitizerSet::HWADDRESS => "hwaddress", |
| SanitizerSet::REALTIME => "realtime", |
| _ => return None, |
| }) |
| } |
| |
| pub fn mutually_exclusive(self) -> Option<(SanitizerSet, SanitizerSet)> { |
| Self::MUTUALLY_EXCLUSIVE |
| .into_iter() |
| .find(|&(a, b)| self.contains(*a) && self.contains(*b)) |
| .copied() |
| } |
| } |
| |
| /// Formats a sanitizer set as a comma separated list of sanitizers' names. |
| impl fmt::Display for SanitizerSet { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| let mut first = true; |
| for s in *self { |
| let name = s.as_str().unwrap_or_else(|| panic!("unrecognized sanitizer {s:?}")); |
| if !first { |
| f.write_str(", ")?; |
| } |
| f.write_str(name)?; |
| first = false; |
| } |
| Ok(()) |
| } |
| } |
| |
| impl FromStr for SanitizerSet { |
| type Err = String; |
| fn from_str(s: &str) -> Result<Self, Self::Err> { |
| Ok(match s { |
| "address" => SanitizerSet::ADDRESS, |
| "cfi" => SanitizerSet::CFI, |
| "dataflow" => SanitizerSet::DATAFLOW, |
| "kcfi" => SanitizerSet::KCFI, |
| "kernel-address" => SanitizerSet::KERNELADDRESS, |
| "kernel-hwaddress" => SanitizerSet::KERNELHWADDRESS, |
| "leak" => SanitizerSet::LEAK, |
| "memory" => SanitizerSet::MEMORY, |
| "memtag" => SanitizerSet::MEMTAG, |
| "safestack" => SanitizerSet::SAFESTACK, |
| "shadow-call-stack" => SanitizerSet::SHADOWCALLSTACK, |
| "thread" => SanitizerSet::THREAD, |
| "hwaddress" => SanitizerSet::HWADDRESS, |
| "realtime" => SanitizerSet::REALTIME, |
| s => return Err(format!("unknown sanitizer {s}")), |
| }) |
| } |
| } |
| |
| impl schemars::JsonSchema for SanitizerSet { |
| fn schema_name() -> std::borrow::Cow<'static, str> { |
| "SanitizerSet".into() |
| } |
| fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema { |
| let all = Self::all().iter().map(|sanitizer| sanitizer.as_str()).collect::<Vec<_>>(); |
| schemars::json_schema! ({ |
| "type": "string", |
| "enum": all, |
| }) |
| } |
| } |
| |
| impl<'de> serde::Deserialize<'de> for SanitizerSet { |
| fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> |
| where |
| D: serde::Deserializer<'de>, |
| { |
| let s = String::deserialize(deserializer)?; |
| FromStr::from_str(&s).map_err(serde::de::Error::custom) |
| } |
| } |