1use rt::cics::Handles;
6use rt::lir::{
7 Advance, Argument, Base, Binding, Bound, CallArg, CallTarget, Ccsid, Chars, Comparand, Compare, Cond, Const, Convert, ConvertTable, Count, DisplayItem, InitValue, Inspected, Expr, FileVerb, Flag, Func, HostPlace, IntExpr,
8 GlobalAt, JsonValue, Marker, Markup, MethodName, MovePlan, Named, Op, Operand, ParseValue, Place, PlaceId, Program, RangeKind, Receiver, Replacement, ReportOp, RowCount, SenderCheck, SetTo, SortIo, SortPlan,
9 SqlStatement, StartKey, StorePlan, SymId, Terminator, UpDown, UserArgument, XmlValue,
10};
11use rt::report::{FieldContent, GroupKind, Origin};
12use rt::vocab::AcceptFrom;
13
14type Check<'a, T> = &'a dyn Fn(T) -> Result<(), String>;
15
16struct Ids<'a> {
18 place: Check<'a, PlaceId>,
19 operand: &'a dyn Fn(&Operand) -> Result<(), String>,
20 symbol: Check<'a, SymId>,
21}
22
23impl Handles<PlaceId, Operand, SymId> for Ids<'_> {
24 type Place = ();
25 type Value = ();
26 type Text = ();
27 type Error = String;
28
29 fn place(&mut self, place: PlaceId) -> Result<(), String> {
30 (self.place)(place)
31 }
32
33 fn value(&mut self, value: Operand) -> Result<(), String> {
34 (self.operand)(&value)
35 }
36
37 fn text(&mut self, text: SymId) -> Result<(), String> {
38 (self.symbol)(text)
39 }
40}
41
42pub fn verify(p: &Program) -> Result<(), String> {
44 verify_program(p)?;
45 let Some(class) = &p.services.class else { return Ok(()) };
46 for part in class.factory.iter().chain(&class.object) {
47 verify(&part.data).map_err(|e| format!("class data: {e}"))?;
48 }
49 for m in &class.methods {
50 verify(&m.code).map_err(|e| format!("method {}: {e}", p.symbols.get(m.name as usize).map_or("", String::as_str)))?;
51 }
52 let symbol = |id: u32| if (id as usize) < p.symbols.len() { Ok(()) } else { Err(format!("symbol {id} of {}", p.symbols.len())) };
53 symbol(class.external)?;
54 symbol(class.parent)?;
55 for m in &class.methods {
56 symbol(m.name)?;
57 m.params.iter().chain(&m.returns).try_for_each(|&s| symbol(s))?;
58 }
59 Ok(())
60}
61
62fn verify_program(p: &Program) -> Result<(), String> {
63 let within = |what: &str, id: u32, len: usize| if (id as usize) < len { Ok(()) } else { Err(format!("{what} {id} of {len}")) };
64 let block = |id| within("block", id, p.blocks.len());
65 let place = |id| within("place", id, p.places.len());
66 let expr = |id| within("expression", id, p.exprs.len());
67 let cond = |id| within("condition", id, p.conds.len());
68 let abend = |id| within("abend", id, p.abends.len());
69 let range = |id: u32, kind: RangeKind| match p.ranges.get(id as usize) {
70 Some(r) if r.kind == kind => Ok(()),
71 Some(r) => Err(format!("range {id} is {:?} where {kind:?} is wanted", r.kind)),
72 None => Err(format!("range {id} of {}", p.ranges.len())),
73 };
74 let places = |qs: &[u32]| qs.iter().try_for_each(|&q| place(q));
75 let int = |e: &IntExpr| match e {
76 IntExpr::Const(_) | IntExpr::Walk(_) => Ok(()),
77 IntExpr::Item(q) => place(*q),
78 IntExpr::Fixed { expr: e, prepass, .. } => expr(*e).and_then(|()| places(prepass)),
79 };
80 let operand = |o: &Operand| match *o {
81 Operand::Load(q) | Operand::LengthOf(q) | Operand::AddressOf(q) => place(q),
82 Operand::Const(c) => within("constant", c, p.consts.len()),
83 Operand::Function(f) => within("function plan", f, p.plans.function.len()),
84 Operand::UserFunction(f) => within("user-defined function invocation", f, p.services.user_functions.len()),
85 };
86 let comparand = |c: &Comparand| match c {
87 Comparand::Operand(o) => operand(o),
88 Comparand::Expr { expr: e, prepass, .. } => expr(*e).and_then(|()| places(prepass)),
89 };
90 let ssrange = p.options.ssrange;
91 let sender = |from: &Operand, check: SenderCheck| match (from, check) {
92 (_, SenderCheck::None) => operand(from),
93 (Operand::Load(q), _) if p.options.options.numcheck.is_some() => place(*q),
94 _ => Err(format!("a NUMCHECK test {check:?} of a sender that is not a data item, or without NUMCHECK")),
95 };
96 if let Some((offset, len)) = p.storage.parmcheck
97 && (p.options.options.parmcheck.is_none() || offset.checked_add(len).is_none_or(|end| end > p.storage.size))
98 {
99 return Err(format!("a PARMCHECK buffer at {offset} for {len} in a slab of {}, or without PARMCHECK", p.storage.size));
100 }
101
102 let numcheck = p.options.options.numcheck;
103 let lax = numcheck.and_then(|c| c.zon).is_some_and(|z| z.lax);
104 for (k, q) in p.places.iter().enumerate() {
105 let Place { moved, subscripts, odo, refmod, at, .. } = q;
106 within("debug entry", *at, p.debug.positions.len())?;
107 if q.numcheck.lax.is_some() && !lax || q.numcheck.removed && numcheck.is_none() {
108 return Err(format!("place {k}: NUMCHECK facts {:?} its options do not read", q.numcheck));
109 }
110 for s in subscripts {
111 int(&s.value)?;
112 }
113 if q.table.is_some() != (ssrange && !subscripts.is_empty()) {
115 return Err(format!("place {k}: a table range without SSRANGE, or none with it"));
116 }
117 for o in moved.iter().chain(odo) {
118 int(&o.object)?;
119 if o.check != ssrange {
120 return Err(format!("place {k}: an OCCURS DEPENDING ON check that disagrees with SSRANGE"));
121 }
122 }
123 if let Some(r) = refmod {
124 int(&r.start)?;
125 r.length.as_ref().map_or(Ok(()), int)?;
126 if r.check != ssrange {
127 return Err(format!("place {k}: a reference-modification check that disagrees with SSRANGE"));
128 }
129 }
130 }
131 for e in &p.exprs {
132 match e {
133 Expr::Operand(o) => operand(o)?,
134 Expr::Neg(a) => expr(*a)?,
135 Expr::Bin(a, _, b) => {
136 expr(*a)?;
137 expr(*b)?;
138 }
139 Expr::Pow(a, n) => {
140 expr(*a)?;
141 int(n)?;
142 }
143 }
144 }
145 for c in &p.conds {
146 match c {
147 Cond::Rel { a, b, .. } => {
148 comparand(a)?;
149 comparand(b)?;
150 }
151 Cond::Class { place: q, .. } => place(*q)?,
152 Cond::Sign { value, .. } => comparand(value)?,
153 Cond::Name { subject, values, .. } => {
154 place(*subject)?;
155 for &(low, high) in values {
156 within("constant", low, p.consts.len())?;
157 high.map_or(Ok(()), |h| within("constant", h, p.consts.len()))?;
158 }
159 }
160 Cond::Not(a) => cond(*a)?,
161 Cond::And(a, b) | Cond::Or(a, b) => {
162 cond(*a)?;
163 cond(*b)?;
164 }
165 Cond::Counter(_) | Cond::Sql(_) => {}
166 Cond::InTable { index, count } => {
167 place(*index)?;
168 if let Count::Odo(o) = count {
169 int(&o.object)?;
170 }
171 }
172 }
173 }
174 for a in &p.abends {
175 a.at.map_or(Ok(()), |at| within("debug entry", at, p.debug.positions.len()))?;
176 }
177 p.consts.iter().try_for_each(|c| if let Const::Refused(a) = c { abend(*a) } else { Ok(()) })?;
178 for a in &p.plans.arith {
179 places(&a.prepass)?;
180 for s in &a.steps {
181 place(s.target)?;
182 expr(s.expr)?;
183 places(&s.probe)?;
184 }
185 if let Some(r) = &a.remainder {
186 place(r.target)?;
187 expr(r.dividend)?;
188 expr(r.divisor)?;
189 }
190 }
191 let symbol = |id: u32| within("symbol", id, p.symbols.len());
192 if p.plans.function.iter().any(|f| f.func == Func::WhenCompiled) != p.options.when_compiled.is_some() {
193 return Err("a compile time without a WHEN-COMPILED plan, or a WHEN-COMPILED plan without one".into());
194 }
195 for f in &p.plans.function {
196 for a in &f.args {
197 match a {
198 Argument::Value(c) => comparand(c)?,
199 Argument::All { element, all } => {
200 place(*element)?;
201 let subscripts = p.places.get(*element as usize).map_or(0, |q| q.subscripts.len());
202 for (position, count) in all {
203 if *position as usize >= subscripts {
204 return Err(format!("an ALL subscript at {position} of place {element}, which has {subscripts}"));
205 }
206 if let Count::Odo(o) = count {
207 int(&o.object)?;
208 if o.check != ssrange {
209 return Err("an ALL subscript's OCCURS DEPENDING ON check that disagrees with SSRANGE".into());
210 }
211 }
212 }
213 }
214 }
215 }
216 f.integer.as_ref().map_or(Ok(()), int)?;
217 if let Some(r) = &f.refmod {
218 int(&r.start)?;
219 r.length.as_ref().map_or(Ok(()), int)?;
220 if r.check {
221 return Err("a FUNCTION's reference modification with an SSRANGE check".into());
222 }
223 }
224 f.arity.map_or(Ok(()), abend)?;
225 within("debug entry", f.at, p.debug.positions.len())?;
226 }
227 for f in &p.services.files {
228 symbol(f.name)?;
229 symbol(f.assign)?;
230 f.error.map_or(Ok(()), |r| range(r, RangeKind::UseProcedure))?;
231 f.status.map_or(Ok(()), |(q, _)| place(q))?;
232 f.depending.map_or(Ok(()), |d| place(d.item))?;
233 f.assign_item.map_or(Ok(()), |a| place(a.place))?;
234 if let Some(r) = &f.relative {
235 place(r.place)?;
236 int(&r.value)?;
237 }
238 if let Some(l) = &f.linage {
239 int(&l.lines)?;
240 [&l.footing, &l.top, &l.bottom].into_iter().flatten().try_for_each(int)?;
241 l.counter.map_or(Ok(()), |(q, _)| place(q))?;
242 }
243 }
244 verify_scope(p, &range)?;
245 let declaratives = &p.services.declaratives;
246 declaratives.modes.iter().flatten().try_for_each(|&r| range(r, RangeKind::UseProcedure))?;
247 if let Some((offset, len)) = declaratives.debug_item
248 && offset.checked_add(len).is_none_or(|end| end > p.storage.size)
249 {
250 return Err(format!("DEBUG-ITEM at {offset} for {len} in a slab of {}", p.storage.size));
251 }
252 for op in &p.services.file_ops {
253 let Some(f) = p.services.files.get(op.file as usize) else { return Err(format!("file {} of {}", op.file, p.services.files.len())) };
254 let keys = f.keys.as_ref().map_or(0, |k| k.alternates.len() + 1);
255 match &op.verb {
256 FileVerb::Open(_) | FileVerb::Close | FileVerb::CloseWith(_) | FileVerb::Delete | FileVerb::DeleteFile => {}
257 FileVerb::Read { into, key, .. } => {
258 into.map_or(Ok(()), |(q, _)| place(q))?;
259 if *key != 0 && usize::from(*key) >= keys {
260 return Err(format!("key {key} of a file with {keys}"));
261 }
262 }
263 FileVerb::Write { record, from, advancing } => {
264 place(*record)?;
265 from.map_or(Ok(()), |m| sender(&m.from, m.check).and_then(|()| place(m.to)))?;
266 if let Some(Advance::Lines { count, .. }) = advancing {
267 int(count)?;
268 }
269 }
270 FileVerb::Rewrite { record, from } => {
271 place(*record)?;
272 from.map_or(Ok(()), |m| sender(&m.from, m.check).and_then(|()| place(m.to)))?;
273 }
274 FileVerb::Start { key, .. } => match key {
275 StartKey::Named { key, .. } if usize::from(*key) >= keys => return Err(format!("key {key} of a file with {keys}")),
276 StartKey::Relative(n) => int(n)?,
277 _ => {}
278 },
279 }
280 }
281 let chars = |c: &Chars| match c {
282 Chars::Literal(_) => Ok(()),
283 Chars::Place(q) => place(*q),
284 Chars::Value(o) => operand(o),
285 };
286 let bounds = |bs: &[Bound]| bs.iter().try_for_each(|b| chars(&b.value));
287 let store = |s: &StorePlan| if let StorePlan::Refused(a) = s { abend(*a) } else { Ok(()) };
288 let moved = |m: &MovePlan| if let MovePlan::Refused(a) = m { abend(*a) } else { Ok(()) };
289 for plan in &p.plans.string {
290 place(plan.into)?;
291 plan.pointer.as_ref().map_or(Ok(()), |(q, s)| place(*q).and_then(|()| store(s)))?;
292 for source in &plan.sources {
293 chars(&source.chars)?;
294 source.delimiter.as_ref().map_or(Ok(()), chars)?;
295 }
296 }
297 for plan in &p.plans.unstring {
298 place(plan.source)?;
299 plan.pointer.as_ref().map_or(Ok(()), |(q, s)| place(*q).and_then(|()| store(s)))?;
300 plan.delimiters.iter().try_for_each(|(_, d)| chars(d))?;
301 for field in &plan.into {
302 place(field.target)?;
303 moved(&field.plan)?;
304 if let Some(d) = &field.delimiter {
305 place(d.target)?;
306 moved(&d.found)?;
307 moved(&d.none)?;
308 }
309 field.count.as_ref().map_or(Ok(()), |(q, s)| place(*q).and_then(|()| store(s)))?;
310 }
311 plan.tallying.as_ref().map_or(Ok(()), |(q, s)| place(*q).and_then(|()| store(&s.store)))?;
312 }
313 for plan in &p.plans.inspect {
314 match &plan.target {
315 Inspected::Item(q) => place(*q)?,
316 Inspected::Value(o) => operand(o)?,
317 }
318 for phrase in plan.tallying.iter().chain(&plan.replacing) {
319 phrase.pattern.as_ref().map_or(Ok(()), chars)?;
320 if let Some(Replacement::Chars(c)) = &phrase.by {
321 chars(c)?;
322 }
323 phrase.counter.as_ref().map_or(Ok(()), |(q, s)| place(*q).and_then(|()| store(&s.store)))?;
324 bounds(&phrase.bounds)?;
325 }
326 if let Some(c) = &plan.converting {
327 if let ConvertTable::Operands { from, to } = &c.table {
328 chars(from)?;
329 chars(to)?;
330 }
331 bounds(&c.bounds)?;
332 }
333 }
334 for plan in &p.plans.search_all {
335 place(plan.index)?;
336 store(&plan.store)?;
337 if let Count::Odo(o) = &plan.count {
338 int(&o.object)?;
339 }
340 for key in &plan.keys {
341 comparand(&key.key)?;
342 comparand(&key.value)?;
343 if let Compare::Refused(a) = key.how {
344 abend(a)?;
345 }
346 }
347 }
348 for c in &p.services.calls {
349 match &c.target {
350 CallTarget::Named { name, .. } => symbol(*name)?,
351 CallTarget::Dynamic(o) => operand(o)?,
352 CallTarget::Pointer(q) | CallTarget::Entry(q) => place(*q)?,
353 }
354 for a in &c.args {
355 match a {
356 CallArg::Reference(q) => place(*q)?,
357 CallArg::Content(ch) => chars(ch)?,
358 CallArg::Value(o) => operand(o)?,
359 CallArg::Omitted => {}
360 }
361 if matches!(c.target, CallTarget::Pointer(_)) && !matches!(a, CallArg::Value(_) | CallArg::Omitted) {
362 return Err("a CALL through a pointer with an argument that is not a value".into());
363 }
364 }
365 c.returning.map_or(Ok(()), place)?;
366 }
367 for i in &p.services.invokes {
368 match i.receiver {
369 Receiver::SelfRef | Receiver::Super => {}
370 Receiver::Class { name, external } => {
371 symbol(name)?;
372 symbol(external)?;
373 }
374 Receiver::Object(q) => place(q)?,
375 }
376 match i.method {
377 MethodName::New => {}
378 MethodName::Named(s) => symbol(s)?,
379 MethodName::Dynamic(q) => place(q)?,
380 }
381 for (o, java) in &i.args {
382 operand(o)?;
383 symbol(*java)?;
384 }
385 if let Some((q, java)) = i.returning {
386 place(q)?;
387 symbol(java)?;
388 }
389 }
390 for u in &p.services.user_functions {
391 symbol(u.name)?;
392 symbol(u.external)?;
393 for a in &u.args {
394 match a {
395 UserArgument::Reference(q) => place(*q)?,
396 UserArgument::Value(c) => comparand(c)?,
397 }
398 }
399 if let Some(r) = &u.refmod {
400 int(&r.start)?;
401 r.length.as_ref().map_or(Ok(()), int)?;
402 if r.check {
403 return Err("a user-defined function's reference modification with an SSRANGE check".into());
404 }
405 }
406 within("debug entry", u.at, p.debug.positions.len())?;
407 }
408 if let Some(f) = &p.services.function {
409 let record = |q: PlaceId| p.places.get(q as usize).map(|q| q.base);
410 let using: Vec<_> = p.storage.using.iter().map(|&o| Some(Base::Linkage(o))).collect();
411 if f.params.iter().map(|&q| record(q)).ne(using) || record(f.returning) != p.storage.returning.map(Base::Linkage) {
412 return Err("a function definition whose places are not its USING and RETURNING records".into());
413 }
414 }
415 for c in &p.services.cics {
416 c.clone().map(&mut Ids { place: &place, operand: &operand, symbol: &symbol })?;
417 c.command.labels().into_iter().try_for_each(|q| within("paragraph", q, p.paragraphs.len()))?;
418 }
419 let host = |hs: &[HostPlace]| {
420 hs.iter().try_for_each(|h| {
421 place(h.var)?;
422 h.ty.as_ref().map_or_else(|&a| abend(a), |_| Ok(()))?;
423 h.indicator.map_or(Ok(()), |(q, _)| place(q))
424 })
425 };
426 let rows_host = |rows: &RowCount| match rows {
427 RowCount::Host(h) => host(std::slice::from_ref(h)),
428 RowCount::Implicit | RowCount::Constant(_) => Ok(()),
429 };
430 for e in &p.sql {
431 symbol(e.verb)?;
432 symbol(e.text)?;
433 match &e.statement {
434 SqlStatement::Query { inputs, into } => host(inputs).and_then(|()| host(into))?,
435 SqlStatement::Change { inputs, current_of, .. } => host(inputs).and_then(|()| current_of.map_or(Ok(()), symbol))?,
436 SqlStatement::Open { cursor, inputs } => symbol(*cursor).and_then(|()| host(inputs))?,
437 SqlStatement::Fetch { cursor, into } => symbol(*cursor).and_then(|()| host(into))?,
438 SqlStatement::Close { cursor: s } | SqlStatement::Unsupported(s) => symbol(*s)?,
439 SqlStatement::Connect { what, location } => symbol(*what).and_then(|()| host(location))?,
440 SqlStatement::Prepare { name, source: inputs } | SqlStatement::Execute { name, inputs } => symbol(*name).and_then(|()| host(inputs))?,
441 SqlStatement::ExecuteImmediate { source } => host(source)?,
442 SqlStatement::OpenPrepared { cursor, statement, inputs } => symbol(*cursor).and_then(|()| symbol(*statement)).and_then(|()| host(inputs))?,
443 SqlStatement::Describe { name, descriptor, .. } | SqlStatement::ExecuteDescriptor { name, descriptor } | SqlStatement::FetchDescriptor { cursor: name, descriptor } => symbol(*name).and_then(|()| place(*descriptor))?,
444 SqlStatement::PrepareInto { name, source, descriptor, .. } => symbol(*name).and_then(|()| host(source)).and_then(|()| place(*descriptor))?,
445 SqlStatement::OpenDescriptor { cursor, statement, descriptor } => symbol(*cursor).and_then(|()| symbol(*statement)).and_then(|()| place(*descriptor))?,
446 SqlStatement::FetchRowset { cursor, rows, into, .. } => {
447 symbol(*cursor)?;
448 rows_host(rows)?;
449 host(&into.iter().map(|a| a.place.clone()).collect::<Vec<_>>())?
450 }
451 SqlStatement::InsertRows { inputs, rows, .. } => {
452 rows_host(rows)?;
453 host(&inputs.iter().map(|a| a.place.clone()).collect::<Vec<_>>())?
454 }
455 SqlStatement::Call { procedure, args } => symbol(*procedure).and_then(|()| host(args))?,
456 SqlStatement::Commit | SqlStatement::Rollback | SqlStatement::Declaration => {}
457 }
458 }
459 p.services.sqlca.fields.iter().try_for_each(|&(_, q, _)| place(q))?;
460 let count = |c: &Count| match c {
461 Count::Fixed(_) | Count::Temp(_) => Ok(()),
462 Count::Odo(o) => int(&o.object),
463 };
464 let marker = |m: &Marker| match *m {
465 Marker::Byte(_) => Ok(()),
466 Marker::Condition(c) => cond(c),
467 Marker::Refused(a) => abend(a),
468 };
469 let convert = |c: &Convert| if let Convert::Refused(a) = *c { abend(a) } else { Ok(()) };
470 let constant = |c: u32| within("constant", c, p.consts.len());
471 let set_to = |s: &SetTo| match *s {
472 SetTo::Nothing => Ok(()),
473 SetTo::Move { place: q, value, .. } => place(q).and_then(|()| constant(value)),
474 SetTo::Refused(a) => abend(a),
475 };
476 let flag = |f: &Flag| match f {
477 Flag::Set { on, off } => set_to(on).and_then(|()| set_to(off)),
478 Flag::Literals { on, off } => constant(on.0).and_then(|()| constant(off.0)),
479 };
480 let ccsid = |c: &Ccsid| if let Ccsid::Operand(o) = c { operand(o) } else { Ok(()) };
481 let members = |k: usize, members: &[u32], nodes: usize| match members.iter().find(|&&m| m as usize <= k || m as usize >= nodes) {
482 Some(m) => Err(format!("markup node {k} holds node {m} of {nodes}")),
483 None => Ok(()),
484 };
485 for m in &p.services.markup {
486 match m {
487 Markup::JsonGenerate(g) => {
488 place(g.from)?;
489 g.subscripts.iter().try_for_each(int)?;
490 g.name.map_or(Ok(()), symbol)?;
491 place(g.receiver)?;
492 ccsid(&g.encoding)?;
493 g.count.map_or(Ok(()), |(q, _)| place(q))?;
494 place(g.code.0)?;
495 for (k, n) in g.nodes.iter().enumerate() {
496 symbol(n.name)?;
497 n.occurs.as_ref().map_or(Ok(()), count)?;
498 if let Some((at, test)) = &n.indicator {
499 at.map_or_else(abend, place)?;
500 marker(test)?;
501 }
502 match &n.value {
503 JsonValue::Object { members: held, .. } => members(k, held, g.nodes.len())?,
504 JsonValue::Leaf(leaf) => {
505 leaf.boolean.as_ref().map_or(Ok(()), marker)?;
506 convert(&leaf.convert)?;
507 }
508 }
509 }
510 }
511 Markup::XmlGenerate(g) => {
512 place(g.receiver)?;
513 ccsid(&g.encoding)?;
514 g.namespace.iter().chain(&g.prefix).try_for_each(operand)?;
515 place(g.from)?;
516 g.subscripts.iter().try_for_each(int)?;
517 g.count.map_or(Ok(()), |(q, _)| place(q))?;
518 place(g.code.0)?;
519 for (k, n) in g.nodes.iter().enumerate() {
520 symbol(n.name)?;
521 n.occurs.as_ref().map_or(Ok(()), count)?;
522 match &n.value {
523 XmlValue::Element { members: held } | XmlValue::Members { members: held } => members(k, held, g.nodes.len())?,
524 XmlValue::Leaf { convert: c, .. } => convert(c)?,
525 }
526 }
527 }
528 Markup::XmlParse(x) => {
529 place(x.document)?;
530 x.encoding.as_ref().map_or(Ok(()), operand)?;
531 range(x.procedure, RangeKind::Processing)?;
532 place(x.event)?;
533 place(x.code.0)?;
534 place(x.information.0)?;
535 int(&x.code_value)?;
536 }
537 Markup::JsonParse(j) => {
538 place(j.source)?;
539 ccsid(&j.encoding)?;
540 place(j.into)?;
541 j.subscripts.iter().try_for_each(int)?;
542 place(j.code.0)?;
543 place(j.status.0)?;
544 for (k, n) in j.nodes.iter().enumerate() {
545 if let Named::Exactly(name) | Named::Folded(name) = n.name {
546 symbol(name)?;
547 }
548 n.occurs.as_ref().map_or(Ok(()), count)?;
549 if let Some(i) = &n.indicator {
550 i.place.map_or(Ok(()), |at| at.map_or_else(abend, place))?;
551 flag(&i.flag)?;
552 }
553 match &n.value {
554 ParseValue::Object { members: held } => members(k, held, j.nodes.len())?,
555 ParseValue::Leaf(leaf) => leaf.boolean.as_ref().map_or(Ok(()), flag)?,
556 ParseValue::Suppressed => {}
557 }
558 }
559 }
560 }
561 }
562 let files = |k: u16| within("file", u32::from(k), p.services.files.len());
563 for s in &p.services.sorts {
564 match s {
565 SortPlan::File(f) => {
566 files(f.sd)?;
567 for io in f.input.iter().chain(&f.output) {
568 match io {
569 SortIo::Files(ks) => ks.iter().try_for_each(|&k| files(k))?,
570 SortIo::Procedure(r) => range(*r, RangeKind::SortProcedure)?,
571 }
572 }
573 place(f.sort_return)?;
574 place(f.sort_control)?;
575 }
576 SortPlan::Table(t) => {
577 place(t.first)?;
578 count(&t.count)?;
579 symbol(t.name)?;
580 }
581 }
582 }
583 for r in &p.services.releases {
584 place(r.record)?;
585 r.file.map_or(Ok(()), files)?;
586 r.from.as_ref().map_or(Ok(()), |m| sender(&m.from, m.check).and_then(|()| place(m.to)).and_then(|()| moved(&m.plan)))?;
587 place(r.sort_return)?;
588 symbol(r.name)?;
589 }
590 for r in &p.services.returns {
591 r.file.map_or(Ok(()), files)?;
592 r.into.as_ref().map_or(Ok(()), |(q, m)| place(*q).and_then(|()| moved(m)))?;
593 place(r.sort_return)?;
594 symbol(r.name)?;
595 }
596 let writer = &p.services.report;
597 let item = |k: usize| within("report item", u32::try_from(k).unwrap_or(u32::MAX), p.places.len());
598 writer.print_switch.map_or(Ok(()), item)?;
599 for r in &writer.reports {
600 within("report file", u32::try_from(r.file).unwrap_or(u32::MAX), p.services.files.len())?;
601 r.code.map_or(Ok(()), constant)?;
602 r.controls.iter().try_for_each(|c| place(c.reference))?;
603 [r.page_counter, r.line_counter, r.state].into_iter().try_for_each(item)?;
604 r.sums.iter().try_for_each(|s| item(s.total))?;
605 let group = |g: usize| within("report group", u32::try_from(g).unwrap_or(u32::MAX), r.groups.len());
606 let sum = |s: usize| within("SUM", u32::try_from(s).unwrap_or(u32::MAX), r.sums.len());
607 [r.report_heading, r.page_heading, r.page_footing, r.report_footing].into_iter().flatten().try_for_each(group)?;
608 r.control_headings.iter().chain(&r.control_footings).flatten().try_for_each(|&g| group(g))?;
609 let origin = |o: &Origin| match o {
610 Origin::Source(c) => comparand(c),
611 Origin::Value(v) => constant(*v),
612 Origin::Total(t) => sum(*t),
613 };
614 for st in &r.subtotals {
615 sum(st.sum)?;
616 comparand(&st.operand)?;
617 }
618 for g in &r.groups {
619 for f in g.lines.iter().flat_map(|l| &l.fields).chain(&g.unprinted) {
620 item(f.item)?;
621 match &f.content {
622 FieldContent::Source(c) => comparand(c)?,
623 FieldContent::Value(v) => constant(*v)?,
624 FieldContent::Sum(s) => sum(*s)?,
625 FieldContent::Program => {}
626 }
627 }
628 for (s, o) in g.cross.iter().chain(&g.rolls) {
629 sum(*s)?;
630 origin(o)?;
631 }
632 g.totals.iter().try_for_each(|&s| sum(s))?;
633 g.declarative.map_or(Ok(()), |d| range(d, RangeKind::UseBeforeReporting))?;
634 }
635 }
636 let report = |op: &ReportOp| {
637 let report = |ri: u32| match writer.reports.get(ri as usize) {
638 Some(r) => Ok(r),
639 None => Err(format!("report {ri} of {}", writer.reports.len())),
640 };
641 match *op {
642 ReportOp::Initiate(ri) | ReportOp::Terminate(ri) => report(ri).map(drop),
643 ReportOp::Generate { report: ri, detail } => {
644 let r = report(ri)?;
645 match detail.map(|d| r.groups.get(d as usize).map(|g| g.kind)) {
646 None | Some(Some(GroupKind::Detail)) => Ok(()),
647 Some(_) => Err(format!("GENERATE of report {ri}'s group {detail:?}, which is not a DETAIL group")),
648 }
649 }
650 ReportOp::Suppress => Ok(()),
651 }
652 };
653 for e in &p.services.entries {
654 symbol(e.name)?;
655 within("paragraph", e.paragraph, p.paragraphs.len())?;
656 block(e.block)?;
657 e.using.iter().try_for_each(|&r| within("LINKAGE record", u32::from(r), p.storage.linkage.len()))?;
658 }
659 for d in &p.plans.display {
660 for item in &d.items {
661 match item {
662 DisplayItem::Bytes(q) | DisplayItem::National(q) | DisplayItem::Digits { place: q, .. } => place(*q)?,
663 DisplayItem::Refused { place: q, abend: a } => {
664 place(*q)?;
665 abend(*a)?;
666 }
667 DisplayItem::Text(t) => within("symbol", *t, p.symbols.len())?,
668 DisplayItem::Value(o) => operand(o)?,
669 }
670 }
671 }
672 for f in p.plans.init.iter().flat_map(|i| &i.fields) {
673 match f.value {
674 InitValue::Default(_) => {}
675 InitValue::Value(c) => within("constant", c, p.consts.len())?,
676 InitValue::Replacing(o) => operand(&o)?,
677 }
678 match f.store {
679 MovePlan::Refused(a) | MovePlan::Numeric { store: StorePlan::Refused(a), .. } => abend(a)?,
680 _ => {}
681 }
682 }
683
684 if p.debug.ops.len() != p.blocks.len() {
685 return Err(format!("debug entries for {} blocks of {}", p.debug.ops.len(), p.blocks.len()));
686 }
687 if p.debug.statements.len() != p.blocks.len() {
688 return Err(format!("statement starts for {} blocks of {}", p.debug.statements.len(), p.blocks.len()));
689 }
690 for (b, (blk, starts)) in p.blocks.iter().zip(&p.debug.statements).enumerate() {
691 if starts.windows(2).any(|w| w[0].0 > w[1].0) || starts.last().is_some_and(|&(k, _)| k as usize > blk.ops.len()) {
692 return Err(format!("block {b}: statement starts out of order or past its {} ops and terminator", blk.ops.len()));
693 }
694 starts.iter().try_for_each(|&(_, id)| within("debug entry", id, p.debug.positions.len()))?;
695 }
696 for (b, (blk, ids)) in p.blocks.iter().zip(&p.debug.ops).enumerate() {
697 if ids.len() != blk.ops.len() + 1 {
698 return Err(format!("block {b}: {} debug entries for {} ops and a terminator", ids.len(), blk.ops.len()));
699 }
700 ids.iter().try_for_each(|&id| within("debug entry", id, p.debug.positions.len()))?;
701 let arms = |op: &Op| match op {
703 Op::Arith(a) if p.plans.arith.get(*a as usize).is_some_and(|plan| plan.handled) => 2,
704 Op::Call(c) if p.services.calls.get(*c as usize).is_some_and(|plan| plan.on_exception || plan.not_on_exception) => 2,
705 Op::Invoke(i) if p.services.invokes.get(*i as usize).is_some_and(|plan| plan.on_exception || plan.not_on_exception) => 2,
706 Op::File(f) => p.services.file_ops.get(*f as usize).map_or(0, |op| op.arms()),
707 Op::String(_) | Op::Unstring(_) | Op::SearchAll(_) | Op::Return(_) | Op::Accept { from: AcceptFrom::ArgumentValue | AcceptFrom::EnvironmentValue, .. } => 2,
708 Op::Markup(m) if p.services.markup.get(*m as usize).is_some_and(|x| x.phrases() != (false, false)) => 2,
709 Op::ScreenAccept { handled: true, .. } => 2,
710 _ => 0,
711 };
712 let armed = |op: &Op| arms(op) > 0;
713 let last = blk.ops.len().saturating_sub(1);
714 if blk.ops.iter().enumerate().any(|(k, op)| armed(op) && (k != last || !matches!(blk.end, Terminator::Select(_)))) {
715 return Err(format!("block {b}: an op that returns an arm is not followed by its Select"));
716 }
717 for op in &blk.ops {
718 match op {
719 Op::Move { from, to, check, .. } => {
720 sender(from, *check)?;
721 place(*to)?;
722 }
723 Op::Set { from, to, .. } => {
724 operand(from)?;
725 place(*to)?;
726 }
727 Op::Initialize { target, plan } => {
728 place(*target)?;
729 within("INITIALIZE plan", *plan, p.plans.init.len())?;
730 }
731 Op::Arith(a) => within("arithmetic plan", *a, p.plans.arith.len())?,
732 Op::Step { var, by, prepass, .. } => {
733 place(*var)?;
734 expr(*by)?;
735 places(prepass)?;
736 }
737 Op::SetTemp(_, n) | Op::ArgumentNumber(n) => int(n)?,
738 Op::SetCount(_, o) if o.check != ssrange => return Err(format!("block {b}: a SEARCH count's check that disagrees with SSRANGE")),
739 Op::SetCount(_, o) => int(&o.object)?,
740 Op::Display(d) | Op::DisplayError(d) => within("DISPLAY plan", *d, p.plans.display.len())?,
741 Op::Environment { display, .. } => within("DISPLAY plan", *display, p.plans.display.len())?,
742 Op::ScreenDisplay { display, screen } => {
743 within("DISPLAY plan", *display, p.plans.display.len())?;
744 screen_ints(screen).try_for_each(int)?;
745 }
746 Op::ScreenAccept { inputs, .. } => {
747 for i in inputs {
748 place(i.target)?;
749 place(i.field)?;
750 position_ints(&i.at).try_for_each(int)?;
751 }
752 }
753 Op::Call(c) => within("CALL plan", *c, p.services.calls.len())?,
754 Op::Cancel(o) => operand(o)?,
755 Op::Invoke(i) => within("INVOKE plan", *i, p.services.invokes.len())?,
756 Op::Alter { para, to } => {
757 within("paragraph", *para, p.paragraphs.len())?;
758 within("paragraph", *to, p.paragraphs.len())?;
759 }
760 Op::File(f) => within("file statement", *f, p.services.file_ops.len())?,
761 Op::Markup(m) => within("JSON or XML statement", *m, p.services.markup.len())?,
762 Op::Cics(c) => within("EXEC CICS command", *c, p.services.cics.len())?,
763 Op::Sort(s) => within("SORT or MERGE", *s, p.services.sorts.len())?,
764 Op::Release(r) => within("RELEASE", *r, p.services.releases.len())?,
765 Op::Return(r) => within("RETURN", *r, p.services.returns.len())?,
766 Op::Report(op) => report(op)?,
767 Op::Sql(k) if *k == 0 || *k as usize > p.sql.len() => return Err(format!("block {b}: EXEC SQL ordinal {k} of {}", p.sql.len())),
768 Op::Sql(_) => {}
769 Op::SetAddress { records, address } => {
770 records.iter().try_for_each(|&r| within("LINKAGE record", u32::from(r), p.storage.linkage.len()))?;
771 operand(address)?;
772 }
773 Op::SetEntry { entry, targets } => {
774 operand(entry)?;
775 places(targets)?;
776 }
777 Op::SetUpDown { by, targets, .. } => {
778 int(by)?;
779 for (q, how) in targets {
780 place(*q)?;
781 if let UpDown::Refused(a) = how {
782 abend(*a)?;
783 }
784 }
785 }
786 Op::String(id) => within("STRING plan", *id, p.plans.string.len())?,
787 Op::Unstring(id) => within("UNSTRING plan", *id, p.plans.unstring.len())?,
788 Op::Inspect(id) => within("INSPECT plan", *id, p.plans.inspect.len())?,
789 Op::SearchAll(id) => within("SEARCH ALL plan", *id, p.plans.search_all.len())?,
790 Op::SetInt { target, value } => {
791 place(*target)?;
792 int(value)?;
793 }
794 Op::Accept { target, plan, .. } => {
795 place(*target)?;
796 moved(plan)?;
797 }
798 Op::DebugAlter { range: r, name, contents } => {
799 range(*r, RangeKind::Debugging)?;
800 symbol(*name)?;
801 symbol(*contents)?;
802 }
803 Op::Nest | Op::Unnest(_) | Op::DecTemp(_) | Op::EnterSegment(_) | Op::DebugLine(_) => {}
804 }
805 }
806 match &blk.end {
807 Terminator::Jump(t) => block(*t)?,
808 Terminator::Branch { cond: c, then, otherwise } => {
809 cond(*c)?;
810 block(*then)?;
811 block(*otherwise)?;
812 }
813 Terminator::Select(targets) => {
814 targets.iter().try_for_each(|&t| block(t))?;
815 if blk.ops.last().map_or(0, arms) != targets.len() {
816 return Err(format!("block {b}: a Select that does not follow an op with its phrases"));
817 }
818 }
819 Terminator::AlteredGoTo { para, otherwise } => {
820 within("paragraph", *para, p.paragraphs.len())?;
821 block(*otherwise)?;
822 }
823 Terminator::ParagraphEnd { next } => {
824 if *next as usize > p.paragraphs.len() {
825 return Err(format!("block {b}: paragraph end to {next} of {}", p.paragraphs.len()));
826 }
827 }
828 Terminator::GoTo(t) => within("paragraph", *t, p.paragraphs.len())?,
829 Terminator::Switch { value, targets, otherwise } => {
830 int(value)?;
831 targets.iter().try_for_each(|&t| within("paragraph", t, p.paragraphs.len()))?;
832 block(*otherwise)?;
833 }
834 Terminator::PerformEnter { range: r, ret, resume } => {
835 range(*r, RangeKind::Perform)?;
836 block(*ret)?;
837 if let Some(resume) = resume {
838 within("paragraph", resume.para, p.paragraphs.len())?;
839 block(resume.block)?;
840 }
841 }
842 Terminator::Debug { range: r, name, next } => {
843 range(*r, RangeKind::Debugging)?;
844 symbol(*name)?;
845 block(*next)?;
846 }
847 Terminator::ExitProgram { next } => block(*next)?,
848 Terminator::End(_) => {}
849 Terminator::Abend(a) => abend(*a)?,
850 }
851 }
852 for (k, para) in p.paragraphs.iter().enumerate() {
853 block(para.entry)?;
854 if (para.section_end as usize) < k || para.section_end as usize >= p.paragraphs.len() {
855 return Err(format!("paragraph {k}: section end {}", para.section_end));
856 }
857 para.abandoned.map_or(Ok(()), abend)?;
858 if para.abandoned.is_some() != p.ranges.iter().any(|r| r.last as usize == k) {
859 return Err(format!("paragraph {k}: an abandoned return point's abend where no range ends, or none where one does"));
860 }
861 }
862 for r in &p.ranges {
863 within("paragraph", r.first, p.paragraphs.len())?;
864 within("paragraph", r.last, p.paragraphs.len())?;
865 }
866 if p.procedure_start as usize > p.paragraphs.len() {
867 return Err(format!("procedure start {} of {} paragraphs", p.procedure_start, p.paragraphs.len()));
868 }
869 Ok(())
870}
871
872fn verify_scope(p: &Program, range: &dyn Fn(u32, RangeKind) -> Result<(), String>) -> Result<(), String> {
875 let scope = &p.services.scope;
876 let within = |what: &str, id: usize, len: usize| if id < len { Ok(()) } else { Err(format!("{what} {id} of {len}")) };
877 let symbol = |id: SymId| within("symbol", id as usize, p.symbols.len());
878 let record = |o: u16| within("LINKAGE record", usize::from(o), p.storage.linkage.len());
879 let file = |k: u16| within("file", usize::from(k), p.services.files.len());
880 scope.containers.iter().chain(&scope.callable).chain(&scope.hidden).try_for_each(|&s| symbol(s))?;
881 let mut last = None;
882 for &(o, ref binding) in &scope.records {
883 record(o)?;
884 if last.is_some_and(|l| l >= o) {
885 return Err(format!("LINKAGE record {o} bound out of order"));
886 }
887 last = Some(o);
888 match *binding {
889 Binding::External { name, .. } => symbol(name)?,
890 Binding::ExternalFile(k) => file(k)?,
891 Binding::Global { program, name, .. } => symbol(program).and_then(|()| symbol(name))?,
892 }
893 }
894 for f in &scope.files {
895 file(f.file)?;
896 f.declared_in.map_or(Ok(()), symbol)?;
897 if !f.external && f.declared_in.is_none() {
898 return Err(format!("file {} shared as neither EXTERNAL nor GLOBAL", f.file));
899 }
900 }
901 for &(k, o) in &scope.areas {
902 file(k)?;
903 record(o)?;
904 }
905 for g in &scope.globals {
906 symbol(g.name)?;
907 match g.at {
908 GlobalAt::Program(offset) => within("GLOBAL record offset", offset as usize, p.storage.image.len())?,
909 GlobalAt::Local(offset) => within("GLOBAL LOCAL-STORAGE offset", offset as usize, p.storage.local_image.len())?,
910 GlobalAt::Linkage(o) => record(o)?,
911 }
912 }
913 for &(k, r) in &scope.global_files {
914 file(k)?;
915 range(r, RangeKind::UseProcedure)?;
916 }
917 scope.global_modes.iter().flatten().try_for_each(|&r| range(r, RangeKind::UseProcedure))
918}
919
920fn screen_ints(screen: &rt::lir::ScreenPlan) -> impl Iterator<Item = &IntExpr> {
922 position_ints(&screen.at)
923}
924
925fn position_ints(at: &rt::lir::ScreenPosition) -> impl Iterator<Item = &IntExpr> {
927 let (a, b, c) = match at {
928 rt::lir::ScreenPosition::Cursor => (None, None, None),
929 rt::lir::ScreenPosition::Combined(at) => (Some(at), None, None),
930 rt::lir::ScreenPosition::LineColumn { line, column } => (None, line.as_ref(), column.as_ref()),
931 };
932 a.into_iter().chain(b).chain(c)
933}