circomspect_program_analysis/
signal_assignments.rs1use log::{debug, trace};
2use program_structure::intermediate_representation::degree_meta::{DegreeRange, DegreeMeta};
3use std::collections::HashSet;
4
5use program_structure::cfg::{Cfg, DefinitionType};
6use program_structure::report_code::ReportCode;
7use program_structure::report::{Report, ReportCollection};
8use program_structure::ir::*;
9use program_structure::ir::AccessType;
10use program_structure::ir::variable_meta::VariableMeta;
11
12pub struct SignalAssignmentWarning {
13 signal: VariableName,
14 access: Vec<AccessType>,
15 assignment_meta: Meta,
16 constraint_metas: Vec<Meta>,
17}
18
19impl SignalAssignmentWarning {
20 pub fn into_report(self) -> Report {
21 let mut report = Report::warning(
22 "Using the signal assignment operator `<--` does not constrain the assigned signal."
23 .to_string(),
24 ReportCode::SignalAssignmentStatement,
25 );
26 if let Some(file_id) = self.assignment_meta.file_id {
28 report.add_primary(
29 self.assignment_meta.location,
30 file_id,
31 format!(
32 "The assigned signal `{}{}` is not constrained here.",
33 self.signal,
34 access_to_string(&self.access)
35 ),
36 );
37 }
38 for meta in self.constraint_metas {
40 if let Some(file_id) = meta.file_id {
41 report.add_secondary(
42 meta.location,
43 file_id,
44 Some(format!(
45 "The signal `{}{}` is constrained here.",
46 self.signal,
47 access_to_string(&self.access),
48 )),
49 );
50 }
51 }
52 if report.secondary().is_empty() {
54 report.add_note(
55 "Consider if it is possible to rewrite the statement using `<==` instead."
56 .to_string(),
57 );
58 }
59 report
60 }
61}
62
63pub struct UnecessarySignalAssignmentWarning {
64 signal: VariableName,
65 access: Vec<AccessType>,
66 assignment_meta: Meta,
67}
68
69impl UnecessarySignalAssignmentWarning {
70 pub fn into_report(self) -> Report {
71 let mut report = Report::warning(
72 "Using the signal assignment operator `<--` is not necessary here.".to_string(),
73 ReportCode::UnnecessarySignalAssignment,
74 );
75 if let Some(file_id) = self.assignment_meta.file_id {
77 report.add_primary(
78 self.assignment_meta.location,
79 file_id,
80 format!(
81 "The expression assigned to `{}{}` is quadratic.",
82 self.signal,
83 access_to_string(&self.access)
84 ),
85 );
86 }
87 report.add_note(
89 "Consider rewriting the statement using the constraint assignment operator `<==`."
90 .to_string(),
91 );
92 report
93 }
94}
95
96type AssignmentSet = HashSet<Assignment>;
97#[derive(Clone, Hash, PartialEq, Eq)]
99struct Assignment {
100 pub meta: Meta,
101 pub signal: VariableName,
102 pub access: Vec<AccessType>,
103 pub degree: Option<DegreeRange>,
104}
105
106impl Assignment {
107 fn new(
108 meta: &Meta,
109 signal: &VariableName,
110 access: &[AccessType],
111 degree: Option<&DegreeRange>,
112 ) -> Assignment {
113 Assignment {
114 meta: meta.clone(),
115 signal: signal.clone(),
116 access: access.to_owned(),
117 degree: degree.cloned(),
118 }
119 }
120
121 fn is_quadratic(&self) -> bool {
122 if let Some(range) = &self.degree {
123 range.is_quadratic()
124 } else {
125 false
126 }
127 }
128}
129
130type ConstraintSet = HashSet<Constraint>;
131
132#[derive(Clone, Hash, PartialEq, Eq)]
133struct Constraint {
134 pub meta: Meta,
135 pub lhe: Expression,
136 pub rhe: Expression,
137}
138
139impl Constraint {
140 fn new(meta: &Meta, lhe: &Expression, rhe: &Expression) -> Constraint {
141 Constraint { meta: meta.clone(), lhe: lhe.clone(), rhe: rhe.clone() }
142 }
143}
144
145#[derive(Clone, Default)]
148struct SignalUse {
149 assignments: AssignmentSet,
150 constraints: ConstraintSet,
151}
152
153impl SignalUse {
154 fn new() -> SignalUse {
156 SignalUse::default()
157 }
158
159 fn add_assignment(
161 &mut self,
162 var: &VariableName,
163 access: &[AccessType],
164 meta: &Meta,
165 degree: Option<&DegreeRange>,
166 ) {
167 trace!("adding signal assignment for `{var:?}` access");
168 self.assignments.insert(Assignment::new(meta, var, access, degree));
169 }
170
171 fn add_constraint(&mut self, lhe: &Expression, rhe: &Expression, meta: &Meta) {
173 trace!("adding constraint `{lhe:?} === {rhe:?}`");
174 self.constraints.insert(Constraint::new(meta, lhe, rhe));
175 }
176
177 fn get_assignments(&self) -> &AssignmentSet {
179 &self.assignments
180 }
181
182 fn get_constraints(&self, signal: &VariableName, access: &Vec<AccessType>) -> Vec<&Constraint> {
184 self.constraints
185 .iter()
186 .filter(|constraint| {
187 let lhe = constraint.lhe.signals_read().iter();
188 let rhe = constraint.rhe.signals_read().iter();
189 lhe.chain(rhe)
190 .any(|signal_use| signal_use.name() == signal && signal_use.access() == access)
191 })
192 .collect()
193 }
194
195 fn get_constraint_metas(&self, signal: &VariableName, access: &Vec<AccessType>) -> Vec<Meta> {
198 self.get_constraints(signal, access)
199 .iter()
200 .map(|constraint| constraint.meta.clone())
201 .collect()
202 }
203}
204
205pub fn find_signal_assignments(cfg: &Cfg) -> ReportCollection {
209 use DefinitionType::*;
210 if matches!(cfg.definition_type(), Function | CustomTemplate) {
211 return ReportCollection::new();
213 }
214 debug!("running signal assignment analysis pass");
215 let mut signal_use = SignalUse::new();
216 for basic_block in cfg.iter() {
217 for stmt in basic_block.iter() {
218 visit_statement(stmt, &mut signal_use);
219 }
220 }
221 let mut reports = ReportCollection::new();
222 for assignment in signal_use.get_assignments() {
223 if assignment.is_quadratic() {
224 reports.push(build_unecessary_assignment_report(
225 &assignment.signal,
226 &assignment.access,
227 &assignment.meta,
228 ))
229 } else {
230 let constraint_metas =
231 signal_use.get_constraint_metas(&assignment.signal, &assignment.access);
232 reports.push(build_assignment_report(
233 &assignment.signal,
234 &assignment.access,
235 &assignment.meta,
236 &constraint_metas,
237 ));
238 }
239 }
240
241 debug!("{} new reports generated", reports.len());
242 reports
243}
244
245fn visit_statement(stmt: &Statement, signal_use: &mut SignalUse) {
246 use Expression::*;
247 use Statement::*;
248 match stmt {
249 Substitution { meta, var, op, rhe } => {
250 let access = if let Update { access, .. } = rhe { access.clone() } else { Vec::new() };
251 match op {
252 AssignOp::AssignSignal => {
253 signal_use.add_assignment(var, &access, meta, rhe.degree());
254 }
255 AssignOp::AssignConstraintSignal => {
260 let lhe = Expression::Variable { meta: meta.clone(), name: var.clone() };
261 signal_use.add_constraint(&lhe, rhe, meta)
262 }
263 AssignOp::AssignLocalOrComponent => {}
264 }
265 }
266 ConstraintEquality { meta, lhe, rhe } => {
267 signal_use.add_constraint(lhe, rhe, meta);
268 }
269 _ => {}
270 }
271}
272
273fn build_unecessary_assignment_report(
274 signal: &VariableName,
275 access: &[AccessType],
276 assignment_meta: &Meta,
277) -> Report {
278 UnecessarySignalAssignmentWarning {
279 signal: signal.clone(),
280 access: access.to_owned(),
281 assignment_meta: assignment_meta.clone(),
282 }
283 .into_report()
284}
285
286fn build_assignment_report(
287 signal: &VariableName,
288 access: &[AccessType],
289 assignment_meta: &Meta,
290 constraint_metas: &[Meta],
291) -> Report {
292 SignalAssignmentWarning {
293 signal: signal.clone(),
294 access: access.to_owned(),
295 assignment_meta: assignment_meta.clone(),
296 constraint_metas: constraint_metas.to_owned(),
297 }
298 .into_report()
299}
300
301#[must_use]
302fn access_to_string(access: &[AccessType]) -> String {
303 access.iter().map(|access| access.to_string()).collect::<Vec<String>>().join("")
304}
305
306#[cfg(test)]
307mod tests {
308 use parser::parse_definition;
309 use program_structure::{cfg::IntoCfg, constants::Curve};
310
311 use super::*;
312
313 #[test]
314 fn test_signal_assignments() {
315 let src = r#"
316 template T(a) {
317 signal input in;
318 signal output out;
319
320 out <-- in + a;
321 }
322 "#;
323 validate_reports(src, 1);
324
325 let src = r#"
326 template T(a) {
327 signal input in;
328 signal output out;
329
330 in + a === out;
331 out <-- in + a;
332 }
333 "#;
334 validate_reports(src, 1);
335
336 let src = r#"
337 template T(n) {
338 signal input in;
339 signal output out[n];
340
341 in + 1 === out[0];
342 out[0] <-- in + 1;
343 }
344 "#;
345 validate_reports(src, 1);
346
347 let src = r#"
348 template T(n) {
349 signal output out[n];
350
351 in + 1 === out[0];
352 out[0] <-- in * in;
353 }
354 "#;
355 validate_reports(src, 1);
356 }
357
358 fn validate_reports(src: &str, expected_len: usize) {
359 println!("{}", src);
361 let mut reports = ReportCollection::new();
362 let cfg = parse_definition(src)
363 .unwrap()
364 .into_cfg(&Curve::default(), &mut reports)
365 .unwrap()
366 .into_ssa()
367 .unwrap();
368 assert!(reports.is_empty());
369
370 let reports = find_signal_assignments(&cfg);
372 for report in &reports {
373 println!("{}", report.message())
374 }
375
376 assert_eq!(reports.len(), expected_len);
377 }
378}