1use std::{
32 collections::HashMap,
33 hash::{DefaultHasher, Hash, Hasher},
34 rc::Rc,
35};
36
37use crate::{
38 ast::{BinaryOp, Body, Callee, Expr, ExprKind, Function, PathPart, Ruleset, Span},
39 diag::Position,
40 eval::{NodeKey, Outcome, Trace, Traced, Val, hash_val, observe, shows},
41 lexer,
42};
43
44pub const MAX_VALUES: usize = 100;
47
48pub struct Coverage {
50 nodes: Vec<Node>,
51 roots: Vec<usize>,
52 index: HashMap<NodeKey, usize>,
53 code_points: Vec<u32>,
55 tokens: HashMap<usize, (usize, u32, u32)>,
57}
58
59struct Node {
60 position: Position,
61 children: Vec<usize>,
62 values: Vec<(Outcome, u64)>,
63 seen: HashMap<u64, Vec<usize>>,
65}
66
67pub struct Expression<'a> {
69 pub position: Position,
70 pub values: &'a [(Outcome, u64)],
72 pub children: &'a [usize],
74}
75
76enum Pending<'r> {
80 Expr(&'r Expr, bool),
81 Lets(&'r Function, usize),
82}
83
84impl Coverage {
85 pub fn new(ruleset: &Ruleset, source: &str) -> Self {
87 let mut code_points = Vec::with_capacity(source.len() + 1);
88 let mut count = 0u32;
89 for c in source.chars() {
90 code_points.extend(std::iter::repeat_n(count, c.len_utf8()));
91 count += 1;
92 }
93 code_points.push(count);
94 let tokens = lexer::lex(source)
95 .tokens
96 .into_iter()
97 .map(|t| (t.start, (t.end, t.line, t.column)))
98 .collect();
99 let mut coverage = Self {
100 nodes: Vec::new(),
101 roots: Vec::new(),
102 index: HashMap::new(),
103 code_points,
104 tokens,
105 };
106 let mut roots = Vec::new();
107 for f in &ruleset.functions {
108 roots.extend(coverage.function(ruleset, f));
109 }
110 for service in &ruleset.services {
111 coverage.body(ruleset, &service.body, &mut roots);
112 }
113 coverage.roots = roots;
114 coverage
115 }
116
117 fn body(&mut self, ruleset: &Ruleset, body: &Body, roots: &mut Vec<usize>) {
118 for f in &body.functions {
119 roots.extend(self.function(ruleset, f));
120 }
121 for m in &body.matches {
122 self.body(ruleset, &m.body, roots);
123 }
124 for allow in &body.allows {
125 if let Some(condition) = allow.condition.as_ref().filter(|c| is_node(c)) {
126 roots.push(self.tree(ruleset, Pending::Expr(condition, false)));
127 }
128 }
129 }
130
131 fn function(&mut self, ruleset: &Ruleset, f: &Function) -> Option<usize> {
132 if f.lets.is_empty() {
133 return is_node(&f.result).then(|| self.tree(ruleset, Pending::Expr(&f.result, false)));
134 }
135 Some(self.tree(ruleset, Pending::Lets(f, 0)))
136 }
137
138 fn tree(&mut self, ruleset: &Ruleset, root: Pending<'_>) -> usize {
140 let mut stack: Vec<(Pending<'_>, Option<usize>)> = vec![(root, None)];
141 let mut added = None;
142 while let Some((pending, parent)) = stack.pop() {
143 let mut children = Vec::new();
144 let (key, position) = match pending {
145 Pending::Expr(e, object) => {
146 children.extend(
147 parts(e)
148 .into_iter()
149 .filter(|(part, _)| is_node(part))
150 .map(|(part, object)| Pending::Expr(part, object)),
151 );
152 let start = if object {
153 start_inside(&ruleset.parens, e)
154 } else {
155 start(&ruleset.parens, e)
156 };
157 (NodeKey::of(e), self.position(start, end(e)))
158 }
159 Pending::Lets(f, i) => {
160 let binding = &f.lets[i];
161 if is_node(&binding.value) {
162 children.push(Pending::Expr(&binding.value, false));
163 }
164 if i + 1 < f.lets.len() {
165 children.push(Pending::Lets(f, i + 1));
166 } else if is_node(&f.result) {
167 children.push(Pending::Expr(&f.result, false));
168 }
169 let position = self.position(binding.span, end(&f.result));
170 (NodeKey::Body(binding.span.start), position)
171 }
172 };
173 self.nodes.push(Node {
174 position,
175 children: Vec::new(),
176 values: Vec::new(),
177 seen: HashMap::new(),
178 });
179 let index = self.nodes.len() - 1;
180 self.index.insert(key, index);
181 match parent {
182 Some(parent) => self.nodes[parent].children.push(index),
183 None => added = Some(index),
184 }
185 stack.extend(children.into_iter().rev().map(|c| (c, Some(index))));
186 }
187 added.expect("the root was added")
188 }
189
190 fn position(&self, start: Span, end: usize) -> Position {
192 let from = self.code_points[start.start];
193 Position {
194 line: start.line,
195 column: start.column,
196 current_offset: from,
197 end_offset: self.code_points[end].max(from + 1) - 1,
198 }
199 }
200
201 fn token(&self, at: usize) -> Option<Position> {
203 let &(end, line, column) = self.tokens.get(&at)?;
204 Some(self.position(
205 Span {
206 start: at,
207 end,
208 line,
209 column,
210 },
211 end,
212 ))
213 }
214
215 pub fn record(&mut self, trace: &Trace) {
218 let Some(values) = &trace.values else {
219 return;
220 };
221 let mut hashes: HashMap<usize, u64> = HashMap::new();
224 for (key, traced) in values {
225 let Some(&i) = self.index.get(key) else {
226 continue;
227 };
228 let mut hasher = DefaultHasher::new();
229 let address = match traced {
230 Traced::Value(Val::Map(map)) => Some(Rc::as_ptr(map).addr()),
231 Traced::Value(Val::List(items)) => Some(Rc::as_ptr(items).addr()),
232 _ => None,
233 };
234 let known = address.and_then(|a| hashes.get(&a).copied());
235 let position = match traced {
236 Traced::Value(v) => {
237 if known.is_none() {
238 0u8.hash(&mut hasher);
239 hash_val(v, &mut hasher);
240 }
241 None
242 }
243 Traced::Error(e) => {
244 let position = e.at.and_then(|at| self.token(at));
245 (1u8, position, &e.message).hash(&mut hasher);
246 position
247 }
248 Traced::Skipped => {
249 2u8.hash(&mut hasher);
250 None
251 }
252 };
253 let hash = known.unwrap_or_else(|| hasher.finish());
254 if let Some(address) = address {
255 hashes.insert(address, hash);
256 }
257 let node = &mut self.nodes[i];
258 let seen = node.seen.get(&hash).into_iter().flatten();
259 let same = |outcome: &Outcome| match (outcome, traced) {
260 (Outcome::Value(o), Traced::Value(v)) => shows(o, v),
261 (
262 Outcome::Error {
263 position: p,
264 message,
265 },
266 Traced::Error(e),
267 ) => *p == position && *message == e.message,
268 (Outcome::Skipped, Traced::Skipped) => true,
269 _ => false,
270 };
271 if let Some(&found) = seen.into_iter().find(|&&j| same(&node.values[j].0)) {
272 node.values[found].1 += 1;
273 } else if node.values.len() < MAX_VALUES {
274 let outcome = match traced {
275 Traced::Value(v) => Outcome::Value(observe(v)),
276 Traced::Error(e) => Outcome::Error {
277 position,
278 message: e.message.clone(),
279 },
280 Traced::Skipped => Outcome::Skipped,
281 };
282 node.seen.entry(hash).or_default().push(node.values.len());
283 node.values.push((outcome, 1));
284 }
285 }
286 }
287
288 pub fn roots(&self) -> &[usize] {
290 &self.roots
291 }
292
293 pub fn expression(&self, i: usize) -> Expression<'_> {
295 let node = &self.nodes[i];
296 Expression {
297 position: node.position,
298 values: &node.values,
299 children: &node.children,
300 }
301 }
302}
303
304pub(crate) fn is_node(e: &Expr) -> bool {
306 !matches!(
307 e.kind,
308 ExprKind::Null
309 | ExprKind::Bool(_)
310 | ExprKind::Int(_)
311 | ExprKind::IntOverflow(_)
312 | ExprKind::Float(_)
313 | ExprKind::String(_)
314 | ExprKind::Bytes(_)
315 )
316}
317
318fn parts(e: &Expr) -> Vec<(&Expr, bool)> {
321 match &e.kind {
322 ExprKind::List(items) => items.iter().map(|e| (e, false)).collect(),
323 ExprKind::Map(entries) => entries
324 .iter()
325 .flat_map(|(k, v)| [(k, false), (v, false)])
326 .collect(),
327 ExprKind::Path(parts) => parts
328 .iter()
329 .filter_map(|part| match part {
330 PathPart::Expr(e) => Some((e, false)),
331 PathPart::Segment(_) => None,
332 })
333 .collect(),
334 ExprKind::Member { object, .. } => vec![(object, true)],
335 ExprKind::Index { object, index } => vec![(object, true), (index, false)],
336 ExprKind::Slice { object, start, end } => {
337 vec![(object, true), (start, false), (end, false)]
338 }
339 ExprKind::Call { callee, args } => {
340 let mut parts = Vec::with_capacity(args.len() + 1);
341 if let Callee::Method { receiver, .. } = callee {
342 parts.push((&**receiver, true));
343 }
344 parts.extend(args.iter().map(|a| (a, false)));
345 parts
346 }
347 ExprKind::Unary { operand, .. } => vec![(operand, false)],
348 ExprKind::Binary {
350 op: BinaryOp::In,
351 left,
352 right,
353 ..
354 } => vec![(right, false), (left, false)],
355 ExprKind::Binary { left, right, .. } => vec![(left, false), (right, false)],
356 ExprKind::Is { value, .. } => vec![(value, false)],
357 ExprKind::Ternary {
358 condition,
359 then,
360 otherwise,
361 ..
362 } => vec![(condition, false), (then, false), (otherwise, false)],
363 ExprKind::Null
364 | ExprKind::Bool(_)
365 | ExprKind::Int(_)
366 | ExprKind::IntOverflow(_)
367 | ExprKind::Float(_)
368 | ExprKind::String(_)
369 | ExprKind::Bytes(_)
370 | ExprKind::Ident(_) => Vec::new(),
371 }
372}
373
374pub(crate) fn walk<'e>(e: &'e Expr, f: &mut dyn FnMut(&'e Expr)) {
376 let mut stack = vec![e];
377 while let Some(e) = stack.pop() {
378 if is_node(e) {
379 f(e);
380 stack.extend(parts(e).into_iter().map(|(part, _)| part));
381 }
382 }
383}
384
385pub(crate) fn start(parens: &HashMap<(usize, usize), Vec<Span>>, e: &Expr) -> Span {
388 match parens.get(&(e.span.start, e.span.end)) {
389 Some(around) if !around.is_empty() => around[0],
390 _ => leftmost(parens, e),
391 }
392}
393
394pub(crate) fn start_inside(parens: &HashMap<(usize, usize), Vec<Span>>, e: &Expr) -> Span {
397 match parens.get(&(e.span.start, e.span.end)) {
398 Some(around) if around.len() > 1 => around[1],
399 _ => leftmost(parens, e),
400 }
401}
402
403fn leftmost(parens: &HashMap<(usize, usize), Vec<Span>>, mut e: &Expr) -> Span {
405 loop {
406 e = match &e.kind {
407 ExprKind::Binary { left, .. } => left,
408 ExprKind::Member { object, .. }
409 | ExprKind::Index { object, .. }
410 | ExprKind::Slice { object, .. } => object,
411 ExprKind::Call {
412 callee: Callee::Method { receiver, .. },
413 ..
414 } => receiver,
415 ExprKind::Is { value, .. } => value,
416 ExprKind::Ternary { condition, .. } => condition,
417 _ => return e.span,
418 };
419 if let Some(around) = parens.get(&(e.span.start, e.span.end))
420 && let Some(outermost) = around.first()
421 {
422 return *outermost;
423 }
424 }
425}
426
427fn end(mut e: &Expr) -> usize {
430 loop {
431 e = match &e.kind {
432 ExprKind::List(items) => match items.last() {
433 Some(last) => last,
434 None => return e.span.start + 1,
435 },
436 ExprKind::Map(entries) => match entries.last() {
437 Some((_, value)) => value,
438 None => return e.span.start + 1,
439 },
440 ExprKind::Path(parts) => match parts.last() {
441 Some(PathPart::Expr(last)) => last,
442 _ => return e.span.end,
443 },
444 ExprKind::Member { field, .. } => return field.span.end,
445 ExprKind::Index { index, .. } => index,
446 ExprKind::Slice { end, .. } => end,
447 ExprKind::Call { callee, args } => match args.last() {
448 Some(last) => last,
449 None => {
450 return match callee {
451 Callee::Global(name)
452 | Callee::Namespaced { name, .. }
453 | Callee::Method { name, .. } => name.span.end,
454 };
455 }
456 },
457 ExprKind::Unary { operand, .. } => operand,
458 ExprKind::Binary { right, .. } => right,
459 ExprKind::Is { type_name, .. } => return type_name.span.end,
460 ExprKind::Ternary { otherwise, .. } => otherwise,
461 _ => return e.span.end,
462 };
463 }
464}