1use rt::lir::{CallArg, CallTarget, Chars, Comparand, Cond, DisplayItem, Expr, IntExpr, MethodName, Op, Operand, Place, Program, Receiver, Terminator};
6
7pub fn verify(p: &Program) -> Result<(), String> {
9 verify_program(p)?;
10 let Some(class) = &p.services.class else { return Ok(()) };
11 for part in class.factory.iter().chain(&class.object) {
12 verify(&part.data).map_err(|e| format!("class data: {e}"))?;
13 }
14 for m in &class.methods {
15 verify(&m.code).map_err(|e| format!("method {}: {e}", p.symbols.get(m.name as usize).map_or("", String::as_str)))?;
16 }
17 let symbol = |id: u32| if (id as usize) < p.symbols.len() { Ok(()) } else { Err(format!("symbol {id} of {}", p.symbols.len())) };
18 symbol(class.external)?;
19 symbol(class.parent)?;
20 for m in &class.methods {
21 symbol(m.name)?;
22 m.params.iter().chain(&m.returns).try_for_each(|&s| symbol(s))?;
23 }
24 Ok(())
25}
26
27fn verify_program(p: &Program) -> Result<(), String> {
28 let within = |what: &str, id: u32, len: usize| if (id as usize) < len { Ok(()) } else { Err(format!("{what} {id} of {len}")) };
29 let block = |id| within("block", id, p.blocks.len());
30 let place = |id| within("place", id, p.places.len());
31 let expr = |id| within("expression", id, p.exprs.len());
32 let cond = |id| within("condition", id, p.conds.len());
33 let abend = |id| within("abend", id, p.abends.len());
34 let places = |qs: &[u32]| qs.iter().try_for_each(|&q| place(q));
35 let int = |e: &IntExpr| match e {
36 IntExpr::Const(_) => Ok(()),
37 IntExpr::Item(q) => place(*q),
38 IntExpr::Fixed { expr: e, prepass, .. } => expr(*e).and_then(|()| places(prepass)),
39 };
40 let operand = |o: &Operand| match *o {
41 Operand::Load(q) | Operand::LengthOf(q) | Operand::AddressOf(q) => place(q),
42 Operand::Const(c) => within("constant", c, p.consts.len()),
43 Operand::Function(f) => within("function plan", f, p.plans.function.len()),
44 };
45 let comparand = |c: &Comparand| match c {
46 Comparand::Operand(o) => operand(o),
47 Comparand::Expr { expr: e, prepass, .. } => expr(*e).and_then(|()| places(prepass)),
48 };
49 let ssrange = p.options.ssrange;
50
51 for (k, q) in p.places.iter().enumerate() {
52 let Place { subscripts, odo, refmod, at, .. } = q;
53 within("debug entry", *at, p.debug.positions.len())?;
54 for s in subscripts {
55 int(&s.value)?;
56 if s.check.is_some() != ssrange {
57 return Err(format!("place {k}: a subscript check without SSRANGE, or none with it"));
58 }
59 }
60 if let Some(o) = odo {
61 int(&o.object)?;
62 if o.check != ssrange {
63 return Err(format!("place {k}: an OCCURS DEPENDING ON check that disagrees with SSRANGE"));
64 }
65 }
66 if let Some(r) = refmod {
67 int(&r.start)?;
68 r.length.as_ref().map_or(Ok(()), int)?;
69 if r.check != ssrange {
70 return Err(format!("place {k}: a reference-modification check that disagrees with SSRANGE"));
71 }
72 }
73 }
74 for e in &p.exprs {
75 match e {
76 Expr::Operand(o) => operand(o)?,
77 Expr::Neg(a) => expr(*a)?,
78 Expr::Bin(a, _, b) => {
79 expr(*a)?;
80 expr(*b)?;
81 }
82 Expr::Pow(a, n) => {
83 expr(*a)?;
84 int(n)?;
85 }
86 }
87 }
88 for c in &p.conds {
89 match c {
90 Cond::Rel { a, b, .. } => {
91 comparand(a)?;
92 comparand(b)?;
93 }
94 Cond::Class { place: q, .. } => place(*q)?,
95 Cond::Sign { value, .. } => comparand(value)?,
96 Cond::Name { subject, values, .. } => {
97 place(*subject)?;
98 for &(low, high) in values {
99 within("constant", low, p.consts.len())?;
100 high.map_or(Ok(()), |h| within("constant", h, p.consts.len()))?;
101 }
102 }
103 Cond::Not(a) => cond(*a)?,
104 Cond::And(a, b) | Cond::Or(a, b) => {
105 cond(*a)?;
106 cond(*b)?;
107 }
108 Cond::Counter(_) | Cond::Sql(_) => {}
109 Cond::InTable { index, .. } => place(*index)?,
110 }
111 }
112 for a in &p.abends {
113 a.at.map_or(Ok(()), |at| within("debug entry", at, p.debug.positions.len()))?;
114 }
115 for a in &p.plans.arith {
116 places(&a.prepass)?;
117 for s in &a.steps {
118 place(s.target)?;
119 expr(s.expr)?;
120 places(&s.probe)?;
121 }
122 if let Some(r) = &a.remainder {
123 place(r.target)?;
124 expr(r.dividend)?;
125 expr(r.divisor)?;
126 }
127 }
128 let symbol = |id: u32| within("symbol", id, p.symbols.len());
129 let chars = |c: &Chars| match c {
130 Chars::Literal(_) => Ok(()),
131 Chars::Place(q) => place(*q),
132 Chars::Value(o) => operand(o),
133 };
134 for c in &p.services.calls {
135 match &c.target {
136 CallTarget::Named { name, .. } => symbol(*name)?,
137 CallTarget::Dynamic(o) => operand(o)?,
138 CallTarget::Pointer(q) => place(*q)?,
139 }
140 for a in &c.args {
141 match a {
142 CallArg::Reference(q) => place(*q)?,
143 CallArg::Content(ch) => chars(ch)?,
144 CallArg::Value(o) => operand(o)?,
145 CallArg::Omitted => {}
146 }
147 if matches!(c.target, CallTarget::Pointer(_)) && !matches!(a, CallArg::Value(_) | CallArg::Omitted) {
148 return Err("a CALL through a pointer with an argument that is not a value".into());
149 }
150 }
151 c.returning.map_or(Ok(()), place)?;
152 }
153 for i in &p.services.invokes {
154 match i.receiver {
155 Receiver::SelfRef | Receiver::Super => {}
156 Receiver::Class { name, external } => {
157 symbol(name)?;
158 symbol(external)?;
159 }
160 Receiver::Object(q) => place(q)?,
161 }
162 match i.method {
163 MethodName::New => {}
164 MethodName::Named(s) => symbol(s)?,
165 MethodName::Dynamic(q) => place(q)?,
166 }
167 for (o, java) in &i.args {
168 operand(o)?;
169 symbol(*java)?;
170 }
171 if let Some((q, java)) = i.returning {
172 place(q)?;
173 symbol(java)?;
174 }
175 }
176 for e in &p.services.entries {
177 symbol(e.name)?;
178 within("paragraph", e.paragraph, p.paragraphs.len())?;
179 block(e.block)?;
180 e.using.iter().try_for_each(|&r| within("LINKAGE record", u32::from(r), p.storage.linkage.len()))?;
181 }
182 for d in &p.plans.display {
183 for item in &d.items {
184 match item {
185 DisplayItem::Bytes(q) | DisplayItem::National(q) | DisplayItem::Digits { place: q, .. } => place(*q)?,
186 DisplayItem::Refused { place: q, abend: a } => {
187 place(*q)?;
188 abend(*a)?;
189 }
190 DisplayItem::Text(t) => within("symbol", *t, p.symbols.len())?,
191 DisplayItem::Value(o) => operand(o)?,
192 }
193 }
194 }
195
196 if p.debug.ops.len() != p.blocks.len() {
197 return Err(format!("debug entries for {} blocks of {}", p.debug.ops.len(), p.blocks.len()));
198 }
199 for (b, (blk, ids)) in p.blocks.iter().zip(&p.debug.ops).enumerate() {
200 if ids.len() != blk.ops.len() + 1 {
201 return Err(format!("block {b}: {} debug entries for {} ops and a terminator", ids.len(), blk.ops.len()));
202 }
203 ids.iter().try_for_each(|&id| within("debug entry", id, p.debug.positions.len()))?;
204 let armed = |op: &Op| match op {
205 Op::Arith(a) => p.plans.arith.get(*a as usize).is_some_and(|plan| plan.handled),
206 Op::Call(c) => p.services.calls.get(*c as usize).is_some_and(|plan| plan.on_exception || plan.not_on_exception),
207 Op::Invoke(i) => p.services.invokes.get(*i as usize).is_some_and(|plan| plan.on_exception || plan.not_on_exception),
208 _ => false,
209 };
210 let last = blk.ops.len().saturating_sub(1);
211 if blk.ops.iter().enumerate().any(|(k, op)| armed(op) && (k != last || !matches!(blk.end, Terminator::Select(_)))) {
212 return Err(format!("block {b}: an op that returns an arm is not followed by its Select"));
213 }
214 for op in &blk.ops {
215 match op {
216 Op::Move { from, to, .. } => {
217 operand(from)?;
218 place(*to)?;
219 }
220 Op::Initialize { target, plan } => {
221 place(*target)?;
222 within("INITIALIZE plan", *plan, p.plans.init.len())?;
223 }
224 Op::Arith(a) => within("arithmetic plan", *a, p.plans.arith.len())?,
225 Op::Step { var, by, prepass, .. } => {
226 place(*var)?;
227 expr(*by)?;
228 places(prepass)?;
229 }
230 Op::SetTemp(_, n) => int(n)?,
231 Op::Display(d) => within("DISPLAY plan", *d, p.plans.display.len())?,
232 Op::Call(c) => within("CALL plan", *c, p.services.calls.len())?,
233 Op::Cancel(o) => operand(o)?,
234 Op::Invoke(i) => within("INVOKE plan", *i, p.services.invokes.len())?,
235 Op::Alter { para, to } => {
236 within("paragraph", *para, p.paragraphs.len())?;
237 within("paragraph", *to, p.paragraphs.len())?;
238 }
239 Op::Nest | Op::Unnest(_) | Op::DecTemp(_) | Op::EnterSegment(_) | Op::SetSegment(_) => {}
240 other => return Err(format!("block {b}: {other:?} is outside this slice")),
241 }
242 }
243 match &blk.end {
244 Terminator::Jump(t) => block(*t)?,
245 Terminator::Branch { cond: c, then, otherwise } => {
246 cond(*c)?;
247 block(*then)?;
248 block(*otherwise)?;
249 }
250 Terminator::Select(arms) => {
251 arms.iter().try_for_each(|&t| block(t))?;
252 if !blk.ops.last().is_some_and(armed) || arms.len() != 2 {
253 return Err(format!("block {b}: a Select that does not follow an op with its phrases"));
254 }
255 }
256 Terminator::AlteredGoTo { para, otherwise } => {
257 within("paragraph", *para, p.paragraphs.len())?;
258 block(*otherwise)?;
259 }
260 Terminator::ParagraphEnd { next } => {
261 if *next as usize > p.paragraphs.len() {
262 return Err(format!("block {b}: paragraph end to {next} of {}", p.paragraphs.len()));
263 }
264 }
265 Terminator::GoTo(t) => within("paragraph", *t, p.paragraphs.len())?,
266 Terminator::Switch { value, targets, otherwise } => {
267 int(value)?;
268 targets.iter().try_for_each(|&t| within("paragraph", t, p.paragraphs.len()))?;
269 block(*otherwise)?;
270 }
271 Terminator::PerformEnter { range, ret } => {
272 within("range", *range, p.ranges.len())?;
273 block(*ret)?;
274 }
275 Terminator::ExitProgram { next } => block(*next)?,
276 Terminator::End(_) => {}
277 Terminator::Abend(a) => abend(*a)?,
278 }
279 }
280 for (k, para) in p.paragraphs.iter().enumerate() {
281 block(para.entry)?;
282 if (para.section_end as usize) < k || para.section_end as usize >= p.paragraphs.len() {
283 return Err(format!("paragraph {k}: section end {}", para.section_end));
284 }
285 }
286 for r in &p.ranges {
287 within("paragraph", r.first, p.paragraphs.len())?;
288 within("paragraph", r.last, p.paragraphs.len())?;
289 }
290 if p.procedure_start as usize > p.paragraphs.len() {
291 return Err(format!("procedure start {} of {} paragraphs", p.procedure_start, p.paragraphs.len()));
292 }
293 Ok(())
294}