1use crate::collating::Sequence;
8use crate::layout::{Kind, Layout, Resolved};
9use numeric::Options;
10use numeric::assumptions::NUMCHECK_ALWAYS_FAILS;
11use rt::picture::Sym;
12use rt::store::LaxRedefinition;
13use rt::vocab::{Figurative, SignClause, SignPosition};
14use std::collections::HashSet;
15use syntax::ast::*;
16use syntax::{Error, Pos, Severity};
17
18#[derive(Clone, Debug, Default)]
19pub struct NumcheckFacts {
20 lax: Vec<Option<LaxRedefinition>>,
21 removed: HashSet<(u16, u32, u32, usize)>,
23}
24
25impl NumcheckFacts {
26 pub fn lax(&self, item: usize) -> Option<LaxRedefinition> {
27 self.lax.get(item).copied().flatten()
28 }
29
30 pub fn removed(&self, item: usize, pos: Pos) -> bool {
31 self.removed.contains(&(pos.file, pos.line, pos.col, item))
32 }
33}
34
35pub(crate) fn facts(program: &Program, layout: &Layout, declared: usize, options: &Options, collating: &Sequence, errors: &mut Vec<Error>) -> NumcheckFacts {
37 let mut facts = NumcheckFacts { lax: lax_redefinitions(layout), removed: HashSet::new() };
38 if options.numcheck.is_none() || program.oo.is_some() {
39 return facts;
40 }
41 let mut tested = Tested { layout, options, found: Vec::new(), set: Vec::new() };
42 for paragraph in &program.paragraphs {
43 tested.statements(¶graph.statements);
44 }
45 let lengths: Vec<usize> = program.files.iter().filter_map(|f| f.record_depending.as_ref()).filter_map(|r| tested.item(r)).collect();
46 tested.set.extend(lengths);
47 let constant: HashSet<usize> = crate::initcheck::never_set(program, layout, declared, &tested.set).into_iter().collect();
48 for (pos, item, as_integer) in tested.found {
49 let key = (pos.file, pos.line, pos.col, item);
50 if !constant.contains(&item) || facts.removed.contains(&key) {
51 continue;
52 }
53 let Some(bytes) = value_bytes(layout, item, options, collating) else { continue };
54 let Some(why) = rt::store::numcheck_fault_in(options, layout.items[item].kind, &bytes, facts.lax(item), as_integer) else { continue };
55 facts.removed.insert(key);
56 let name = layout.items[item].name.as_deref().unwrap_or("FILLER");
57 let hex = rt::digest::hex(&bytes).to_ascii_uppercase();
58 errors.push(
59 syntax::messages::IWC0110.at(pos, format!("NUMCHECK: {name} {why} wherever this statement reads it: its VALUE clauses give it X'{hex}' and no statement changes it, so the test is removed (see {NUMCHECK_ALWAYS_FAILS})"))
60 .graded(Severity::Error),
61 );
62 }
63 facts
64}
65
66fn lax_redefinitions(layout: &Layout) -> Vec<Option<LaxRedefinition>> {
71 let items = &layout.items;
72 let root = |mut i: usize| {
73 while let Some(p) = items[i].parent {
74 i = p;
75 }
76 i
77 };
78 let overpunch = |sign: Option<SignClause>| matches!(sign, None | Some(SignClause { separate: false, position: SignPosition::Trailing }));
79 (0..items.len())
80 .map(|i| {
81 let item = &items[i];
82 let Kind::Zoned { signed, sign, .. } = item.kind else { return None };
83 if !item.dims.is_empty() {
84 return None;
85 }
86 let record = root(i);
87 let target = items[record].redefines.as_deref()?;
88 let redefined = (0..record).rev().find(|&s| {
89 let s = &items[s];
90 s.parent.is_none() && matches!(s.level, 1 | 77) && s.name.as_deref() == Some(target) && s.local == items[record].local && s.linkage.is_some() == items[record].linkage.is_some()
91 })?;
92 let base = &items[redefined];
93 let from = item.offset - items[record].offset;
94 match base.kind {
95 Kind::Zoned { signed: true, sign: base_sign, .. } if !signed && overpunch(base_sign) && from + item.size == base.size => Some(LaxRedefinition::Signed),
96 Kind::NumericEdited { edit, .. } if from == 0 && (!signed || overpunch(sign)) => {
97 let spaces = suppressed_lead(&layout.edits[edit as usize]).min(item.size);
98 (spaces > 0).then_some(LaxRedefinition::LeadingSpaces(spaces))
99 }
100 _ => None,
101 }
102 })
103 .collect()
104}
105
106fn suppressed_lead(symbols: &[Sym]) -> u32 {
109 let lead = symbols.iter().take_while(|s| matches!(s, Sym::Z | Sym::Insert(_)));
110 lead.enumerate().filter(|(_, s)| **s == Sym::Z).map(|(k, _)| k as u32 + 1).last().unwrap_or(0)
111}
112
113fn value_bytes(layout: &Layout, i: usize, options: &Options, collating: &Sequence) -> Option<Vec<u8>> {
117 let items = &layout.items;
118 let item = &items[i];
119 if !item.dims.is_empty() {
120 return None;
121 }
122 let covering = std::iter::successors(Some(i), |&j| items[j].parent).find(|&j| items[j].value.is_some())?;
123 let c = &items[covering];
124 if !matches!(c.kind, Kind::Group | Kind::Alnum { justified: false }) {
125 return None;
126 }
127 let (start, end) = (item.offset, item.offset + item.size);
128 let reaches = |j: usize| {
129 let o = &items[j];
130 let span: u32 = o.dims.iter().map(|&(stride, count)| stride * count.saturating_sub(1)).sum();
131 o.local == item.local && o.offset < end && start < o.offset + o.size + span
132 };
133 if (0..items.len()).any(|j| j != covering && items[j].value.is_some() && reaches(j)) {
134 return None;
135 }
136 let image = literal_image(c.value.as_ref()?, c.size as usize, options, collating)?;
137 let from = (start - c.offset) as usize;
138 Some(image[from..from + item.size as usize].to_vec())
139}
140
141fn literal_image(literal: &Literal, len: usize, options: &Options, collating: &Sequence) -> Option<Vec<u8>> {
143 let characters = |literal: &Literal| match literal {
144 Literal::Alnum(s) => options.code_page().encode(s).ok(),
145 Literal::Hex(b) => Some(b.clone()),
146 Literal::Figurative(f) => Some(vec![collating.figurative(*f)]),
147 _ => None,
148 };
149 match literal {
150 Literal::Alnum(_) | Literal::Hex(_) => {
151 let mut bytes = characters(literal).filter(|b| b.len() <= len)?;
152 bytes.resize(len, collating.figurative(Figurative::Space));
153 Some(bytes)
154 }
155 Literal::Figurative(f) => Some(vec![collating.figurative(*f); len]),
156 Literal::All(inner) => {
157 let pattern = characters(inner).filter(|p| !p.is_empty())?;
158 Some(pattern.iter().copied().cycle().take(len).collect())
159 }
160 Literal::National(_) | Literal::Dbcs(_) | Literal::Number(_) => None,
161 }
162}
163
164struct Tested<'a> {
170 layout: &'a Layout,
171 options: &'a Options,
172 found: Vec<(Pos, usize, bool)>,
174 set: Vec<usize>,
175}
176
177impl Tested<'_> {
178 fn item(&self, r: &Ref) -> Option<usize> {
179 match self.layout.resolve(&r.name, &r.qualifiers, r.pos) {
180 Ok(Resolved::Item(i)) => Some(i),
181 _ => None,
182 }
183 }
184
185 fn plain(&self, r: &Ref) -> Option<usize> {
186 if r.refmod.is_some() || !r.subscripts.is_empty() {
187 return None;
188 }
189 self.item(r)
190 }
191
192 fn kind(&self, r: &Ref) -> Option<Kind> {
193 if r.refmod.is_some() {
194 return Some(Kind::Alnum { justified: false });
195 }
196 self.item(r).map(|i| self.layout.items[i].kind)
197 }
198
199 fn sender(&mut self, r: &Ref, as_integer: bool) {
200 if let Some(i) = self.plain(r) {
201 self.found.push((r.pos, i, as_integer));
202 }
203 }
204
205 fn statements(&mut self, stmts: &[Stmt]) {
206 stmts.iter().for_each(|s| self.statement(s));
207 }
208
209 fn opt(&mut self, stmts: &Option<Vec<Stmt>>) {
210 self.statements(stmts.as_deref().unwrap_or_default());
211 }
212
213 fn handlers(&mut self, h: &Handlers) {
214 self.opt(&h.on);
215 self.opt(&h.not_on);
216 }
217
218 fn size_error(&mut self, se: &Option<SizeError>) {
219 if let Some(se) = se {
220 self.statements(&se.on);
221 self.statements(&se.not_on);
222 }
223 }
224
225 fn statement(&mut self, s: &Stmt) {
226 match s {
227 Stmt::Move { from: Operand::Ref(r), to, .. } => self.moved(r, to),
228 Stmt::Move { .. } => {}
229 Stmt::Compute { expr, size_error, .. } => {
230 self.arithmetic(expr);
231 self.size_error(size_error);
232 }
233 Stmt::Arith(a) => {
234 a.computations.iter().for_each(|(_, e)| self.arithmetic(e));
235 if let Some((_, x, y)) = &a.remainder {
236 self.arithmetic(x);
237 self.arithmetic(y);
238 }
239 self.size_error(&a.size_error);
240 }
241 Stmt::If { cond, then, otherwise, .. } => {
242 self.cond(cond);
243 self.statements(then);
244 self.statements(otherwise);
245 }
246 Stmt::Evaluate { subjects, whens, other, .. } => {
247 for subject in subjects {
248 if let Subject::Cond(c) = subject {
249 self.cond(c);
250 }
251 }
252 for object in whens.iter().flat_map(|w| &w.alternatives).flatten() {
253 if let Object::Cond(c) = object {
254 self.cond(c);
255 }
256 }
257 whens.iter().for_each(|w| self.statements(&w.body));
258 self.statements(other);
259 }
260 Stmt::PerformInline { body, repeat, .. } => {
261 self.repeat(repeat);
262 self.statements(body);
263 }
264 Stmt::PerformProc { repeat, .. } => self.repeat(repeat),
265 Stmt::Read(r) => {
266 self.handlers(&r.at_end);
267 self.handlers(&r.invalid);
268 }
269 Stmt::Write { invalid, end_of_page, .. } => {
270 self.handlers(invalid);
271 self.handlers(end_of_page);
272 }
273 Stmt::Rewrite { invalid, .. } | Stmt::Delete { invalid, .. } | Stmt::Start { invalid, .. } => self.handlers(invalid),
274 Stmt::Call(c) => {
275 for arg in &c.using {
276 if let (ArgMode::Content | ArgMode::Value, Some(Operand::Ref(r))) = (arg.mode, &arg.value) {
277 self.sender(r, false);
278 }
279 }
280 self.opt(&c.on_exception);
281 self.opt(&c.not_on_exception);
282 }
283 Stmt::Invoke(i) => {
284 self.opt(&i.on_exception);
285 self.opt(&i.not_on_exception);
286 }
287 Stmt::String(st) => {
288 self.opt(&st.on_overflow);
289 self.opt(&st.not_on_overflow);
290 }
291 Stmt::Unstring(u) => {
292 self.opt(&u.on_overflow);
293 self.opt(&u.not_on_overflow);
294 }
295 Stmt::Search(se) => {
296 self.opt(&se.at_end);
297 se.whens.iter().for_each(|(_, body)| self.statements(body));
298 }
299 Stmt::XmlParse(x) => {
300 self.opt(&x.on_exception);
301 self.opt(&x.not_on_exception);
302 }
303 Stmt::XmlGenerate(x) => {
304 self.opt(&x.on_exception);
305 self.opt(&x.not_on_exception);
306 }
307 Stmt::JsonParse(j) => {
308 self.opt(&j.on_exception);
309 self.opt(&j.not_on_exception);
310 }
311 Stmt::JsonGenerate(g) => {
312 self.opt(&g.on_exception);
313 self.opt(&g.not_on_exception);
314 }
315 Stmt::Sorting(so) => match &**so {
316 Sorting::Sort(sort) => {
317 let table = self.item(&sort.subject);
318 self.set.extend(table);
319 }
320 Sorting::Return { at_end, .. } => self.handlers(at_end),
321 Sorting::Release { .. } => {}
322 },
323 Stmt::Display { .. }
324 | Stmt::Open { .. }
325 | Stmt::Close { .. }
326 | Stmt::DeleteFile { .. }
327 | Stmt::Initialize { .. }
328 | Stmt::GoTo { .. }
329 | Stmt::GoToDepending { .. }
330 | Stmt::Alter { .. }
331 | Stmt::Entry { .. }
332 | Stmt::Goback { .. }
333 | Stmt::ExitProgram { .. }
334 | Stmt::ExitMethod { .. }
335 | Stmt::StopRun { .. }
336 | Stmt::Cancel { .. }
337 | Stmt::Set { .. }
338 | Stmt::Accept { .. }
339 | Stmt::Inspect(_)
340 | Stmt::NextSentence
341 | Stmt::SentenceEnd
342 | Stmt::Exec(_)
343 | Stmt::Report(_)
344 | Stmt::Continue { .. }
345 | Stmt::Exit { .. }
346 | Stmt::Corresponding(_) => {}
347 }
348 }
349
350 fn moved(&mut self, from: &Ref, to: &[Ref]) {
354 let Some(sender) = self.plain(from).map(|i| self.layout.items[i].kind) else { return };
355 let lax = self.options.numcheck.and_then(|c| c.zon).is_some_and(|z| z.lax);
356 for receiver in to {
357 let Some(kind) = self.kind(receiver) else { continue };
358 let numeric = matches!(kind, Kind::Zoned { .. } | Kind::Packed { .. } | Kind::Binary { .. } | Kind::Float(_) | Kind::NumericEdited { .. });
359 let spared = lax && matches!(sender, Kind::Zoned { .. }) && matches!(kind, Kind::Zoned { .. } | Kind::Alnum { .. } | Kind::Group);
360 if !spared {
361 self.sender(from, numeric && matches!(sender, Kind::Alnum { .. } | Kind::Group));
362 }
363 }
364 }
365
366 fn repeat(&mut self, repeat: &Loop) {
367 match repeat {
368 Loop::Once | Loop::Forever => {}
369 Loop::Times(e) => self.arithmetic(e),
370 Loop::Until { cond, .. } => self.cond(cond),
371 Loop::Varying { varying, after, .. } => {
372 for v in std::iter::once(&**varying).chain(after) {
373 self.cond(&v.until);
374 }
375 }
376 }
377 }
378
379 fn arithmetic(&mut self, e: &Expr) {
380 match e {
381 Expr::Operand(Operand::Ref(r)) => self.sender(r, false),
382 Expr::Operand(_) => {}
383 Expr::Neg(inner) => self.arithmetic(inner),
384 Expr::Bin(a, _, b) => {
385 self.arithmetic(a);
386 self.arithmetic(b);
387 }
388 }
389 }
390
391 fn cond(&mut self, c: &Cond) {
392 match c {
393 Cond::Rel(a, _, b) => {
394 self.compared(a, b);
395 self.compared(b, a);
396 }
397 Cond::Name(r) => {
398 if r.refmod.is_none()
399 && r.subscripts.is_empty()
400 && let Ok(Resolved::Condition(k)) = self.layout.resolve(&r.name, &r.qualifiers, r.pos)
401 {
402 self.found.push((r.pos, self.layout.conditions[k].item, false));
403 }
404 }
405 Cond::Not(inner) => self.cond(inner),
406 Cond::And(a, b) | Cond::Or(a, b) => {
407 self.cond(a);
408 self.cond(b);
409 }
410 Cond::Class(..) | Cond::NameOrRel { .. } => {}
411 }
412 }
413
414 fn compared(&mut self, e: &Expr, other: &Expr) {
418 if self.options.zones_compared() {
419 return;
420 }
421 let numeric = |kind: Kind| matches!(kind, Kind::Zoned { .. } | Kind::Packed { .. } | Kind::Binary { .. } | Kind::Float(_));
422 match e {
423 Expr::Bin(..) | Expr::Neg(_) => self.arithmetic(e),
424 Expr::Operand(Operand::Ref(r)) => {
425 let against_number = match other {
426 Expr::Operand(Operand::Literal(Literal::Number(_) | Literal::Figurative(Figurative::Zero))) | Expr::Bin(..) | Expr::Neg(_) => true,
427 Expr::Operand(Operand::Ref(o)) => self.kind(o).is_some_and(numeric),
428 Expr::Operand(_) => false,
429 };
430 if against_number && self.kind(r).is_some_and(numeric) {
431 self.sender(r, false);
432 }
433 }
434 Expr::Operand(_) => {}
435 }
436 }
437}