Skip to main content

ironwork_exec/lower/
verify.rs

1//! The checks of lir.md ยง12.1 a lowered program must pass: every id names an entry of its table,
2//! the control-flow graph is closed, every op has its debug entry, and places carry SSRANGE checks
3//! exactly when the program has SSRANGE.
4
5use rt::lir::{CallArg, CallTarget, Chars, Comparand, Cond, DisplayItem, Expr, IntExpr, MethodName, Op, Operand, Place, Program, Receiver, Terminator};
6
7/// A class definition's data and methods are programs of their own, each checked as one.
8pub 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}