1use rt::cics::Handles;
6use rt::lir::{
7 Advance, Argument, Bound, CallArg, CallTarget, Ccsid, Chars, Comparand, Compare, Cond, Convert, ConvertTable, Count, DisplayItem, Expr, FileVerb, Flag, Func, HostPlace, IntExpr,
8 JsonValue, Marker, Markup, MethodName, MovePlan, Named, Op, Operand, ParseValue, Place, PlaceId, Program, RangeKind, Receiver, Replacement, SetTo, SqlStatement, StartKey,
9 StorePlan, SymId, Terminator, UpDown, XmlValue,
10};
11
12type Check<'a, T> = &'a dyn Fn(T) -> Result<(), String>;
13
14struct Ids<'a> {
16 place: Check<'a, PlaceId>,
17 operand: &'a dyn Fn(&Operand) -> Result<(), String>,
18 symbol: Check<'a, SymId>,
19}
20
21impl Handles<PlaceId, Operand, SymId> for Ids<'_> {
22 type Place = ();
23 type Value = ();
24 type Text = ();
25 type Error = String;
26
27 fn place(&mut self, place: PlaceId) -> Result<(), String> {
28 (self.place)(place)
29 }
30
31 fn value(&mut self, value: Operand) -> Result<(), String> {
32 (self.operand)(&value)
33 }
34
35 fn text(&mut self, text: SymId) -> Result<(), String> {
36 (self.symbol)(text)
37 }
38}
39
40pub fn verify(p: &Program) -> Result<(), String> {
42 verify_program(p)?;
43 let Some(class) = &p.services.class else { return Ok(()) };
44 for part in class.factory.iter().chain(&class.object) {
45 verify(&part.data).map_err(|e| format!("class data: {e}"))?;
46 }
47 for m in &class.methods {
48 verify(&m.code).map_err(|e| format!("method {}: {e}", p.symbols.get(m.name as usize).map_or("", String::as_str)))?;
49 }
50 let symbol = |id: u32| if (id as usize) < p.symbols.len() { Ok(()) } else { Err(format!("symbol {id} of {}", p.symbols.len())) };
51 symbol(class.external)?;
52 symbol(class.parent)?;
53 for m in &class.methods {
54 symbol(m.name)?;
55 m.params.iter().chain(&m.returns).try_for_each(|&s| symbol(s))?;
56 }
57 Ok(())
58}
59
60fn verify_program(p: &Program) -> Result<(), String> {
61 let within = |what: &str, id: u32, len: usize| if (id as usize) < len { Ok(()) } else { Err(format!("{what} {id} of {len}")) };
62 let block = |id| within("block", id, p.blocks.len());
63 let place = |id| within("place", id, p.places.len());
64 let expr = |id| within("expression", id, p.exprs.len());
65 let cond = |id| within("condition", id, p.conds.len());
66 let abend = |id| within("abend", id, p.abends.len());
67 let range = |id: u32, kind: RangeKind| match p.ranges.get(id as usize) {
68 Some(r) if r.kind == kind => Ok(()),
69 Some(r) => Err(format!("range {id} is {:?} where {kind:?} is wanted", r.kind)),
70 None => Err(format!("range {id} of {}", p.ranges.len())),
71 };
72 let places = |qs: &[u32]| qs.iter().try_for_each(|&q| place(q));
73 let int = |e: &IntExpr| match e {
74 IntExpr::Const(_) | IntExpr::Walk(_) => Ok(()),
75 IntExpr::Item(q) => place(*q),
76 IntExpr::Fixed { expr: e, prepass, .. } => expr(*e).and_then(|()| places(prepass)),
77 };
78 let operand = |o: &Operand| match *o {
79 Operand::Load(q) | Operand::LengthOf(q) | Operand::AddressOf(q) => place(q),
80 Operand::Const(c) => within("constant", c, p.consts.len()),
81 Operand::Function(f) => within("function plan", f, p.plans.function.len()),
82 };
83 let comparand = |c: &Comparand| match c {
84 Comparand::Operand(o) => operand(o),
85 Comparand::Expr { expr: e, prepass, .. } => expr(*e).and_then(|()| places(prepass)),
86 };
87 let ssrange = p.options.ssrange;
88
89 for (k, q) in p.places.iter().enumerate() {
90 let Place { subscripts, odo, refmod, at, .. } = q;
91 within("debug entry", *at, p.debug.positions.len())?;
92 for s in subscripts {
93 int(&s.value)?;
94 if s.check.is_some() != ssrange {
95 return Err(format!("place {k}: a subscript check without SSRANGE, or none with it"));
96 }
97 }
98 if let Some(o) = odo {
99 int(&o.object)?;
100 if o.check != ssrange {
101 return Err(format!("place {k}: an OCCURS DEPENDING ON check that disagrees with SSRANGE"));
102 }
103 }
104 if let Some(r) = refmod {
105 int(&r.start)?;
106 r.length.as_ref().map_or(Ok(()), int)?;
107 if r.check != ssrange {
108 return Err(format!("place {k}: a reference-modification check that disagrees with SSRANGE"));
109 }
110 }
111 }
112 for e in &p.exprs {
113 match e {
114 Expr::Operand(o) => operand(o)?,
115 Expr::Neg(a) => expr(*a)?,
116 Expr::Bin(a, _, b) => {
117 expr(*a)?;
118 expr(*b)?;
119 }
120 Expr::Pow(a, n) => {
121 expr(*a)?;
122 int(n)?;
123 }
124 }
125 }
126 for c in &p.conds {
127 match c {
128 Cond::Rel { a, b, .. } => {
129 comparand(a)?;
130 comparand(b)?;
131 }
132 Cond::Class { place: q, .. } => place(*q)?,
133 Cond::Sign { value, .. } => comparand(value)?,
134 Cond::Name { subject, values, .. } => {
135 place(*subject)?;
136 for &(low, high) in values {
137 within("constant", low, p.consts.len())?;
138 high.map_or(Ok(()), |h| within("constant", h, p.consts.len()))?;
139 }
140 }
141 Cond::Not(a) => cond(*a)?,
142 Cond::And(a, b) | Cond::Or(a, b) => {
143 cond(*a)?;
144 cond(*b)?;
145 }
146 Cond::Counter(_) | Cond::Sql(_) => {}
147 Cond::InTable { index, count } => {
148 place(*index)?;
149 if let Count::Odo(o) = count {
150 int(&o.object)?;
151 }
152 }
153 }
154 }
155 for a in &p.abends {
156 a.at.map_or(Ok(()), |at| within("debug entry", at, p.debug.positions.len()))?;
157 }
158 for a in &p.plans.arith {
159 places(&a.prepass)?;
160 for s in &a.steps {
161 place(s.target)?;
162 expr(s.expr)?;
163 places(&s.probe)?;
164 }
165 if let Some(r) = &a.remainder {
166 place(r.target)?;
167 expr(r.dividend)?;
168 expr(r.divisor)?;
169 }
170 }
171 let symbol = |id: u32| within("symbol", id, p.symbols.len());
172 if p.plans.function.iter().any(|f| f.func == Func::WhenCompiled) != p.options.when_compiled.is_some() {
173 return Err("a compile time without a WHEN-COMPILED plan, or a WHEN-COMPILED plan without one".into());
174 }
175 for f in &p.plans.function {
176 for a in &f.args {
177 match a {
178 Argument::Value(c) => comparand(c)?,
179 Argument::All { element, all } => {
180 place(*element)?;
181 let subscripts = p.places.get(*element as usize).map_or(0, |q| q.subscripts.len());
182 for (position, count) in all {
183 if *position as usize >= subscripts {
184 return Err(format!("an ALL subscript at {position} of place {element}, which has {subscripts}"));
185 }
186 if let Count::Odo(o) = count {
187 int(&o.object)?;
188 if o.check != ssrange {
189 return Err("an ALL subscript's OCCURS DEPENDING ON check that disagrees with SSRANGE".into());
190 }
191 }
192 }
193 }
194 }
195 }
196 f.integer.as_ref().map_or(Ok(()), int)?;
197 if let Some(r) = &f.refmod {
198 int(&r.start)?;
199 r.length.as_ref().map_or(Ok(()), int)?;
200 if r.check {
201 return Err("a FUNCTION's reference modification with an SSRANGE check".into());
202 }
203 }
204 f.arity.map_or(Ok(()), abend)?;
205 within("debug entry", f.at, p.debug.positions.len())?;
206 }
207 for f in &p.services.files {
208 symbol(f.name)?;
209 symbol(f.assign)?;
210 f.error.map_or(Ok(()), |r| range(r, RangeKind::UseProcedure))?;
211 f.status.map_or(Ok(()), |(q, _)| place(q))?;
212 if let Some(r) = &f.relative {
213 place(r.place)?;
214 int(&r.value)?;
215 }
216 if let Some(l) = &f.linage {
217 int(&l.lines)?;
218 [&l.footing, &l.top, &l.bottom].into_iter().flatten().try_for_each(int)?;
219 l.counter.map_or(Ok(()), |(q, _)| place(q))?;
220 }
221 }
222 let declaratives = &p.services.declaratives;
223 declaratives.modes.iter().flatten().try_for_each(|&r| range(r, RangeKind::UseProcedure))?;
224 if let Some((offset, len)) = declaratives.debug_item
225 && offset.checked_add(len).is_none_or(|end| end > p.storage.size)
226 {
227 return Err(format!("DEBUG-ITEM at {offset} for {len} in a slab of {}", p.storage.size));
228 }
229 for op in &p.services.file_ops {
230 let Some(f) = p.services.files.get(op.file as usize) else { return Err(format!("file {} of {}", op.file, p.services.files.len())) };
231 let keys = f.keys.as_ref().map_or(0, |k| k.alternates.len() + 1);
232 match &op.verb {
233 FileVerb::Open(_) | FileVerb::Close | FileVerb::CloseWith(_) | FileVerb::Delete => {}
234 FileVerb::Read { into, key, .. } => {
235 into.map_or(Ok(()), |(q, _)| place(q))?;
236 if *key != 0 && usize::from(*key) >= keys {
237 return Err(format!("key {key} of a file with {keys}"));
238 }
239 }
240 FileVerb::Write { record, from, advancing } => {
241 place(*record)?;
242 from.map_or(Ok(()), |m| operand(&m.from).and_then(|()| place(m.to)))?;
243 if let Some(Advance::Lines { count, .. }) = advancing {
244 int(count)?;
245 }
246 }
247 FileVerb::Rewrite { record, from } => {
248 place(*record)?;
249 from.map_or(Ok(()), |m| operand(&m.from).and_then(|()| place(m.to)))?;
250 }
251 FileVerb::Start { key, .. } => match key {
252 StartKey::Named { key, .. } if usize::from(*key) >= keys => return Err(format!("key {key} of a file with {keys}")),
253 StartKey::Relative(n) => int(n)?,
254 _ => {}
255 },
256 }
257 }
258 let chars = |c: &Chars| match c {
259 Chars::Literal(_) => Ok(()),
260 Chars::Place(q) => place(*q),
261 Chars::Value(o) => operand(o),
262 };
263 let bounds = |bs: &[Bound]| bs.iter().try_for_each(|b| chars(&b.value));
264 let store = |s: &StorePlan| if let StorePlan::Refused(a) = s { abend(*a) } else { Ok(()) };
265 let moved = |m: &MovePlan| if let MovePlan::Refused(a) = m { abend(*a) } else { Ok(()) };
266 for plan in &p.plans.string {
267 place(plan.into)?;
268 plan.pointer.as_ref().map_or(Ok(()), |(q, s)| place(*q).and_then(|()| store(s)))?;
269 for source in &plan.sources {
270 chars(&source.chars)?;
271 source.delimiter.as_ref().map_or(Ok(()), chars)?;
272 }
273 }
274 for plan in &p.plans.unstring {
275 place(plan.source)?;
276 plan.pointer.as_ref().map_or(Ok(()), |(q, s)| place(*q).and_then(|()| store(s)))?;
277 plan.delimiters.iter().try_for_each(|(_, d)| chars(d))?;
278 for field in &plan.into {
279 place(field.target)?;
280 moved(&field.plan)?;
281 if let Some(d) = &field.delimiter {
282 place(d.target)?;
283 moved(&d.found)?;
284 moved(&d.none)?;
285 }
286 field.count.as_ref().map_or(Ok(()), |(q, s)| place(*q).and_then(|()| store(s)))?;
287 }
288 plan.tallying.as_ref().map_or(Ok(()), |(q, s)| place(*q).and_then(|()| store(&s.store)))?;
289 }
290 for plan in &p.plans.inspect {
291 place(plan.target)?;
292 for phrase in plan.tallying.iter().chain(&plan.replacing) {
293 phrase.pattern.as_ref().map_or(Ok(()), chars)?;
294 if let Some(Replacement::Chars(c)) = &phrase.by {
295 chars(c)?;
296 }
297 phrase.counter.as_ref().map_or(Ok(()), |(q, s)| place(*q).and_then(|()| store(&s.store)))?;
298 bounds(&phrase.bounds)?;
299 }
300 if let Some(c) = &plan.converting {
301 if let ConvertTable::Operands { from, to } = &c.table {
302 chars(from)?;
303 chars(to)?;
304 }
305 bounds(&c.bounds)?;
306 }
307 }
308 for plan in &p.plans.search_all {
309 place(plan.index)?;
310 store(&plan.store)?;
311 if let Count::Odo(o) = &plan.count {
312 int(&o.object)?;
313 }
314 for key in &plan.keys {
315 comparand(&key.key)?;
316 comparand(&key.value)?;
317 if let Compare::Refused(a) = key.how {
318 abend(a)?;
319 }
320 }
321 }
322 for c in &p.services.calls {
323 match &c.target {
324 CallTarget::Named { name, .. } => symbol(*name)?,
325 CallTarget::Dynamic(o) => operand(o)?,
326 CallTarget::Pointer(q) => place(*q)?,
327 }
328 for a in &c.args {
329 match a {
330 CallArg::Reference(q) => place(*q)?,
331 CallArg::Content(ch) => chars(ch)?,
332 CallArg::Value(o) => operand(o)?,
333 CallArg::Omitted => {}
334 }
335 if matches!(c.target, CallTarget::Pointer(_)) && !matches!(a, CallArg::Value(_) | CallArg::Omitted) {
336 return Err("a CALL through a pointer with an argument that is not a value".into());
337 }
338 }
339 c.returning.map_or(Ok(()), place)?;
340 }
341 for i in &p.services.invokes {
342 match i.receiver {
343 Receiver::SelfRef | Receiver::Super => {}
344 Receiver::Class { name, external } => {
345 symbol(name)?;
346 symbol(external)?;
347 }
348 Receiver::Object(q) => place(q)?,
349 }
350 match i.method {
351 MethodName::New => {}
352 MethodName::Named(s) => symbol(s)?,
353 MethodName::Dynamic(q) => place(q)?,
354 }
355 for (o, java) in &i.args {
356 operand(o)?;
357 symbol(*java)?;
358 }
359 if let Some((q, java)) = i.returning {
360 place(q)?;
361 symbol(java)?;
362 }
363 }
364 for c in &p.services.cics {
365 c.clone().map(&mut Ids { place: &place, operand: &operand, symbol: &symbol })?;
366 c.command.labels().into_iter().try_for_each(|q| within("paragraph", q, p.paragraphs.len()))?;
367 }
368 let host = |hs: &[HostPlace]| {
369 hs.iter().try_for_each(|h| {
370 place(h.var)?;
371 h.ty.as_ref().map_or_else(|&a| abend(a), |_| Ok(()))?;
372 h.indicator.map_or(Ok(()), |(q, _)| place(q))
373 })
374 };
375 for e in &p.sql {
376 symbol(e.verb)?;
377 symbol(e.text)?;
378 match &e.statement {
379 SqlStatement::Query { inputs, into } => host(inputs).and_then(|()| host(into))?,
380 SqlStatement::Change { inputs, current_of, .. } => host(inputs).and_then(|()| current_of.map_or(Ok(()), symbol))?,
381 SqlStatement::Open { cursor, inputs } => symbol(*cursor).and_then(|()| host(inputs))?,
382 SqlStatement::Fetch { cursor, into } => symbol(*cursor).and_then(|()| host(into))?,
383 SqlStatement::Close { cursor: s } | SqlStatement::Unsupported(s) => symbol(*s)?,
384 SqlStatement::Commit | SqlStatement::Rollback | SqlStatement::Declaration => {}
385 }
386 }
387 p.services.sqlca.fields.iter().try_for_each(|&(_, q, _)| place(q))?;
388 let count = |c: &Count| match c {
389 Count::Fixed(_) => Ok(()),
390 Count::Odo(o) => int(&o.object),
391 };
392 let marker = |m: &Marker| match *m {
393 Marker::Byte(_) => Ok(()),
394 Marker::Condition(c) => cond(c),
395 Marker::Refused(a) => abend(a),
396 };
397 let convert = |c: &Convert| if let Convert::Refused(a) = *c { abend(a) } else { Ok(()) };
398 let constant = |c: u32| within("constant", c, p.consts.len());
399 let set_to = |s: &SetTo| match *s {
400 SetTo::Nothing => Ok(()),
401 SetTo::Move { place: q, value, .. } => place(q).and_then(|()| constant(value)),
402 SetTo::Refused(a) => abend(a),
403 };
404 let flag = |f: &Flag| match f {
405 Flag::Set { on, off } => set_to(on).and_then(|()| set_to(off)),
406 Flag::Literals { on, off } => constant(on.0).and_then(|()| constant(off.0)),
407 };
408 let ccsid = |c: &Ccsid| if let Ccsid::Operand(o) = c { operand(o) } else { Ok(()) };
409 let members = |k: usize, members: &[u32], nodes: usize| match members.iter().find(|&&m| m as usize <= k || m as usize >= nodes) {
410 Some(m) => Err(format!("markup node {k} holds node {m} of {nodes}")),
411 None => Ok(()),
412 };
413 for m in &p.services.markup {
414 match m {
415 Markup::JsonGenerate(g) => {
416 place(g.from)?;
417 g.subscripts.iter().try_for_each(int)?;
418 g.name.map_or(Ok(()), symbol)?;
419 place(g.receiver)?;
420 ccsid(&g.encoding)?;
421 g.count.map_or(Ok(()), |(q, _)| place(q))?;
422 place(g.code.0)?;
423 for (k, n) in g.nodes.iter().enumerate() {
424 symbol(n.name)?;
425 n.occurs.as_ref().map_or(Ok(()), count)?;
426 match &n.value {
427 JsonValue::Object { members: held, .. } => members(k, held, g.nodes.len())?,
428 JsonValue::Leaf(leaf) => {
429 if let Some((at, test)) = &leaf.indicator {
430 at.map_or_else(abend, place)?;
431 marker(test)?;
432 }
433 leaf.boolean.as_ref().map_or(Ok(()), marker)?;
434 convert(&leaf.convert)?;
435 }
436 }
437 }
438 }
439 Markup::XmlGenerate(g) => {
440 place(g.receiver)?;
441 ccsid(&g.encoding)?;
442 g.namespace.iter().chain(&g.prefix).try_for_each(operand)?;
443 place(g.from)?;
444 g.subscripts.iter().try_for_each(int)?;
445 g.count.map_or(Ok(()), |(q, _)| place(q))?;
446 place(g.code.0)?;
447 for (k, n) in g.nodes.iter().enumerate() {
448 symbol(n.name)?;
449 n.occurs.as_ref().map_or(Ok(()), count)?;
450 match &n.value {
451 XmlValue::Element { members: held } | XmlValue::Members { members: held } => members(k, held, g.nodes.len())?,
452 XmlValue::Leaf { convert: c, .. } => convert(c)?,
453 }
454 }
455 }
456 Markup::XmlParse(x) => {
457 place(x.document)?;
458 x.encoding.as_ref().map_or(Ok(()), operand)?;
459 range(x.procedure, RangeKind::Processing)?;
460 place(x.event)?;
461 place(x.code.0)?;
462 place(x.information.0)?;
463 int(&x.code_value)?;
464 }
465 Markup::JsonParse(j) => {
466 place(j.source)?;
467 ccsid(&j.encoding)?;
468 place(j.into)?;
469 j.subscripts.iter().try_for_each(int)?;
470 place(j.code.0)?;
471 place(j.status.0)?;
472 for (k, n) in j.nodes.iter().enumerate() {
473 if let Named::Exactly(name) | Named::Folded(name) = n.name {
474 symbol(name)?;
475 }
476 n.occurs.as_ref().map_or(Ok(()), count)?;
477 if let Some(i) = &n.indicator {
478 i.place.map_or(Ok(()), |at| at.map_or_else(abend, place))?;
479 flag(&i.flag)?;
480 }
481 match &n.value {
482 ParseValue::Object { members: held } => members(k, held, j.nodes.len())?,
483 ParseValue::Leaf(leaf) => leaf.boolean.as_ref().map_or(Ok(()), flag)?,
484 ParseValue::Suppressed => {}
485 }
486 }
487 }
488 }
489 }
490 for e in &p.services.entries {
491 symbol(e.name)?;
492 within("paragraph", e.paragraph, p.paragraphs.len())?;
493 block(e.block)?;
494 e.using.iter().try_for_each(|&r| within("LINKAGE record", u32::from(r), p.storage.linkage.len()))?;
495 }
496 for d in &p.plans.display {
497 for item in &d.items {
498 match item {
499 DisplayItem::Bytes(q) | DisplayItem::National(q) | DisplayItem::Digits { place: q, .. } => place(*q)?,
500 DisplayItem::Refused { place: q, abend: a } => {
501 place(*q)?;
502 abend(*a)?;
503 }
504 DisplayItem::Text(t) => within("symbol", *t, p.symbols.len())?,
505 DisplayItem::Value(o) => operand(o)?,
506 }
507 }
508 }
509
510 if p.debug.ops.len() != p.blocks.len() {
511 return Err(format!("debug entries for {} blocks of {}", p.debug.ops.len(), p.blocks.len()));
512 }
513 for (b, (blk, ids)) in p.blocks.iter().zip(&p.debug.ops).enumerate() {
514 if ids.len() != blk.ops.len() + 1 {
515 return Err(format!("block {b}: {} debug entries for {} ops and a terminator", ids.len(), blk.ops.len()));
516 }
517 ids.iter().try_for_each(|&id| within("debug entry", id, p.debug.positions.len()))?;
518 let arms = |op: &Op| match op {
520 Op::Arith(a) if p.plans.arith.get(*a as usize).is_some_and(|plan| plan.handled) => 2,
521 Op::Call(c) if p.services.calls.get(*c as usize).is_some_and(|plan| plan.on_exception || plan.not_on_exception) => 2,
522 Op::Invoke(i) if p.services.invokes.get(*i as usize).is_some_and(|plan| plan.on_exception || plan.not_on_exception) => 2,
523 Op::File(f) => p.services.file_ops.get(*f as usize).map_or(0, |op| op.arms()),
524 Op::String(_) | Op::Unstring(_) | Op::SearchAll(_) => 2,
525 Op::Markup(m) if p.services.markup.get(*m as usize).is_some_and(|x| x.phrases() != (false, false)) => 2,
526 _ => 0,
527 };
528 let armed = |op: &Op| arms(op) > 0;
529 let last = blk.ops.len().saturating_sub(1);
530 if blk.ops.iter().enumerate().any(|(k, op)| armed(op) && (k != last || !matches!(blk.end, Terminator::Select(_)))) {
531 return Err(format!("block {b}: an op that returns an arm is not followed by its Select"));
532 }
533 for op in &blk.ops {
534 match op {
535 Op::Move { from, to, .. } => {
536 operand(from)?;
537 place(*to)?;
538 }
539 Op::Initialize { target, plan } => {
540 place(*target)?;
541 within("INITIALIZE plan", *plan, p.plans.init.len())?;
542 }
543 Op::Arith(a) => within("arithmetic plan", *a, p.plans.arith.len())?,
544 Op::Step { var, by, prepass, .. } => {
545 place(*var)?;
546 expr(*by)?;
547 places(prepass)?;
548 }
549 Op::SetTemp(_, n) => int(n)?,
550 Op::Display(d) => within("DISPLAY plan", *d, p.plans.display.len())?,
551 Op::Call(c) => within("CALL plan", *c, p.services.calls.len())?,
552 Op::Cancel(o) => operand(o)?,
553 Op::Invoke(i) => within("INVOKE plan", *i, p.services.invokes.len())?,
554 Op::Alter { para, to } => {
555 within("paragraph", *para, p.paragraphs.len())?;
556 within("paragraph", *to, p.paragraphs.len())?;
557 }
558 Op::File(f) => within("file statement", *f, p.services.file_ops.len())?,
559 Op::Markup(m) => within("JSON or XML statement", *m, p.services.markup.len())?,
560 Op::Cics(c) => within("EXEC CICS command", *c, p.services.cics.len())?,
561 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())),
562 Op::Sql(_) => {}
563 Op::SetAddress { records, address } => {
564 records.iter().try_for_each(|&r| within("LINKAGE record", u32::from(r), p.storage.linkage.len()))?;
565 operand(address)?;
566 }
567 Op::SetUpDown { by, targets, .. } => {
568 int(by)?;
569 for (q, how) in targets {
570 place(*q)?;
571 if let UpDown::Refused(a) = how {
572 abend(*a)?;
573 }
574 }
575 }
576 Op::String(id) => within("STRING plan", *id, p.plans.string.len())?,
577 Op::Unstring(id) => within("UNSTRING plan", *id, p.plans.unstring.len())?,
578 Op::Inspect(id) => within("INSPECT plan", *id, p.plans.inspect.len())?,
579 Op::SearchAll(id) => within("SEARCH ALL plan", *id, p.plans.search_all.len())?,
580 Op::SetInt { target, value } => {
581 place(*target)?;
582 int(value)?;
583 }
584 Op::Accept { target, plan, .. } => {
585 place(*target)?;
586 moved(plan)?;
587 }
588 Op::DebugAlter { range: r, name, contents } => {
589 range(*r, RangeKind::Debugging)?;
590 symbol(*name)?;
591 symbol(*contents)?;
592 }
593 Op::Nest | Op::Unnest(_) | Op::DecTemp(_) | Op::EnterSegment(_) | Op::DebugLine(_) => {}
594 other => return Err(format!("block {b}: {other:?} is outside this slice")),
595 }
596 }
597 match &blk.end {
598 Terminator::Jump(t) => block(*t)?,
599 Terminator::Branch { cond: c, then, otherwise } => {
600 cond(*c)?;
601 block(*then)?;
602 block(*otherwise)?;
603 }
604 Terminator::Select(targets) => {
605 targets.iter().try_for_each(|&t| block(t))?;
606 if blk.ops.last().map_or(0, arms) != targets.len() {
607 return Err(format!("block {b}: a Select that does not follow an op with its phrases"));
608 }
609 }
610 Terminator::AlteredGoTo { para, otherwise } => {
611 within("paragraph", *para, p.paragraphs.len())?;
612 block(*otherwise)?;
613 }
614 Terminator::ParagraphEnd { next } => {
615 if *next as usize > p.paragraphs.len() {
616 return Err(format!("block {b}: paragraph end to {next} of {}", p.paragraphs.len()));
617 }
618 }
619 Terminator::GoTo(t) => within("paragraph", *t, p.paragraphs.len())?,
620 Terminator::Switch { value, targets, otherwise } => {
621 int(value)?;
622 targets.iter().try_for_each(|&t| within("paragraph", t, p.paragraphs.len()))?;
623 block(*otherwise)?;
624 }
625 Terminator::PerformEnter { range: r, ret, resume } => {
626 range(*r, RangeKind::Perform)?;
627 block(*ret)?;
628 if let Some(resume) = resume {
629 within("paragraph", resume.para, p.paragraphs.len())?;
630 block(resume.block)?;
631 }
632 }
633 Terminator::Debug { range: r, name, next } => {
634 range(*r, RangeKind::Debugging)?;
635 symbol(*name)?;
636 block(*next)?;
637 }
638 Terminator::ExitProgram { next } => block(*next)?,
639 Terminator::End(_) => {}
640 Terminator::Abend(a) => abend(*a)?,
641 }
642 }
643 for (k, para) in p.paragraphs.iter().enumerate() {
644 block(para.entry)?;
645 if (para.section_end as usize) < k || para.section_end as usize >= p.paragraphs.len() {
646 return Err(format!("paragraph {k}: section end {}", para.section_end));
647 }
648 para.abandoned.map_or(Ok(()), abend)?;
649 if para.abandoned.is_some() != p.ranges.iter().any(|r| r.last as usize == k) {
650 return Err(format!("paragraph {k}: an abandoned return point's abend where no range ends, or none where one does"));
651 }
652 }
653 for r in &p.ranges {
654 within("paragraph", r.first, p.paragraphs.len())?;
655 within("paragraph", r.last, p.paragraphs.len())?;
656 }
657 if p.procedure_start as usize > p.paragraphs.len() {
658 return Err(format!("procedure start {} of {} paragraphs", p.procedure_start, p.paragraphs.len()));
659 }
660 Ok(())
661}