1use crate::shape::{check_shape, checked_num_elements, infer};
5use crate::{Error, Graph, Limits, Source, ValueId};
6use std::collections::HashSet;
7
8impl Graph {
9 pub fn verify(&self) -> Result<(), Error> {
10 if self.outputs.is_empty() {
11 return Err(Error::NoOutputs);
12 }
13 if self.output_names.len() != self.outputs.len() {
14 return Err(Error::Verify(
15 "output names do not match the outputs".to_owned(),
16 ));
17 }
18 let mut names: HashSet<&str> = HashSet::new();
19 let mut input_ids = Vec::new();
20 for (index, value) in self.values.iter().enumerate() {
21 let id = ValueId(index as u32);
22 let label = self.label(id);
23 if value.ty.shape.contains(&0) {
24 return Err(Error::InvalidShape {
25 name: label,
26 shape: value.ty.shape.clone(),
27 reason: "dimensions must be at least 1",
28 });
29 }
30 if let Some(name) = &value.name
31 && !names.insert(name.as_str())
32 {
33 return Err(Error::DuplicateName { name: name.clone() });
34 }
35 match &value.source {
36 Source::Input => input_ids.push(id),
37 Source::Constant(data) => {
38 let expected = checked_num_elements(&value.ty.shape).ok_or_else(|| {
39 Error::Verify(format!("`{label}` element count overflows"))
40 })?;
41 if data.len() != expected {
42 return Err(Error::DataLength {
43 name: label,
44 shape: value.ty.shape.clone(),
45 expected,
46 found: data.len(),
47 });
48 }
49 }
50 Source::Node { op, inputs } => {
51 let mut types = Vec::with_capacity(inputs.len());
52 for input in inputs {
53 if input.index() >= index {
54 return Err(Error::Verify(format!(
55 "node `{label}` uses value %{} defined at or after it",
56 input.0
57 )));
58 }
59 types.push(&self.values[input.index()].ty);
60 }
61 let inferred = infer(&label, op, &types)?;
62 if inferred != value.ty {
63 return Err(Error::Verify(format!(
64 "node `{label}` stored type {:?} differs from inferred {:?}",
65 value.ty, inferred
66 )));
67 }
68 }
69 }
70 }
71 if input_ids != self.inputs {
72 return Err(Error::Verify(
73 "input list does not match the input values".to_owned(),
74 ));
75 }
76 for output in &self.outputs {
77 if output.index() >= self.values.len() {
78 return Err(Error::Verify(format!(
79 "output %{} is out of range",
80 output.0
81 )));
82 }
83 }
84 Ok(())
85 }
86
87 pub fn verify_limits(&self, limits: &Limits) -> Result<(), Error> {
91 if self.values.len() > limits.max_values {
92 return Err(Error::LimitExceeded {
93 what: "values",
94 found: self.values.len(),
95 limit: limits.max_values,
96 });
97 }
98 let mut total = 0usize;
99 for (index, value) in self.values.iter().enumerate() {
100 let name = self.label(ValueId(index as u32));
101 total = total.saturating_add(check_shape(&name, &value.ty.shape, limits)?);
102 if total > limits.max_total_elements {
103 return Err(Error::TensorLimit {
104 name,
105 what: "total tensor elements",
106 found: total,
107 limit: limits.max_total_elements,
108 });
109 }
110 }
111 Ok(())
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use crate::{Error, Graph, GraphBuilder, Limits, Op, Source, TensorType, ValueId};
118 use std::sync::Arc;
119
120 fn valid() -> Graph {
122 let mut b = GraphBuilder::new(Limits::default());
123 let x = b.input("x", vec![2, 3]).unwrap();
124 b.constant("c", vec![2], vec![1.0, 2.0]).unwrap();
125 let y = b.node("y", Op::Relu, &[x]).unwrap();
126 b.finish(&[y]).unwrap()
127 }
128
129 #[test]
130 fn builder_output_verifies() {
131 assert_eq!(valid().verify(), Ok(()));
132 }
133
134 #[test]
135 fn rejects_forward_reference() {
136 let mut g = valid();
137 g.values[2].source = Source::Node {
138 op: Op::Relu,
139 inputs: vec![ValueId(2)],
140 };
141 assert!(matches!(g.verify(), Err(Error::Verify(m)) if m.contains("defined at or after")));
142 }
143
144 #[test]
145 fn rejects_stored_type_that_differs_from_inferred() {
146 let mut g = valid();
147 g.values[2].ty = TensorType::f32(vec![9]);
148 assert!(matches!(g.verify(), Err(Error::Verify(m)) if m.contains("differs from inferred")));
149 }
150
151 #[test]
152 fn rejects_empty_outputs() {
153 let mut g = valid();
154 g.outputs.clear();
155 assert_eq!(g.verify(), Err(Error::NoOutputs));
156 }
157
158 #[test]
159 fn rejects_output_out_of_range() {
160 let mut g = valid();
161 g.outputs = vec![ValueId(7)];
162 assert!(matches!(g.verify(), Err(Error::Verify(m)) if m.contains("out of range")));
163 }
164
165 #[test]
166 fn rejects_constant_with_wrong_data_length() {
167 let mut g = valid();
168 g.values[1].source = Source::Constant(Arc::from(vec![1.0f32]));
169 assert!(matches!(
170 g.verify(),
171 Err(Error::DataLength {
172 expected: 2,
173 found: 1,
174 ..
175 })
176 ));
177 }
178
179 #[test]
180 fn rejects_input_list_that_disagrees_with_values() {
181 let mut g = valid();
182 g.inputs.clear();
183 assert!(matches!(g.verify(), Err(Error::Verify(m)) if m.contains("input list")));
184 }
185
186 #[test]
187 fn rejects_duplicate_names() {
188 let mut g = valid();
189 g.values[1].name = Some("x".into());
190 assert_eq!(g.verify(), Err(Error::DuplicateName { name: "x".into() }));
191 }
192
193 #[test]
194 fn rejects_output_names_that_do_not_match_outputs() {
195 let mut g = valid();
196 g.output_names.pop();
197 assert!(matches!(g.verify(), Err(Error::Verify(m)) if m.contains("output names")));
198 }
199
200 #[test]
201 fn rejects_a_zero_dimension_in_a_stored_type() {
202 let mut g = valid();
203 g.values[0].ty = TensorType::f32(vec![2, 0]);
204 assert!(matches!(g.verify(), Err(Error::InvalidShape { .. })));
205 }
206
207 #[test]
208 fn verify_limits_accepts_a_builder_graph() {
209 assert_eq!(valid().verify_limits(&Limits::default()), Ok(()));
210 }
211
212 #[test]
213 fn verify_limits_rejects_a_graph_built_under_looser_limits() {
214 let loose = Limits {
215 max_tensor_elements: 1 << 20,
216 ..Limits::default()
217 };
218 let mut b = GraphBuilder::new(loose);
219 let x = b.input("x", vec![1000, 1000]).unwrap();
220 let g = b.finish(&[x]).unwrap();
221 let strict = Limits {
222 max_tensor_elements: 1000,
223 ..Limits::default()
224 };
225 assert!(matches!(
226 g.verify_limits(&strict),
227 Err(Error::TensorLimit {
228 what: "tensor elements",
229 ..
230 })
231 ));
232 }
233
234 #[test]
235 fn verify_limits_checks_the_value_count_and_the_total() {
236 let g = valid();
237 let few = Limits {
238 max_values: 2,
239 ..Limits::default()
240 };
241 assert!(matches!(
242 g.verify_limits(&few),
243 Err(Error::LimitExceeded { what: "values", .. })
244 ));
245 let small_total = Limits {
246 max_total_elements: 7,
247 ..Limits::default()
248 };
249 assert!(matches!(
250 g.verify_limits(&small_total),
251 Err(Error::TensorLimit {
252 what: "total tensor elements",
253 ..
254 })
255 ));
256 }
257
258 #[test]
259 fn verify_limits_accepts_a_graph_exactly_at_the_limits() {
260 let exactly = Limits {
262 max_values: 3,
263 max_total_elements: 14,
264 ..Limits::default()
265 };
266 assert_eq!(valid().verify_limits(&exactly), Ok(()));
267 let one_less = Limits {
268 max_total_elements: 13,
269 ..exactly
270 };
271 assert!(valid().verify_limits(&one_less).is_err());
272 }
273}