Skip to main content

ironwork_rt/oo/
run.rs

1//! INVOKE, SELF and SUPER, and the JNI environment, at run time. A COBOL class runs here: its
2//! factory data, each object's instance data and each method's WORKING-STORAGE are storage of the
3//! run unit that is never released. An object reference holds a local or global reference, as the
4//! JNI hands them out; a method's local references are freed when it returns. A Java class, or a
5//! JNI service that needs a JVM, ends the run with abend JAVA naming what was reached.
6
7use super::{ClassCode, Instance, JAVA_LANG_OBJECT, LoadedClass, MAX_MEMORY, Objects, Part, Referent, Running};
8use crate::abend::{Abend, AbendCode, Ending};
9use crate::callee::{self, By, Callee};
10use crate::display::utf16_text;
11use crate::host::Values;
12use crate::jni;
13use crate::lir::{CallArg, InvokePlan, MethodName, Receiver, Step};
14use crate::storage::{Kind, Loc, Val};
15use crate::store::{self, ProgramFacts};
16use crate::unit::{ADDRESS_BASE, Event, Loaded, Loader, RETURN_CODE, RunUnit, UnitHost};
17use crate::vocab::{Figurative, Pos};
18use numeric::assumptions::{EXPIRED_REFERENCE_ABENDS, LOCAL_FRAMES};
19use numeric::precision::{Fixed, Places};
20use std::cmp::Ordering;
21use std::rc::Rc;
22
23type R<T> = Result<T, Abend>;
24
25/// What INVOKE and the JNI services ask of the executor beyond `Values` and the run unit: the method
26/// the activation runs, names and Java signatures, an argument's bytes, and running a method.
27pub trait OoHost<'w, P: Copy, O, S>: Values<P, O> + UnitHost<'w> {
28    fn running(&self) -> Option<Running>;
29    fn program_id(&self) -> String;
30    /// A name, or a Java type signature, which an executor may work out only when it is read.
31    fn symbol(&mut self, symbol: &S, pos: Pos) -> R<String>;
32    /// A data item's name as written, which messages give.
33    fn place_name(&self, place: P) -> String;
34    /// An operand's name when it is a data item.
35    fn operand_name(&self, operand: &O) -> Option<String>;
36    /// An INVOKE argument as the method receives it, in the bytes of its Java type.
37    fn invoke_argument(&mut self, operand: &O, java: &str, pos: Pos) -> R<Vec<u8>>;
38    /// Gives loaded storage `index` the VALUE clauses of `data`, a FACTORY or OBJECT part.
39    fn initialize_data(&mut self, data: Self::Program, index: usize) -> R<()>;
40    /// Runs a method as a called program runs. An error is one given before or after it ran.
41    fn run_method(&mut self, call: MethodCall<Self::Program>, pos: Pos) -> R<Returned>;
42}
43
44/// How a method's run ended, and when it ended normally, its RETURNING item's name and value.
45pub struct Returned {
46    pub ending: R<Ending>,
47    pub value: Option<(String, Val)>,
48}
49
50/// A COBOL method's activation: its code and loaded WORKING-STORAGE, the LINKAGE ordinal and
51/// address of each record of the object's or the factory's data, its arguments' addresses, and the
52/// frame it runs in.
53pub struct MethodCall<H> {
54    pub code: H,
55    pub storage: usize,
56    pub records: Vec<(usize, usize)>,
57    pub arguments: Vec<Option<usize>>,
58    pub running: Running,
59}
60
61/// What an INVOKE is sent to: a class, or an object by its position plus one.
62enum Target {
63    Class(usize),
64    Object(u32),
65    Super { this: u32, start: usize, factory: bool },
66}
67
68/// Where the method an INVOKE names was found.
69enum Found {
70    Cobol { class: usize, method: usize },
71    /// java.lang.Object's equals, which is identity.
72    ObjectEquals,
73    Java(String),
74    Missing,
75}
76
77/// An INVOKE argument: the bytes of a Java primitive, or the object an object reference identifies,
78/// which the method receives as a new local reference.
79enum Argument {
80    Bytes(Vec<u8>),
81    Object(Option<u32>),
82}
83
84/// A JNI function-table slot's value: this plus the slot, above any storage address.
85const JNI_TAG: u32 = 0x7F00_0000;
86
87/// java.lang.Object's public methods with their JNI signatures, which the JVM would run.
88const OBJECT_METHODS: &[(&str, &[&str], Option<&str>)] = &[
89    ("hashCode", &[], Some("I")),
90    ("toString", &[], Some("Ljava/lang/String;")),
91    ("getClass", &[], Some("Ljava/lang/Class;")),
92    ("notify", &[], None),
93    ("notifyAll", &[], None),
94    ("wait", &[], None),
95    ("wait", &["J"], None),
96    ("wait", &["J", "I"], None),
97    ("clone", &[], Some("Ljava/lang/Object;")),
98    ("finalize", &[], None),
99];
100
101fn java(what: String, class: &str, pos: Pos) -> Abend {
102    crate::refusal::IWR0062.ending(AbendCode::Java, format_args!("{what} was reached: {class} is a Java class, and ironwork for COBOL checks Java classes but has no JVM to run them"), pos)
103}
104
105/// An object reference's Java type, as a JNI signature spells it.
106fn is_reference(java: &str) -> bool {
107    java.starts_with('L') || java.starts_with('[')
108}
109
110fn int(n: i128) -> Val {
111    Val::Num(Fixed::new(n, Places::new(9, 0)))
112}
113
114/// SELF in method `running`, given its local reference the first time the method reads it.
115pub fn self_reference<H: Clone, L: Loader<H>>(unit: &mut RunUnit<'_, H, L>, running: Option<Running>, pos: Pos) -> R<Loc> {
116    let Some(m) = running else { return Err(Abend::ironwork("SELF outside a method", pos)) };
117    if unit.mem[m.cell..m.cell + 4] == [0; 4] {
118        let made = unit.oo.event(format!("SELF of {}", unit.oo.told(m.invoked)));
119        let reference = unit.oo.local_in(m.frame, m.this, made).map_err(|e| Abend::ironwork(e, pos))?;
120        unit.mem[m.cell..m.cell + 4].copy_from_slice(&reference.to_be_bytes());
121    }
122    Ok(Loc { offset: m.cell, len: 4, kind: Kind::ObjectReference, item: usize::MAX })
123}
124
125/// `line 12 of CLIENT`, or `line 30 of Account.credit` in a method.
126fn site<'w, P: Copy, O, S, X: OoHost<'w, P, O, S>>(x: &mut X, pos: Pos) -> String {
127    let id = x.program_id();
128    match x.running() {
129        Some(m) => format!("line {} of {}.{}", pos.line, x.unit().oo.classes[m.class].external, id),
130        None => format!("line {} of {}", pos.line, id),
131    }
132}
133
134/// The object an object reference's value identifies, None for NULL. A reference that was
135/// freed, or four bytes no reference was given, end the run (see [`EXPIRED_REFERENCE_ABENDS`]).
136fn referent<C>(objects: &Objects<C>, value: u32, what: &str, holder: &str, pos: Pos) -> R<Option<u32>> {
137    match objects.referent(value) {
138        Referent::Null => Ok(None),
139        Referent::Object(o) => Ok(Some(o)),
140        Referent::Expired(told) => Err(Abend::ironwork(format!("{what}: {holder} holds {told}, and IBM leaves using it unpredictable (see {EXPIRED_REFERENCE_ABENDS})"), pos)),
141        Referent::Unknown => Err(Abend::ironwork(format!("{what}: {holder} holds X'{value:08X}', which is not an object reference"), pos)),
142    }
143}
144
145/// A new local reference in the innermost frame.
146fn local_reference<C>(objects: &mut Objects<C>, object: u32, made: String, pos: Pos) -> R<u32> {
147    let made = objects.event(made);
148    objects.local(object, made).map_err(|m| Abend::ironwork(m, pos))
149}
150
151/// The object SELF refers to, as the method's SELF reference says once it has been read.
152fn self_object<H, L: Loader<H>>(unit: &RunUnit<'_, H, L>, m: Running, what: &str, pos: Pos) -> R<u32> {
153    let value = u32::from_be_bytes(unit.mem[m.cell..m.cell + 4].try_into().unwrap_or_default());
154    Ok(referent(&unit.oo, value, what, "SELF", pos)?.unwrap_or(m.this))
155}
156
157/// Two object references compare equal when they identify the same object (Language Reference
158/// SC27-8713-03, p. 282), so each is looked up; one compared with the figurative constant NULL
159/// is only tested for NULL. None when neither is an object reference; `names` names the two.
160pub fn compare_references<C>(objects: &Objects<C>, names: impl FnOnce() -> (String, String), x: (&Val, Option<Loc>), y: (&Val, Option<Loc>), pos: Pos) -> R<Option<Ordering>> {
161    let reference = |l: Option<Loc>| l.is_some_and(|l| l.kind == Kind::ObjectReference);
162    let (Val::Address(p), Val::Address(q)) = (x.0, y.0) else { return Ok(None) };
163    if !reference(x.1) && !reference(y.1) {
164        return Ok(None);
165    }
166    let (a, b) = names();
167    let what = format!("{a} = {b}");
168    let same = referent(objects, *p, &what, &a, pos)? == referent(objects, *q, &what, &b, pos)?;
169    Ok(Some(if same { Ordering::Equal } else { Ordering::Less }))
170}
171
172fn room<H, L: Loader<H>>(unit: &RunUnit<'_, H, L>, size: usize, pos: Pos) -> R<()> {
173    if unit.mem.len() + size > MAX_MEMORY {
174        return Err(Abend::ironwork(format!("objects and classes take the run unit past ironwork's {MAX_MEMORY} bytes"), pos));
175    }
176    Ok(())
177}
178
179/// Storage the run unit keeps: a loaded entry no CALL can name holds it, so that releasing
180/// arguments never reaches it.
181fn keep<H: Clone, L: Loader<H>>(unit: &mut RunUnit<'_, H, L>, compiled: Option<H>, bytes: &[u8], pos: Pos) -> R<usize> {
182    room(unit, bytes.len(), pos)?;
183    let base = unit.push_temporary(bytes);
184    let files = compiled.as_ref().map_or(0, |c| L::shape(c).0);
185    unit.programs.push(Loaded::new(compiled, String::new(), base, bytes.len(), files));
186    Ok(unit.programs.len() - 1)
187}
188
189/// The JNIENVPTR cell, pointing at the JNI environment, which points at the function table.
190pub fn jni_environment<H: Clone, L: Loader<H>>(unit: &mut RunUnit<'_, H, L>, pos: Pos) -> R<usize> {
191    if let Some(cell) = unit.oo.jni {
192        return Ok(cell);
193    }
194    let slots = jni::RESERVED + jni::FUNCTIONS.len();
195    let loaded = keep(unit, None, &vec![0; 8 + 4 * slots], pos)?;
196    let base = unit.programs[loaded].base;
197    let address = |offset: usize| ADDRESS_BASE + offset as u32;
198    let mut bytes = [address(base + 4).to_be_bytes(), address(base + 8).to_be_bytes()].concat();
199    for slot in 0..slots {
200        let value = if slot < jni::RESERVED { 0 } else { JNI_TAG + slot as u32 };
201        bytes.extend(value.to_be_bytes());
202    }
203    unit.mem[base..base + bytes.len()].copy_from_slice(&bytes);
204    unit.oo.jni = Some(base);
205    Ok(base)
206}
207
208/// FACTORY or OBJECT data, zeroed and given its VALUE clauses.
209fn part_storage<'w, P: Copy, O, S, X: OoHost<'w, P, O, S>>(x: &mut X, part: &Part<X::Program>, pos: Pos) -> R<usize> {
210    let data = part.data.clone();
211    let size = X::Loader::shape(&data).1;
212    let loaded = keep(x.unit(), Some(data.clone()), &vec![0; size], pos)?;
213    x.initialize_data(data, loaded)?;
214    x.unit().programs[loaded].active = false;
215    Ok(loaded)
216}
217
218/// The run unit's class of this external name, loading its COBOL class definition, and those of
219/// the classes it inherits, the first time.
220fn load_class<'w, P: Copy, O, S, X: OoHost<'w, P, O, S>>(x: &mut X, external: &str, pos: Pos) -> R<usize> {
221    if let Some(c) = x.unit().oo.find(external) {
222        return Ok(c);
223    }
224    let (code, sources) = match x.unit().library.class(external).map_err(|m| Abend::ironwork(m, pos))? {
225        None => (None, Vec::new()),
226        Some(found) => (Some(found.code), found.sources),
227    };
228    let oo = &mut x.unit().oo;
229    let index = oo.classes.len();
230    let methods = code.as_ref().map_or(0, |c| c.methods.len());
231    oo.classes.push(LoadedClass { external: external.to_owned(), code: code.clone(), parent: None, factory_object: 0, factory_data: None, methods: vec![None; methods], sources });
232    let factory_object = oo.add_object(Instance { class: index, factory: true, parts: Vec::new() }).map_err(|m| Abend::ironwork(m, pos))?;
233    oo.classes[index].factory_object = factory_object;
234    let Some(code) = code else { return Ok(index) };
235    if x.unit().observed() {
236        let mut facts = numeric::governs::Facts::default();
237        let data = code.factory.iter().chain(&code.object).map(|p| &p.data);
238        for program in data.chain(code.methods.iter().map(|m| &m.code)) {
239            facts.union(<X::Loader as Loader<X::Program>>::facts(program));
240        }
241        x.unit().notify(Event::Class { class: external, facts });
242    }
243    let parent = load_class(x, &code.parent, pos)?;
244    let oo = &mut x.unit().oo;
245    let mut at = Some(parent);
246    for _ in 0..=oo.classes.len() {
247        match at {
248            Some(c) if c == index => return Err(Abend::ironwork(format!("class {external} inherits from itself"), pos)),
249            Some(c) => at = oo.classes[c].parent,
250            None => break,
251        }
252    }
253    oo.classes[index].parent = Some(parent);
254    if let Some(part) = &code.factory {
255        let loaded = part_storage(x, part, pos)?;
256        x.unit().oo.classes[index].factory_data = Some(loaded);
257    }
258    Ok(index)
259}
260
261/// The class and its ancestors, the class first.
262fn chain<C>(objects: &Objects<C>, class: usize) -> Vec<usize> {
263    let mut out = vec![class];
264    while let Some(p) = objects.classes[*out.last().unwrap()].parent {
265        if out.contains(&p) {
266            break;
267        }
268        out.push(p);
269    }
270    out
271}
272
273fn object<C>(objects: &Objects<C>, id: u32, what: &str, pos: Pos) -> R<(usize, bool)> {
274    objects.object(id).map(|o| (o.class, o.factory)).ok_or_else(|| Abend::ironwork(format!("{what}: no object {id}"), pos))
275}
276
277fn target<'w, P: Copy, O, S, X: OoHost<'w, P, O, S>>(x: &mut X, receiver: &Receiver<P, S>, what: &str, pos: Pos) -> R<Target> {
278    match receiver {
279        Receiver::SelfRef | Receiver::Super => {
280            let name = if matches!(receiver, Receiver::SelfRef) { "SELF" } else { "SUPER" };
281            let Some(m) = x.running() else { return Err(Abend::ironwork(format!("INVOKE {name} outside a method"), pos)) };
282            let unit = x.unit();
283            let this = self_object(unit, m, what, pos)?;
284            if matches!(receiver, Receiver::SelfRef) {
285                return Ok(Target::Object(this));
286            }
287            let start = unit.oo.classes[m.class].parent.ok_or_else(|| Abend::ironwork("INVOKE SUPER: the class has no parent", pos))?;
288            Ok(Target::Super { this, start, factory: m.factory })
289        }
290        Receiver::Class { external, .. } => {
291            let external = x.symbol(external, pos)?;
292            Ok(Target::Class(load_class(x, &external, pos)?))
293        }
294        Receiver::Object(place) => {
295            let loc = x.locate(*place, false)?;
296            let name = x.place_name(*place);
297            let value = match <[u8; 4]>::try_from(store::bytes(x.mem(), loc)) {
298                Ok(bytes) if loc.kind == Kind::ObjectReference => u32::from_be_bytes(bytes),
299                _ => return Err(Abend::ironwork(format!("INVOKE {name}: not an object reference"), pos)),
300            };
301            match referent(&x.unit().oo, value, what, &name, pos)? {
302                Some(object) => Ok(Target::Object(object)),
303                None => Err(Abend::ironwork(format!("{what}: the object reference {name} is NULL"), pos)),
304            }
305        }
306    }
307}
308
309/// The receiver as the INVOKE names it, which messages give.
310fn target_name<'w, P: Copy, O, S, X: OoHost<'w, P, O, S>>(x: &mut X, receiver: &Receiver<P, S>, pos: Pos) -> R<String> {
311    Ok(match receiver {
312        Receiver::SelfRef => "SELF".into(),
313        Receiver::Super => "SUPER".into(),
314        Receiver::Class { name, .. } => x.symbol(name, pos)?,
315        Receiver::Object(place) => x.place_name(*place),
316    })
317}
318
319fn method_name<'w, P: Copy, O, S, X: OoHost<'w, P, O, S>>(x: &mut X, method: &MethodName<P, S>, pos: Pos) -> R<String> {
320    Ok(match method {
321        MethodName::New => "NEW".into(),
322        MethodName::Named(n) => x.symbol(n, pos)?,
323        MethodName::Dynamic(place) => {
324            let loc = x.locate(*place, false)?;
325            let page = x.facts().page();
326            let bytes = store::bytes(x.mem(), loc);
327            let text = if loc.kind == Kind::National { utf16_text(bytes) } else { page.decode(bytes) };
328            let name = text.trim_end_matches(' ').to_owned();
329            if name.is_empty() {
330                return Err(Abend::ironwork(format!("INVOKE: {} holds no method name", x.place_name(*place)), pos));
331            }
332            name
333        }
334    })
335}
336
337fn find<H>(objects: &Objects<Rc<ClassCode<H>>>, start: usize, factory: bool, name: &str, params: &[String], returns: Option<&str>) -> Found {
338    for class in chain(objects, start) {
339        let c = &objects.classes[class];
340        match &c.code {
341            Some(code) => {
342                if let Some(method) = code.methods.iter().position(|m| m.factory == factory && m.name == name && m.params == params && m.returns.as_deref() == returns) {
343                    return Found::Cobol { class, method };
344                }
345            }
346            None if c.external == JAVA_LANG_OBJECT => {
347                if factory {
348                    return Found::Missing;
349                }
350                if name == "equals" && params == ["Ljava/lang/Object;"] && returns == Some("Z") {
351                    return Found::ObjectEquals;
352                }
353                let java = OBJECT_METHODS.iter().any(|(n, p, r)| *n == name && *p == params && *r == returns);
354                return if java { Found::Java(c.external.clone()) } else { Found::Missing };
355            }
356            None => return Found::Java(c.external.clone()),
357        }
358    }
359    Found::Missing
360}
361
362fn no_method<P, O, S>(plan: &InvokePlan<P, O, S>, what: String, pos: Pos) -> R<Step> {
363    if plan.on_exception {
364        return Ok(Step::Arm(1));
365    }
366    Err(Abend {
367        code: AbendCode::user(4038),
368        message: format!("IGZ0045S Unable to invoke method on line number {}. ({what}: no method matches it, and the INVOKE has no ON EXCEPTION)", pos.line),
369        pos,
370        file: None,
371    })
372}
373
374fn succeeded<P, O, S>(plan: &InvokePlan<P, O, S>) -> Step {
375    if plan.on_exception || plan.not_on_exception { Step::Arm(0) } else { Step::Next }
376}
377
378/// INVOKE: Arm(1) when no method matches and ON EXCEPTION is written, Arm(0) after a method ran
379/// when a phrase is written, Next when none is, or End after a STOP RUN. A CICS task refuses it
380/// (C147).
381pub fn invoke<'w, P: Copy, O, S, X: OoHost<'w, P, O, S>>(x: &mut X, plan: &InvokePlan<P, O, S>, pos: Pos) -> R<Step> {
382    if x.unit().cics.is_some() {
383        return Err(Abend::ironwork("INVOKE was reached in a CICS task, where a COBOL program with object-oriented syntax cannot run", pos));
384    }
385    x.unit().unfollowed("object-oriented COBOL and calls through pointers");
386    let name = method_name(x, &plan.method, pos)?;
387    let written = target_name(x, &plan.receiver, pos)?;
388    let what = format!("INVOKE {written} \"{name}\"");
389    let receiver = target(x, &plan.receiver, &what, pos)?;
390    let mut params = Vec::new();
391    let mut arguments = Vec::new();
392    for (n, (op, java)) in plan.args.iter().enumerate() {
393        let java = x.symbol(java, pos)?;
394        let bytes = x.invoke_argument(op, &java, pos)?;
395        arguments.push(if is_reference(&java) {
396            let holder = x.operand_name(op).unwrap_or_else(|| format!("argument {}", n + 1));
397            Argument::Object(referent(&x.unit().oo, u32::from_be_bytes(bytes.as_slice().try_into().unwrap_or_default()), &what, &holder, pos)?)
398        } else {
399            Argument::Bytes(bytes)
400        });
401        params.push(java);
402    }
403    if matches!(plan.method, MethodName::New) {
404        let Target::Class(class) = receiver else { return Err(Abend::ironwork("INVOKE ... NEW takes a class-name", pos)) };
405        return new_object(x, plan, &written, class, &params, pos);
406    }
407    let returns = match &plan.returning {
408        None => None,
409        Some((_, java)) => Some(x.symbol(java, pos)?),
410    };
411    let (this, start, factory) = match receiver {
412        Target::Class(c) => (x.unit().oo.classes[c].factory_object, c, true),
413        Target::Object(o) => {
414            let (class, factory) = object(&x.unit().oo, o, &what, pos)?;
415            (o, class, factory)
416        }
417        Target::Super { this, start, factory } => (this, start, factory),
418    };
419    match find(&x.unit().oo, start, factory, &name, &params, returns.as_deref()) {
420        Found::Missing => no_method(plan, what, pos),
421        Found::Java(class) => Err(java(what, &class, pos)),
422        Found::ObjectEquals => {
423            let same = matches!(arguments.first(), Some(Argument::Object(Some(other))) if *other == this);
424            if let Some((place, _)) = &plan.returning {
425                let dest = x.locate(*place, false)?;
426                x.assign(dest, Val::Bytes(vec![u8::from(same)]), None, pos)?;
427            }
428            Ok(succeeded(plan))
429        }
430        Found::Cobol { class, method } => {
431            x.unit().enter(pos)?;
432            let step = run_method(x, plan, class, method, this, arguments, &what, pos);
433            x.unit().depth -= 1;
434            step
435        }
436    }
437}
438
439/// INVOKE class NEW: a COBOL class gets its instance data from its VALUE clauses and takes no
440/// arguments; a class with a Java ancestor other than java.lang.Object needs the JVM.
441fn new_object<'w, P: Copy, O, S, X: OoHost<'w, P, O, S>>(x: &mut X, plan: &InvokePlan<P, O, S>, written: &str, class: usize, params: &[String], pos: Pos) -> R<Step> {
442    let what = format!("INVOKE {written} NEW");
443    let oo = &x.unit().oo;
444    let chain = chain(oo, class);
445    if let Some(&j) = chain.iter().find(|&&c| oo.classes[c].code.is_none() && oo.classes[c].external != JAVA_LANG_OBJECT) {
446        return Err(java(what, &oo.classes[j].external.clone(), pos));
447    }
448    if !params.is_empty() {
449        return no_method(plan, format!("{what} USING {} arguments", params.len()), pos);
450    }
451    let mut parts = Vec::new();
452    for &c in chain.iter().rev() {
453        if let Some(code) = x.unit().oo.classes[c].code.clone()
454            && let Some(part) = &code.object
455        {
456            parts.push((c, part_storage(x, part, pos)?));
457        }
458    }
459    let object = x.unit().oo.add_object(Instance { class, factory: false, parts }).map_err(|m| Abend::ironwork(m, pos))?;
460    let here = site(x, pos);
461    let made = format!("made by INVOKE {} NEW at {here}", x.unit().oo.classes[class].external);
462    let reference = local_reference(&mut x.unit().oo, object, made, pos)?;
463    if let Some((place, _)) = &plan.returning {
464        let dest = x.locate(*place, false)?;
465        x.assign(dest, Val::Address(reference), None, pos)?;
466    }
467    Ok(succeeded(plan))
468}
469
470/// Runs a COBOL method as a called program runs, with the data of its paragraph as the records
471/// after its own LINKAGE, its arguments BY VALUE and SELF, in a local frame of its own: the
472/// object references it receives are new local references there, and every local reference
473/// made there is freed when it returns. A returned object reference reaches the invoker as a
474/// new local reference of the invoker's frame.
475#[allow(clippy::too_many_arguments)]
476fn run_method<'w, P: Copy, O, S, X: OoHost<'w, P, O, S>>(x: &mut X, plan: &InvokePlan<P, O, S>, class: usize, k: usize, this: u32, arguments: Vec<Argument>, what: &str, pos: Pos) -> R<Step> {
477    let code = x.unit().oo.classes[class].code.clone().ok_or_else(|| Abend::ironwork("not a COBOL class", pos))?;
478    let method = &code.methods[k];
479    let this = if method.factory { x.unit().oo.classes[class].factory_object } else { this };
480    let storage = match x.unit().oo.classes[class].methods[k] {
481        Some(s) => s,
482        None => {
483            let size = X::Loader::shape(&method.code).1;
484            let s = keep(x.unit(), Some(method.code.clone()), &vec![0; size], pos)?;
485            x.unit().oo.classes[class].methods[k] = Some(s);
486            s
487        }
488    };
489    let (part, data) = if method.factory {
490        (code.factory.as_ref(), x.unit().oo.classes[class].factory_data)
491    } else {
492        let data = x.unit().oo.object(this).and_then(|o| o.parts.iter().find(|(c, _)| *c == class)).map(|(_, l)| *l);
493        (code.object.as_ref(), data)
494    };
495    let here = site(x, pos);
496    let unit = x.unit();
497    let base = data.map(|d| unit.programs[d].base);
498    let return_code = [unit.mem[RETURN_CODE], unit.mem[RETURN_CODE + 1]];
499    let mark = unit.mem.len();
500    let invoked_text = format!("method \"{}\" of {}, invoked at {here}", method.name, unit.oo.classes[class].external);
501    let invoked = unit.oo.event(invoked_text.clone());
502    let (depth, frame) = unit.oo.push_frame(false);
503    let mut addresses = Vec::new();
504    for (n, argument) in arguments.into_iter().enumerate() {
505        let bytes = match argument {
506            Argument::Bytes(b) => b,
507            Argument::Object(None) => vec![0; 4],
508            Argument::Object(Some(object)) => {
509                let made = unit.oo.event(format!("received as argument {} by {invoked_text}", n + 1));
510                unit.oo.local_in(frame, object, made).map_err(|m| Abend::ironwork(m, pos))?.to_be_bytes().to_vec()
511            }
512        };
513        addresses.push(Some(unit.push_temporary(&bytes)));
514    }
515    let cell = unit.push_temporary(&[0; 4]);
516    let records = match (part, base) {
517        (Some(part), Some(base)) => part.records.iter().enumerate().map(|(n, offset)| (method.own_records + n, base + *offset as usize)).collect(),
518        _ => Vec::new(),
519    };
520    let running = Running { class, factory: method.factory, this, cell, frame, invoked };
521    let call = MethodCall { code: method.code.clone(), storage, records, arguments: addresses, running };
522    let callee = Callee { index: storage, by: By::Invoke, mark: Some(mark), pos, lengths: &[] };
523    let (ending, returned) = callee::run(x, &callee, |x| x.run_method(call, pos).map(|r| (r.ending, r.value)))?;
524    let unit = x.unit();
525    let ending = ending.map_err(|mut abend| {
526        if abend.file.is_none() {
527            abend.file = Some(unit.oo.classes[class].sources.get(abend.pos.file as usize).cloned().unwrap_or_default());
528        }
529        abend
530    });
531    if ending? == Ending::StopRun {
532        return Ok(Step::End(Ending::StopRun));
533    }
534    let mut returned_object = None;
535    if let Some((item, Val::Address(value))) = &returned
536        && method.returns.as_deref().is_some_and(is_reference)
537    {
538        let holder = format!("{item}, the RETURNING item of method \"{}\",", method.name);
539        returned_object = Some(referent(&unit.oo, *value, what, &holder, pos)?);
540    }
541    let mut returned = returned.map(|(_, value)| value);
542    let expired = unit.oo.event(format!("expired when {invoked_text}, returned"));
543    unit.oo.pop_frames(depth, expired);
544    if let Some(object) = returned_object {
545        let value = match object {
546            None => 0,
547            Some(o) => local_reference(&mut unit.oo, o, format!("returned by {invoked_text}"), pos)?,
548        };
549        returned = Some(Val::Address(value));
550    }
551    unit.mem[RETURN_CODE..RETURN_CODE + 2].copy_from_slice(&return_code);
552    if let (Some((place, _)), Some(val)) = (&plan.returning, returned) {
553        let dest = x.locate(*place, false)?;
554        x.assign(dest, val, None, pos)?;
555    }
556    Ok(succeeded(plan))
557}
558
559/// CALL through a FUNCTION-POINTER or PROCEDURE-POINTER: a JNI service from the function table,
560/// run here when it needs no JVM. Each argument is `Value` or `Omitted`, OMITTED as NULL. The
561/// reference services keep the JNI's rules for local and global references (see [`LOCAL_FRAMES`]).
562pub fn call_through_pointer<'w, P: Copy, O, S, X: OoHost<'w, P, O, S>>(x: &mut X, pointer: P, args: &[CallArg<P, O>], returning: Option<P>, pos: Pos) -> R<()> {
563    x.unit().unfollowed("object-oriented COBOL and calls through pointers");
564    let name = x.place_name(pointer);
565    let loc = x.locate(pointer, false)?;
566    let Ok(value) = <[u8; 4]>::try_from(store::bytes(x.mem(), loc)).map(u32::from_be_bytes) else {
567        return Err(Abend::ironwork(format!("CALL {name}: a reference-modified function-pointer"), pos));
568    };
569    let service = value.checked_sub(JNI_TAG).and_then(|slot| jni::function(slot as usize));
570    let Some(service) = service else {
571        return Err(Abend::ironwork(format!("CALL {name}: X'{value:08X}' is not a JNI service, the only entry a function-pointer can hold here"), pos));
572    };
573    let mut values = Vec::new();
574    for arg in args {
575        values.push(match arg {
576            CallArg::Value(op) => x.value(op, pos)?,
577            CallArg::Omitted => Val::Address(0),
578            CallArg::Reference(_) | CallArg::Content(_) => return Err(Abend::ironwork(format!("CALL {service}: a JNI service takes its arguments by value"), pos)),
579        });
580    }
581    let what = format!("CALL {service}");
582    let holder = |x: &X, n: usize| match args.get(n) {
583        Some(CallArg::Value(op)) => x.operand_name(op),
584        _ => None,
585    }
586    .unwrap_or_else(|| format!("argument {n}"));
587    let value = |n: usize| match values.get(n) {
588        Some(Val::Address(a)) => Ok(*a),
589        Some(Val::Fig(Figurative::Null)) => Ok(0),
590        _ => Err(Abend::ironwork(format!("CALL {service}: argument {n} is not an object reference"), pos)),
591    };
592    let referent_of = |x: &mut X, n: usize, value: u32| {
593        let holder = holder(x, n);
594        referent(&x.unit().oo, value, &what, &holder, pos)
595    };
596    let here = site(x, pos);
597    let result = match service {
598        "NewGlobalRef" | "NewLocalRef" => {
599            let object = referent_of(x, 1, value(1)?)?;
600            let reference = match object {
601                None => 0,
602                Some(o) => {
603                    let oo = &mut x.unit().oo;
604                    let made = oo.event(format!("made by {service} at {here}"));
605                    let made = if service == "NewGlobalRef" { oo.global(o, made) } else { oo.local(o, made) };
606                    made.map_err(|m| Abend::ironwork(m, pos))?
607                }
608            };
609            Some(Val::Address(reference))
610        }
611        "DeleteGlobalRef" | "DeleteLocalRef" => {
612            let reference = value(1)?;
613            if referent_of(x, 1, reference)?.is_some() {
614                let global = service == "DeleteGlobalRef";
615                if x.unit().oo.is_global(reference) != global {
616                    let kind = if global { "local" } else { "global" };
617                    return Err(Abend::ironwork(format!("{what}: {} holds a {kind} reference, which {service} does not delete (see {LOCAL_FRAMES})", holder(x, 1)), pos));
618                }
619                let oo = &mut x.unit().oo;
620                let event = oo.event(format!("was deleted by {service} at {here}"));
621                oo.free(reference, event);
622            }
623            None
624        }
625        "IsSameObject" => {
626            let same = referent_of(x, 1, value(1)?)? == referent_of(x, 2, value(2)?)?;
627            Some(Val::Bytes(vec![u8::from(same)]))
628        }
629        "GetObjectRefType" => {
630            let reference = value(1)?;
631            let kind = match referent_of(x, 1, reference)? {
632                None => 0,
633                Some(_) if x.unit().oo.is_global(reference) => 2,
634                Some(_) => 1,
635            };
636            Some(int(kind))
637        }
638        "PushLocalFrame" => {
639            x.unit().oo.push_frame(true);
640            Some(int(0))
641        }
642        "PopLocalFrame" => {
643            let Some(depth) = x.unit().oo.pushed_frame() else {
644                return Err(Abend::ironwork(format!("{what}: no frame PushLocalFrame pushed is open here (see {LOCAL_FRAMES})"), pos));
645            };
646            let object = referent_of(x, 1, value(1)?)?;
647            let oo = &mut x.unit().oo;
648            let expired = oo.event(format!("expired when PopLocalFrame at {here} freed its frame"));
649            oo.pop_frames(depth, expired);
650            let reference = match object {
651                None => 0,
652                Some(o) => local_reference(oo, o, format!("returned by PopLocalFrame at {here}"), pos)?,
653            };
654            Some(Val::Address(reference))
655        }
656        "EnsureLocalCapacity" => Some(int(0)),
657        "ExceptionOccurred" => Some(Val::Address(0)),
658        "ExceptionCheck" => Some(Val::Bytes(vec![0])),
659        "ExceptionClear" => None,
660        _ => {
661            return Err(crate::refusal::IWR0063.ending(AbendCode::Java, format_args!("CALL {name} was reached: {service} is a JNI service, and ironwork for COBOL has no JVM to run it"), pos));
662        }
663    };
664    if let (Some(target), Some(val)) = (returning, result) {
665        let dest = x.locate(target, false)?;
666        x.assign(dest, val, None, pos)?;
667    }
668    Ok(())
669}