1use serde::{Deserialize, Serialize};
10
11pub use vyre_foundation::soundness::{
12 validate_dynamic_pipeline, validate_dynamic_primitive, validate_pipeline, validate_primitive,
13 DynamicPrimitiveSoundness, DynamicSoundnessViolation, PrecisionContract, PrimitiveSoundness,
14 Soundness, SoundnessTagged, SoundnessViolation,
15};
16
17pub const SHARED_FACT_SCHEMA_VERSION: u16 = 1;
19
20#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
22pub enum SharedFactKind {
23 Source,
25 Sink,
27 Taint,
29 Sanitizer,
31 GraphEdge,
33 BorrowLoan,
35 BorrowOrigin,
37 BorrowSubset,
39 Dominance,
41 Range,
43 Witness,
45}
46
47impl SharedFactKind {
48 #[must_use]
50 pub const fn wire_tag(self) -> &'static str {
51 match self {
52 Self::Source => "source",
53 Self::Sink => "sink",
54 Self::Taint => "taint",
55 Self::Sanitizer => "sanitizer",
56 Self::GraphEdge => "graph_edge",
57 Self::BorrowLoan => "borrow_loan",
58 Self::BorrowOrigin => "borrow_origin",
59 Self::BorrowSubset => "borrow_subset",
60 Self::Dominance => "dominance",
61 Self::Range => "range",
62 Self::Witness => "witness",
63 }
64 }
65}
66
67#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
69pub struct SharedFactHeader {
70 pub schema_version: u16,
72 pub producer: String,
74 pub kind: SharedFactKind,
76 pub fact_id: u64,
78 pub subject: u64,
80 pub object: Option<u64>,
82 pub aux: Option<u64>,
84 pub file_id: u32,
86 pub start_byte: u32,
88 pub end_byte: u32,
90 pub soundness: Soundness,
92}
93
94impl SharedFactHeader {
95 #[must_use]
97 pub fn new(
98 producer: impl Into<String>,
99 kind: SharedFactKind,
100 fact_id: u64,
101 subject: u64,
102 soundness: Soundness,
103 ) -> Self {
104 Self {
105 schema_version: SHARED_FACT_SCHEMA_VERSION,
106 producer: producer.into(),
107 kind,
108 fact_id,
109 subject,
110 object: None,
111 aux: None,
112 file_id: 0,
113 start_byte: 0,
114 end_byte: 0,
115 soundness,
116 }
117 }
118
119 #[must_use]
121 pub const fn with_object(mut self, object: u64) -> Self {
122 self.object = Some(object);
123 self
124 }
125
126 #[must_use]
128 pub const fn with_aux(mut self, aux: u64) -> Self {
129 self.aux = Some(aux);
130 self
131 }
132
133 #[must_use]
135 pub const fn with_span(mut self, file_id: u32, start_byte: u32, end_byte: u32) -> Self {
136 self.file_id = file_id;
137 self.start_byte = start_byte;
138 self.end_byte = end_byte;
139 self
140 }
141
142 #[must_use]
149 pub fn wire_header(&self) -> String {
150 let object_token = self
151 .object
152 .map_or_else(|| "-".to_string(), |v| v.to_string());
153 let aux_token = self.aux.map_or_else(|| "-".to_string(), |v| v.to_string());
154 format!(
155 "schema=v{};producer={};kind={};fact_id={};subject={};object={};aux={};file={};start={};end={};soundness={:?}",
156 self.schema_version,
157 self.producer,
158 self.kind.wire_tag(),
159 self.fact_id,
160 self.subject,
161 object_token,
162 aux_token,
163 self.file_id,
164 self.start_byte,
165 self.end_byte,
166 self.soundness
167 )
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174
175 #[test]
176 fn c_security_source_fact_header_is_exact() {
177 let header =
178 SharedFactHeader::new("c-c11", SharedFactKind::Source, 1, 42, Soundness::Exact)
179 .with_span(7, 100, 120);
180
181 assert_eq!(
185 header.wire_header(),
186 "schema=v1;producer=c-c11;kind=source;fact_id=1;subject=42;object=-;aux=-;file=7;start=100;end=120;soundness=Exact"
187 );
188 }
189
190 #[test]
191 fn rust_borrow_subset_fact_header_is_exact() {
192 let header = SharedFactHeader::new(
193 "rustc-nll",
194 SharedFactKind::BorrowSubset,
195 9,
196 3,
197 Soundness::Exact,
198 )
199 .with_object(5)
200 .with_aux(11);
201
202 assert_eq!(
203 header.wire_header(),
204 "schema=v1;producer=rustc-nll;kind=borrow_subset;fact_id=9;subject=3;object=5;aux=11;file=0;start=0;end=0;soundness=Exact"
205 );
206 }
207
208 #[test]
212 fn wire_header_distinguishes_absent_object_from_zero_object() {
213 let no_object = SharedFactHeader::new(
214 "rustc-nll",
215 SharedFactKind::BorrowLoan,
216 1,
217 5,
218 Soundness::Exact,
219 );
220 let object_zero = no_object.clone().with_object(0);
221
222 assert_ne!(
224 no_object.wire_header(),
225 object_zero.wire_header(),
226 "wire_header must distinguish object=None from object=Some(0)"
227 );
228 assert!(
229 no_object.wire_header().contains("object=-"),
230 "absent object must encode as 'object=-', got: {}",
231 no_object.wire_header()
232 );
233 assert!(
234 object_zero.wire_header().contains("object=0"),
235 "object=Some(0) must encode as 'object=0', got: {}",
236 object_zero.wire_header()
237 );
238 }
239
240 #[test]
242 fn wire_header_distinguishes_absent_aux_from_zero_aux() {
243 let no_aux = SharedFactHeader::new(
244 "rustc-nll",
245 SharedFactKind::BorrowLoan,
246 2,
247 7,
248 Soundness::Exact,
249 );
250 let aux_zero = no_aux.clone().with_aux(0);
251
252 assert_ne!(
253 no_aux.wire_header(),
254 aux_zero.wire_header(),
255 "wire_header must distinguish aux=None from aux=Some(0)"
256 );
257 assert!(
258 no_aux.wire_header().contains("aux=-"),
259 "absent aux must encode as 'aux=-', got: {}",
260 no_aux.wire_header()
261 );
262 assert!(
263 aux_zero.wire_header().contains("aux=0"),
264 "aux=Some(0) must encode as 'aux=0', got: {}",
265 aux_zero.wire_header()
266 );
267 }
268
269 #[test]
270 fn external_witness_fact_header_is_exact() {
271 let header = SharedFactHeader::new(
272 "external-dataflow",
273 SharedFactKind::Witness,
274 13,
275 21,
276 Soundness::Exact,
277 )
278 .with_object(34)
279 .with_aux(55);
280
281 assert_eq!(
282 header.wire_header(),
283 "schema=v1;producer=external-dataflow;kind=witness;fact_id=13;subject=21;object=34;aux=55;file=0;start=0;end=0;soundness=Exact"
284 );
285 }
286}