Skip to main content

ironwork_rt/
unit.rs

1//! The run unit: every program a run calls, sharing one memory as they share an address space on
2//! z/OS. A called program keeps its WORKING-STORAGE and open files from one CALL to the next until
3//! it is cancelled, or in a CICS task until the LINK or XCTL that started its run unit ends it
4//! (C145). A reference or pointer can reach anywhere in this memory, but never outside it.
5//!
6//! `H` is the executor's handle to a loaded program and `L` the loader CALL goes through; the run
7//! unit holds both without looking inside, and asks `L` what it needs to know about an `H`.
8
9use crate::abend::Abend;
10use crate::files::{Dds, Open};
11use crate::oo::ClassCode;
12use crate::storage::Loc;
13use crate::taint::Taint;
14use crate::vocab::{OpenMode, Pos};
15use numeric::Dialect;
16use std::collections::{HashMap, HashSet, VecDeque};
17use std::io::{BufRead, Write};
18use std::path::{Path, PathBuf};
19use std::time::{Duration, Instant};
20use std::rc::Rc;
21
22/// A pointer's value is its offset into run-unit memory plus this, so that no item's address is
23/// NULL.
24pub const ADDRESS_BASE: u32 = 0x0001_0000;
25/// RETURN-CODE, a halfword shared by every program in the run unit.
26pub const RETURN_CODE: usize = 0;
27const RESERVED: usize = 8;
28const ALIGNMENT: usize = 8;
29
30pub struct Loaded<H> {
31    /// None for the first program, which the caller of the run unit owns.
32    pub compiled: Option<H>,
33    pub name: String,
34    pub base: usize,
35    pub size: usize,
36    /// False once a CICS run unit has set the program's storage at `base` aside, or has ended and
37    /// released it: its next activation gets storage of its own.
38    pub placed: bool,
39    pub files: Vec<Option<Open>>,
40    /// The files CLOSE WITH LOCK has closed, which OPEN refuses with status 38.
41    pub locked: Vec<bool>,
42    pub initialized: bool,
43    pub active: bool,
44    /// A dynamic CALL has entered it, so CANCEL acts on it.
45    pub dynamic: bool,
46    /// For a copy a dynamic CALL of an ENTRY name loaded, that entry (numbered as
47    /// [`Loader::entry`] numbers them); None for the program loaded by its PROGRAM-ID.
48    pub entry: Option<usize>,
49    /// Where each paragraph's GO TO goes since an ALTER, by paragraph; empty until one runs.
50    pub altered: Vec<Option<usize>>,
51    /// The library file CALL loaded the program from; None for the programs of the first source.
52    pub source: Option<PathBuf>,
53}
54
55impl<H> Loaded<H> {
56    /// A program with `files` files and `size` bytes of storage at `base`, in its initial state.
57    pub fn new(compiled: Option<H>, name: String, base: usize, size: usize, files: usize) -> Self {
58        let (locked, files) = (vec![false; files], (0..files).map(|_| None).collect());
59        Self { compiled, name, base, size, placed: true, files, locked, initialized: false, active: false, dynamic: false, entry: None, altered: Vec::new(), source: None }
60    }
61
62    /// What the program's activations have left, taken away: it is in its initial state, with its
63    /// storage set aside.
64    fn set_aside(&mut self) -> Held {
65        let files = self.files.iter_mut().map(Option::take).collect();
66        let locked = std::mem::replace(&mut self.locked, vec![false; self.files.len()]);
67        let held = Held { base: self.base, placed: self.placed, files, locked, initialized: self.initialized, active: self.active, dynamic: self.dynamic, altered: std::mem::take(&mut self.altered) };
68        (self.placed, self.initialized, self.active, self.dynamic) = (false, false, false, false);
69        held
70    }
71
72    fn restore(&mut self, held: Held) {
73        (self.base, self.placed, self.files, self.locked, self.altered) = (held.base, held.placed, held.files, held.locked, held.altered);
74        (self.initialized, self.active, self.dynamic) = (held.initialized, held.active, held.dynamic);
75    }
76}
77
78/// What a CICS run unit holds that the LINK or XCTL starting another sets aside until it ends:
79/// each program's state, the EXTERNAL data and files with the connectors to them, the Language
80/// Environment heap (C126), FUNCTION RANDOM's sequence (C104) and RETURN-CODE (C105), which
81/// belong to the enclave.
82struct Enclave {
83    programs: Vec<Held>,
84    externals: Externals,
85    connectors: HashMap<(usize, usize), Connector>,
86    heap: Vec<(usize, usize, bool)>,
87    random: Option<u32>,
88    /// RETURN-CODE's bytes, and whether they may hold input.
89    return_code: ([u8; 2], bool),
90}
91
92/// A program's state in a CICS run unit that a LINK or XCTL has set aside.
93struct Held {
94    base: usize,
95    placed: bool,
96    files: Vec<Option<Open>>,
97    locked: Vec<bool>,
98    initialized: bool,
99    active: bool,
100    dynamic: bool,
101    altered: Vec<Option<usize>>,
102}
103
104pub enum LoadError {
105    NotFound,
106    /// Found, but its source does not compile or its load module does not load.
107    Compile(String),
108}
109
110/// A program a loader found and compiled.
111pub struct LoadedProgram<H> {
112    pub compiled: H,
113    /// The PROGRAM-ID, in upper case.
114    pub name: String,
115    pub files: usize,
116    pub size: usize,
117    /// The file it was read from, when a program library supplied it.
118    pub source: Option<PathBuf>,
119    /// For a program a load module holds, unless of the source the run began with: each source of
120    /// its debug table by name, with the file the module records for it, its own source first.
121    pub recorded: Vec<(String, Option<crate::module::SourceFile>)>,
122}
123
124/// A class definition a loader found and compiled, and its source table, its own source first by
125/// path when a program library supplied it.
126pub struct FoundClass<C> {
127    pub code: C,
128    pub sources: Vec<String>,
129}
130
131/// Where CALL finds programs, and what the run unit needs to know about one it loaded.
132pub trait Loader<H> {
133    /// The program whose PROGRAM-ID CALL names, compiled.
134    fn program(&mut self, name: &str) -> Result<LoadedProgram<H>, LoadError>;
135
136    /// The PROGRAM-ID of a program not yet loaded that has an ENTRY of this name.
137    fn holder(&self, entry: &str) -> Option<String>;
138
139    /// Which of a loaded program's ENTRY statements has this name.
140    fn entry(program: &H, name: &str) -> Option<usize>;
141
142    /// A loaded program's file count and storage size.
143    fn shape(program: &H) -> (usize, usize);
144
145    /// The statement kinds, usages and options a loaded program holds.
146    fn facts(program: &H) -> numeric::governs::Facts;
147
148    /// The PROGRAM-IDs of the programs a loaded program contains, which a CANCEL of it reaches.
149    fn nested(program: &H) -> &[String];
150
151    /// Source file `file` of a loaded program's source table, by name.
152    fn source(program: &H, file: usize) -> Option<String>;
153
154    /// The COBOL class definition of this external name, its data and methods each a program the
155    /// executor runs; None for a Java class.
156    fn class(&mut self, external: &str) -> Result<Option<FoundClass<Rc<ClassCode<H>>>>, String>;
157
158    /// A BMS mapset from the copy libraries, which SEND MAP and RECEIVE MAP read; None when no
159    /// library holds it.
160    fn mapset(&mut self, name: &str) -> Option<Result<crate::bms::Mapset, String>>;
161}
162
163/// An executor's activation, as each service's host trait reaches the run unit through it: `Program`
164/// is the executor's handle to a loaded program, `Loader` the loader CALL goes through.
165pub trait UnitHost<'w> {
166    type Program: Clone;
167    type Loader: Loader<Self::Program>;
168    fn unit(&mut self) -> &mut RunUnit<'w, Self::Program, Self::Loader>;
169}
170
171#[derive(Clone, Copy, Debug)]
172pub enum Clock {
173    System,
174    /// Seconds since 1970-01-01T00:00:00Z and hundredths, for runs that must repeat exactly.
175    Fixed(i64, u32),
176}
177
178/// What a run did that its evidence journal records: each file as it is opened and closed, each
179/// program CALL loads, with the source it was read from when a library supplied it, and each
180/// operation an input could steer, with its operand as the program's code page reads it.
181pub enum Event<'a> {
182    Open { dd: &'a str, mode: OpenMode, path: &'a Path },
183    Close { dd: &'a str, path: &'a Path },
184    /// `recorded` is [`LoadedProgram::recorded`], empty for a program read from source; `facts`
185    /// what the program holds ([`Loader::facts`]).
186    Load { program: &'a str, source: Option<&'a Path>, recorded: &'a [(String, Option<crate::module::SourceFile>)], facts: numeric::governs::Facts },
187    /// A COBOL class definition an INVOKE loaded, with what its data and methods hold.
188    Class { class: &'a str, facts: numeric::governs::Facts },
189    /// Control entering paragraph (or section header) `index` of `program` at its start.
190    Paragraph { program: &'a str, name: &'a str, index: usize },
191    /// `kind` is cobolwork's name for the sink (`dynamic-program-load`, `log`, ...); `file` is the
192    /// library file or COPY member the operation is in, empty for the first program's own source;
193    /// `input`, under [`RunUnit::taint`], whether an input byte may be in the operand
194    /// ([`crate::taint::Taint::at_sink`]).
195    Sink { kind: &'static str, file: &'a str, line: u32, operand: &'a str, input: Option<bool> },
196    /// A statement starting, under [`RunUnit::statements`]; `file` as `Sink`'s.
197    Statement { file: &'a str, line: u32 },
198}
199
200/// Every kind of [`Event::Sink`] ironwork raises. A sink record joins a cobolwork finding by its
201/// kind, so each is one cobolwork's `lib/dataflow.mjs` names (fixtures/cobolwork/evidence/sinks.tsv).
202pub const SINK_KINDS: [&str; 16] = [
203    "cics-dynamic-transfer",
204    "cics-sysid",
205    "connection-target",
206    "dynamic-file-path",
207    "dynamic-program-load",
208    "dynamic-sql",
209    "http-header",
210    "log",
211    "os-command",
212    "outbound-host",
213    "outbound-http",
214    "queue-name",
215    "record-key",
216    "record-update",
217    "screen",
218    "web-response",
219];
220
221/// The statements whose start a run tells its observer of: every one, or those on these lines of
222/// any source, which the observer narrows to their files.
223#[derive(Clone, Debug, PartialEq, Eq)]
224pub enum StatementFilter {
225    All,
226    Lines(HashSet<u32>),
227}
228
229pub type Observer<'w> = Box<dyn FnMut(Event<'_>) + 'w>;
230
231/// How deep PERFORMs and CALLs may nest before the run abends, rather than exhaust the stack.
232pub const MAX_DEPTH: usize = 100;
233
234/// EXTERNAL data records and file connectors, which belong to the run unit rather than to a
235/// program: one of each name, whichever program first describes it (Language Reference
236/// SC27-8713-03, pp. 65, 184, 197).
237#[derive(Default)]
238pub struct Externals {
239    /// Each EXTERNAL data record, and each EXTERNAL file's record area, by name: where it is and
240    /// how many bytes it has.
241    storage: HashMap<(bool, String), (usize, usize)>,
242    files: Vec<Option<Open>>,
243    /// Whether each EXTERNAL file was closed WITH LOCK.
244    locked: Vec<bool>,
245    file_names: HashMap<String, usize>,
246}
247
248/// The EXTERNAL record of the run unit that holds UPSI switch `n`: one byte, 1 when the switch is
249/// on and 0 when it is off (assumption C410). No program can spell the name, so only a
250/// SPECIAL-NAMES entry for the switch reaches it.
251pub fn switch_record(n: u8) -> String {
252    format!("UPSI-{n} SWITCH")
253}
254
255/// The UPSI switch whose [`switch_record`] is named `name`.
256pub fn switch_of_record(name: &str) -> Option<u8> {
257    match name.strip_prefix("UPSI-")?.strip_suffix(" SWITCH")?.as_bytes() {
258        [d @ b'0'..=b'7'] => Some(d - b'0'),
259        _ => None,
260    }
261}
262
263/// A file of a program that is another's file connector.
264#[derive(Clone, Copy, Debug, PartialEq, Eq)]
265pub enum Connector {
266    /// The run unit's EXTERNAL file of this number.
267    External(usize),
268    /// File `k` of loaded program `p`: a GLOBAL file of a program containing this one.
269    Program(usize, usize),
270}
271
272/// The routines cobolwork reads a CALL of as running an operating-system command, whose arguments
273/// the input trace checks.
274pub const OS_COMMAND_ROUTINES: &[&str] = &["SYSTEM", "C$SYSTEM", "CBL_EXEC_RUN_UNIT", "CBL_GC_HOSTED", "BXPSYSTM"];
275
276pub struct RunUnit<'w, H, L: Loader<H>> {
277    pub mem: Vec<u8>,
278    /// PERFORMs and CALLs in progress, across every program.
279    pub depth: usize,
280    pub programs: Vec<Loaded<H>>,
281    names: HashMap<String, usize>,
282    pub library: L,
283    pub dds: Dds,
284    pub sysin: Option<Box<dyn BufRead + 'w>>,
285    pub clock: Clock,
286    pub out: &'w mut dyn Write,
287    pub err: &'w mut dyn Write,
288    /// The CICS task a harness run stands in for, with its EXEC interface block and open files.
289    pub cics: Option<crate::cics::Task>,
290    pub eib: usize,
291    pub cics_files: HashMap<String, Open>,
292    /// The database EXEC SQL statements reach, when the run has one.
293    pub sql: Option<crate::sql::Session<'w>>,
294    /// Language Environment's heap storage and message files.
295    pub le: crate::le::State,
296    /// Classes, objects and the JNI environment of the run unit's object-oriented programs.
297    pub oo: crate::oo::Objects<Rc<ClassCode<H>>>,
298    /// Told what the run opens, closes and loads, when a caller keeps evidence of it.
299    pub observer: Option<Observer<'w>>,
300    /// FUNCTION RANDOM's generator, one for the run unit, from the first reference on.
301    pub random: Option<u32>,
302    /// The first program activated, the run unit's main program.
303    pub main: Option<usize>,
304    /// The programs CALLs, functions and INVOKEs in progress entered, outermost first.
305    pub calls: Vec<usize>,
306    /// The length of each argument the entries in `calls` were passed, 0 for one omitted, each
307    /// entry's from its start in `argument_starts`.
308    pub argument_lengths: Vec<usize>,
309    pub argument_starts: Vec<usize>,
310    /// The job step's program arguments, which ACCEPT ... FROM COMMAND-LINE and ARGUMENT-VALUE read
311    /// under `--compliance extended`; empty without a PARM.
312    pub arguments: crate::le::parm::Arguments,
313    /// The screen positioned DISPLAY and ACCEPT use under `--compliance extended`, and the operator
314    /// a screen script plays; None until one is given or the run first uses the screen.
315    pub crt: Option<Rc<std::cell::RefCell<crate::crt::Crt>>>,
316    /// The environment variables ACCEPT ... FROM ENVIRONMENT reads under `--compliance extended`.
317    pub environment: crate::environment::Environment,
318    externals: Externals,
319    /// The files of loaded programs that are another's connector, by program and file.
320    connectors: HashMap<(usize, usize), Connector>,
321    /// The entries SET TO ENTRY has named, which function-pointers and procedure-pointers hold.
322    pub entries: Vec<crate::set::Entry>,
323    /// The statements an observer is told of as each starts; None tells it of none.
324    pub statements: Option<StatementFilter>,
325    /// Which bytes may hold input, when the run traces input.
326    pub taint: Option<Taint>,
327    /// How many more statements may start before the run ends with S322; None for no limit.
328    pub statement_limit: Option<u64>,
329    /// When the run ends with S322 for its time limit, and that limit in seconds.
330    deadline: Option<(Instant, u64)>,
331    /// Statement starts since the clock was last read against `deadline`.
332    unclocked: u32,
333    /// The bytes of storage the run unit may hold before the run ends; None for no limit.
334    storage_limit: Option<usize>,
335    /// The last statements started under a statement limit, oldest first, and once it is spent the
336    /// loop statement the S322 waits for.
337    recent: VecDeque<Started>,
338    overrun: Option<Overrun>,
339    /// The ACCEPTs, by position, that have said they found SYSIN at its end; a loop around one
340    /// would otherwise write a line each time round.
341    pub(crate) sysin_ended: HashSet<(u16, u32, u32)>,
342    /// The CICS run units the LINKs and XCTLs running have set aside, the innermost last.
343    set_aside: Vec<Enclave>,
344}
345
346fn end_file(f: Open, unclosed: bool) -> std::io::Result<()> {
347    if unclosed { f.abandon() } else { f.close() }
348}
349
350/// How many statement starts pass between readings of the clock under a time limit.
351const CLOCK_EVERY: u32 = 256;
352
353/// How many of the last statement starts an S322 looks back over for the loop the run is in, and
354/// how many more it lets start while it waits for that loop's first statement.
355const LOOP_WINDOW: usize = 4096;
356
357/// A statement start: the loaded program, how deep PERFORMs and CALLs had nested, and where.
358#[derive(Clone, Copy, PartialEq, Eq)]
359struct Started {
360    program: usize,
361    depth: usize,
362    pos: Pos,
363}
364
365/// A spent statement limit: the loop's first statement, the S322's place, its lines, and the starts
366/// left before the run ends wherever it is.
367struct Overrun {
368    head: Started,
369    lines: Vec<u32>,
370    grace: usize,
371}
372
373/// The loop the last starts ran: the statements that started more than once among them, those of
374/// their outermost frame, in its program's own source before any COPY member, and the first of
375/// them by position. A loop's statements recur at one depth however many statements ran before
376/// it, and anything it PERFORMs or CALLs runs deeper. With none recurring, the statement starting
377/// now and no lines.
378fn loop_of(recent: &VecDeque<Started>, now: Started) -> Overrun {
379    let key = |s: &Started| (s.program, s.depth, s.pos.file, s.pos.line, s.pos.col);
380    let mut seen: HashMap<_, usize> = HashMap::new();
381    for s in recent {
382        *seen.entry(key(s)).or_default() += 1;
383    }
384    let recurring: Vec<&Started> = recent.iter().filter(|s| seen[&key(s)] > 1).collect();
385    let Some(outer) = recurring.iter().map(|s| s.depth).min() else { return Overrun { head: now, lines: Vec::new(), grace: 0 } };
386    let program = recurring.iter().rev().find(|s| s.depth == outer).map_or(now.program, |s| s.program);
387    let frame: Vec<&Started> = recurring.into_iter().filter(|s| s.depth == outer && s.program == program).collect();
388    let file = frame.iter().map(|s| s.pos.file).min().unwrap_or(now.pos.file);
389    let head = frame.iter().filter(|s| s.pos.file == file).min_by_key(|s| (s.pos.line, s.pos.col)).map_or(now, |s| **s);
390    let mut lines: Vec<u32> = frame.iter().filter(|s| s.pos.file == file).map(|s| s.pos.line).collect();
391    lines.sort_unstable();
392    lines.dedup();
393    Overrun { head, lines, grace: LOOP_WINDOW }
394}
395
396/// The return code a job step ends with: RETURN-CODE modulo 4096, a negative value by the same
397/// arithmetic (assumption C457).
398pub fn step_return_code(return_code: i16) -> u16 {
399    i32::from(return_code).rem_euclid(4096) as u16
400}
401
402impl<H, L: Loader<H>> RunUnit<'_, H, L> {
403    /// Copies `bytes` into memory at `offset`; under taint they may hold input when the running
404    /// statement has read a byte that may. Every write of data to memory goes through here or
405    /// [`RunUnit::write_input`], or marks its bytes with [`RunUnit::mark`].
406    pub fn write(&mut self, offset: usize, bytes: &[u8]) {
407        self.mem[offset..offset + bytes.len()].copy_from_slice(bytes);
408        self.mark(offset, bytes.len());
409    }
410
411    /// Copies input into memory at `offset`: bytes a READ, ACCEPT or row brought in.
412    pub fn write_input(&mut self, offset: usize, bytes: &[u8]) {
413        self.mem[offset..offset + bytes.len()].copy_from_slice(bytes);
414        self.mark_input(offset, bytes.len(), true);
415    }
416
417    /// Marks bytes written outside [`RunUnit::write`] as it would.
418    pub fn mark(&mut self, offset: usize, len: usize) {
419        if let Some(t) = self.taint.as_mut() {
420            let pending = t.pending();
421            t.set(offset, len, pending);
422        }
423    }
424
425    /// Marks bytes as input, or as holding none: initial values, which are constants.
426    pub fn mark_input(&mut self, offset: usize, len: usize, input: bool) {
427        if let Some(t) = self.taint.as_mut() {
428            t.set(offset, len, input);
429        }
430    }
431
432    /// A read of `loc` by the running statement.
433    pub fn taint_read(&mut self, loc: Loc) {
434        if let Some(t) = self.taint.as_mut() {
435            t.read(loc.offset, loc.len);
436        }
437    }
438
439    /// [`Taint::writing`], when the run traces input.
440    pub fn writing(&mut self, on: bool) -> bool {
441        self.taint.as_mut().is_some_and(|t| t.writing(on))
442    }
443
444    /// A statement with a position starts: its writes carry only what it reads.
445    pub fn statement_starts(&mut self) {
446        if let Some(t) = self.taint.as_mut() {
447            t.start_statement();
448        }
449    }
450
451    /// [`Taint::take_input`], when the run traces input.
452    pub fn take_input(&mut self) {
453        if let Some(t) = self.taint.as_mut() {
454            t.take_input();
455        }
456    }
457
458    /// Whether any byte of the range may hold input; false without taint.
459    pub fn holds_input(&self, offset: usize, len: usize) -> bool {
460        self.taint.as_ref().is_some_and(|t| t.any(offset, len))
461    }
462
463    /// Whether the running statement has read a byte that may hold input.
464    pub fn pending(&self) -> bool {
465        self.taint.as_ref().is_some_and(Taint::pending)
466    }
467
468    /// [`Taint::resume_statement`], when the run traces input.
469    pub fn resume_statement(&mut self, read_before: bool) {
470        if let Some(t) = self.taint.as_mut() {
471            t.resume_statement(read_before);
472        }
473    }
474
475    /// The run did `what`, which taint does not follow.
476    pub fn unfollowed(&mut self, what: &'static str) {
477        if let Some(t) = self.taint.as_mut() {
478            t.unfollowed(what);
479        }
480    }
481
482    /// Whether an input byte may be in a sink's operand; None without taint.
483    pub fn input_at_sink(&self) -> Option<bool> {
484        self.taint.as_ref().and_then(Taint::at_sink)
485    }
486}
487
488impl<'w, H: Clone, L: Loader<H>> RunUnit<'w, H, L> {
489    pub fn new(library: L, dds: Dds, sysin: Option<Box<dyn BufRead + 'w>>, clock: Clock, out: &'w mut dyn Write, err: &'w mut dyn Write) -> Self {
490        Self {
491            mem: vec![0; RESERVED],
492            depth: 0,
493            programs: Vec::new(),
494            names: HashMap::new(),
495            library,
496            dds,
497            sysin,
498            clock,
499            out,
500            err,
501            cics: None,
502            eib: 0,
503            cics_files: HashMap::new(),
504            sql: None,
505            le: crate::le::State::default(),
506            oo: Default::default(),
507            observer: None,
508            random: None,
509            main: None,
510            calls: Vec::new(),
511            argument_lengths: Vec::new(),
512            argument_starts: Vec::new(),
513            arguments: Default::default(),
514            crt: None,
515            environment: Default::default(),
516            externals: Externals::default(),
517            connectors: HashMap::new(),
518            entries: Vec::new(),
519            statements: None,
520            taint: None,
521            statement_limit: None,
522            deadline: None,
523            unclocked: 0,
524            storage_limit: None,
525            recent: VecDeque::new(),
526            overrun: None,
527            sysin_ended: HashSet::new(),
528            set_aside: Vec::new(),
529        }
530    }
531
532    fn allocate(&mut self, size: usize) -> usize {
533        let base = self.mem.len().div_ceil(ALIGNMENT) * ALIGNMENT;
534        self.mem.resize(base + size, 0);
535        base
536    }
537
538    /// Adds a program to the run unit under `name` and gives it its storage.
539    pub fn add_named(&mut self, compiled: Option<H>, name: String, files: usize, size: usize) -> usize {
540        let base = self.allocate(size);
541        let index = self.programs.len();
542        self.names.insert(name.clone(), index);
543        self.programs.push(Loaded::new(compiled, name, base, size, files));
544        index
545    }
546
547    /// A CICS LINK or XCTL starts a run unit of its own (C145): every program starts in it in its
548    /// initial state, with storage of its own, it has no EXTERNAL data or files and an empty heap
549    /// (C126), FUNCTION RANDOM has not been referenced (C104) and RETURN-CODE is zero (C105), and
550    /// the state of the run unit that issued it is set aside until [`RunUnit::end_cics_run_unit`].
551    pub fn begin_cics_run_unit(&mut self) {
552        let programs = self.programs.iter_mut().map(Loaded::set_aside).collect();
553        let (externals, connectors) = (std::mem::take(&mut self.externals), std::mem::take(&mut self.connectors));
554        let return_code = ([self.mem[RETURN_CODE], self.mem[RETURN_CODE + 1]], self.holds_input(RETURN_CODE, 2));
555        self.mem[RETURN_CODE..RETURN_CODE + 2].fill(0);
556        self.mark_input(RETURN_CODE, 2, false);
557        let (heap, random) = (std::mem::take(&mut self.le.heap), self.random.take());
558        self.set_aside.push(Enclave { programs, externals, connectors, heap, random, return_code });
559    }
560
561    /// Ends the run unit [`RunUnit::begin_cics_run_unit`] started, closing the files its programs
562    /// and its EXTERNAL files left open as Language Environment closes an enclave's, and dropping
563    /// its programs' storage, EXTERNAL data and heap: a program it loaded stays loaded, in its
564    /// initial state with no storage (C129), and the run unit set aside has everything back but,
565    /// after `xctl`, RETURN-CODE, the program XCTL started having taken the issuer's place (C105).
566    pub fn end_cics_run_unit(&mut self, xctl: bool) -> Result<(), String> {
567        let mut closed = Ok(());
568        for program in &mut self.programs {
569            for f in program.files.iter_mut().filter_map(Option::take) {
570                if let Err(e) = f.close() {
571                    closed = closed.and(Err(format!("closing a file of {}: {e}", program.name)));
572                }
573            }
574            drop(program.set_aside());
575        }
576        closed = closed.and(self.close_external_files(false));
577        let Some(enclave) = self.set_aside.pop() else { return closed };
578        for (program, held) in self.programs.iter_mut().zip(enclave.programs) {
579            program.restore(held);
580        }
581        (self.externals, self.connectors, self.le.heap, self.random) = (enclave.externals, enclave.connectors, enclave.heap, enclave.random);
582        if !xctl {
583            let (bytes, input) = enclave.return_code;
584            self.mem[RETURN_CODE..RETURN_CODE + 2].copy_from_slice(&bytes);
585            self.mark_input(RETURN_CODE, 2, input);
586        }
587        closed
588    }
589
590    /// The program a CALL of `name` enters, and which of its ENTRY statements when `name` is not
591    /// its PROGRAM-ID: the one copy of the program, or with `copy` a copy of its own for the entry
592    /// name ([`crate::callee::entry_copy`]).
593    pub fn load_entry(&mut self, name: &str, copy: bool) -> Result<(usize, Option<usize>), LoadError> {
594        if let Some(i) = self.find(name) {
595            return Ok((i, self.programs[i].entry));
596        }
597        let name = name.to_ascii_uppercase();
598        let index = match self.programs.iter().position(|p| p.compiled.as_ref().is_some_and(|c| L::entry(c, &name).is_some())) {
599            Some(i) => i,
600            None => {
601                let holder = self.library.holder(&name);
602                self.load(holder.as_deref().unwrap_or(&name))?
603            }
604        };
605        let Some(compiled) = self.programs[index].compiled.clone() else { return Ok((index, None)) };
606        let Some(entry) = L::entry(&compiled, &name) else { return Ok((index, None)) };
607        if !copy {
608            return Ok((index, Some(entry)));
609        }
610        let (files, size) = L::shape(&compiled);
611        let copy = self.add_named(Some(compiled), name, files, size);
612        self.programs[copy].entry = Some(entry);
613        self.programs[copy].source = self.programs[index].source.clone();
614        Ok((copy, Some(entry)))
615    }
616
617    /// Storage for a BY CONTENT or BY VALUE argument, at the end of memory, written as
618    /// [`RunUnit::write`] writes.
619    pub fn push_temporary(&mut self, bytes: &[u8]) -> usize {
620        let at = self.allocate(bytes.len());
621        self.write(at, bytes);
622        at
623    }
624
625    /// Releases arguments pushed since `mark`, unless a program's storage, heap storage or EXTERNAL
626    /// storage was placed behind them.
627    pub fn release_temporaries(&mut self, mark: usize) {
628        if self.mem.len() <= mark {
629            return;
630        }
631        let external = self.externals.storage.values().all(|&(at, _)| at < mark);
632        if self.programs.iter().all(|p| !p.placed || p.base + p.size <= mark) && self.le.heap_end() <= mark && external {
633            self.mem.truncate(mark.max(RESERVED));
634            if let Some(t) = self.taint.as_mut() {
635                t.truncate(self.mem.len());
636            }
637        }
638    }
639
640    /// One more PERFORM or CALL in progress, refused past `MAX_DEPTH` rather than exhaust the stack.
641    pub fn enter(&mut self, pos: Pos) -> Result<(), Abend> {
642        if self.depth >= MAX_DEPTH {
643            return Err(Abend::ironwork(format!("PERFORM and CALL nest deeper than {MAX_DEPTH}"), pos));
644        }
645        self.depth += 1;
646        Ok(())
647    }
648
649    /// Marks program `me` active: where its storage starts, and whether the activation starts from
650    /// fresh storage, as its first does, the first after a CANCEL, and every one of an INITIAL
651    /// program.
652    pub fn activate(&mut self, me: usize, initial: bool) -> (usize, bool) {
653        if !self.programs[me].placed {
654            let base = self.allocate(self.programs[me].size);
655            (self.programs[me].base, self.programs[me].placed) = (base, true);
656        }
657        self.main.get_or_insert(me);
658        let program = &mut self.programs[me];
659        program.active = true;
660        (program.base, !program.initialized || initial)
661    }
662
663    /// The program that entered program `me`'s latest activation: the main program for one a CALL
664    /// from it entered, None for the main program.
665    pub fn caller_of(&self, me: usize) -> Option<usize> {
666        match self.calls.iter().rposition(|&p| p == me)? {
667            0 => self.main,
668            k => Some(self.calls[k - 1]),
669        }
670    }
671
672    /// ALLOCATE's storage: `size` bytes of zeros from the heap CEEGTST also takes from, and their
673    /// address; NULL past the largest request the run grants (Language Reference, ALLOCATE).
674    pub fn heap_allocate(&mut self, size: usize) -> u32 {
675        if size == 0 || size > crate::le::HEAP_LIMIT {
676            return 0;
677        }
678        let at = self.push_temporary(&vec![0; size]);
679        self.le.heap.push((at, size, false));
680        ADDRESS_BASE + at as u32
681    }
682
683    /// FREE: the block `address` starts released, and NULL; any other address given back as it is,
684    /// nothing freed (Language Reference, FREE).
685    pub fn heap_free(&mut self, address: u32) -> u32 {
686        let offset = address.checked_sub(ADDRESS_BASE).map(|o| o as usize);
687        match self.le.heap.iter_mut().find(|(start, _, freed)| Some(*start) == offset && !*freed) {
688            Some(block) => {
689                block.2 = true;
690                0
691            }
692            None => address,
693        }
694    }
695
696    /// The length of the argument in USING position `position`, from 1, of program `me`'s latest
697    /// activation: 0 for one omitted or not passed, and in the main program.
698    pub fn argument_length_of(&self, me: usize, position: usize) -> usize {
699        let Some(k) = self.calls.iter().rposition(|&p| p == me) else { return 0 };
700        let end = self.argument_starts.get(k + 1).copied().unwrap_or(self.argument_lengths.len());
701        position.checked_sub(1).and_then(|i| self.argument_lengths[self.argument_starts[k]..end].get(i)).copied().unwrap_or(0)
702    }
703
704    /// Program `me`'s storage holds its initial values, and its GO TOs go where they are written.
705    pub fn initialized(&mut self, me: usize) {
706        self.programs[me].initialized = true;
707        self.programs[me].altered.clear();
708    }
709
710    pub fn find(&self, name: &str) -> Option<usize> {
711        if name.bytes().any(|b| b.is_ascii_lowercase()) {
712            return self.names.get(&name.to_ascii_uppercase()).copied();
713        }
714        self.names.get(name).copied()
715    }
716
717    /// The program CALL names, compiling and loading it the first time.
718    pub fn load(&mut self, name: &str) -> Result<usize, LoadError> {
719        let name = name.to_ascii_uppercase();
720        if let Some(i) = self.find(&name) {
721            return Ok(i);
722        }
723        let loaded = self.library.program(&name)?;
724        self.notify(Event::Load { program: &name, source: loaded.source.as_deref(), recorded: &loaded.recorded, facts: L::facts(&loaded.compiled) });
725        let index = self.add_named(Some(loaded.compiled), loaded.name, loaded.files, loaded.size);
726        self.programs[index].source = loaded.source;
727        Ok(index)
728    }
729
730    pub const fn observed(&self) -> bool {
731        self.observer.is_some()
732    }
733
734    /// Whether a statement starting on `line` is told to the observer.
735    /// Sets the run's limits: statements that may start, seconds from now, and bytes of storage.
736    /// Each is checked as a statement starts; a request for storage past the limit is granted, and
737    /// the run ends at the next statement.
738    pub fn limit(&mut self, statements: Option<u64>, seconds: Option<u64>, storage: Option<u64>) {
739        self.statement_limit = statements;
740        self.deadline = seconds.and_then(|s| Some((Instant::now().checked_add(Duration::from_secs(s))?, s)));
741        self.storage_limit = storage.map(|b| usize::try_from(b).unwrap_or(usize::MAX));
742    }
743
744    /// Whether statement starts are checked against a limit.
745    pub const fn limited(&self) -> bool {
746        self.statement_limit.is_some() || self.deadline.is_some() || self.storage_limit.is_some()
747    }
748
749    /// Counts the start of `program`'s statement at `pos` against the run's limits. Storage past
750    /// its limit ends the run there, and so does the time limit, read every `CLOCK_EVERY` starts.
751    /// Once the statement limit is spent the run ends with S322, as z/OS ends a step that runs past its TIME=, at the next start
752    /// of the loop it is in, so the place does not depend on how many statements ran before the
753    /// loop (assumption C241).
754    pub fn start_statement(&mut self, program: usize, pos: Pos) -> Result<(), Abend> {
755        if let Some(limit) = self.storage_limit
756            && self.mem.len() > limit
757        {
758            return Err(Abend::ironwork(format!("the run unit's storage reached {} bytes, past its storage limit of {limit}", self.mem.len()), pos));
759        }
760        if let Some((deadline, seconds)) = self.deadline {
761            self.unclocked += 1;
762            if self.unclocked == CLOCK_EVERY {
763                self.unclocked = 0;
764                if Instant::now() >= deadline {
765                    let unit = if seconds == 1 { "second" } else { "seconds" };
766                    let message = format!("the run reached its time limit of {seconds} {unit}, as a step past its TIME= ends");
767                    return Err(Abend { code: crate::abend::AbendCode::TimeLimit, message, pos, file: None });
768                }
769            }
770        }
771        let Some(left) = self.statement_limit.as_mut() else { return Ok(()) };
772        let now = Started { program, depth: self.depth, pos };
773        if *left > 0 {
774            *left -= 1;
775            if self.recent.len() == LOOP_WINDOW {
776                self.recent.pop_front();
777            }
778            self.recent.push_back(now);
779            return Ok(());
780        }
781        let overrun = self.overrun.get_or_insert_with(|| loop_of(&self.recent, now));
782        let message = "the run reached its statement limit, as a step past its TIME= ends";
783        if now == overrun.head && !overrun.lines.is_empty() {
784            const SHOWN: usize = 24;
785            let mut lines = overrun.lines.iter().take(SHOWN).map(u32::to_string).collect::<Vec<_>>().join(", ");
786            if overrun.lines.len() > SHOWN {
787                lines += &format!(" and {} more", overrun.lines.len() - SHOWN);
788            }
789            return Err(Abend { code: crate::abend::AbendCode::TimeLimit, message: format!("{message}, in the loop over lines {lines}"), pos, file: None });
790        }
791        if overrun.grace == 0 {
792            return Err(Abend { code: crate::abend::AbendCode::TimeLimit, message: message.into(), pos, file: None });
793        }
794        overrun.grace -= 1;
795        Ok(())
796    }
797
798    pub fn traces(&self, line: u32) -> bool {
799        match &self.statements {
800            None => false,
801            Some(StatementFilter::All) => self.observer.is_some(),
802            Some(StatementFilter::Lines(lines)) => self.observer.is_some() && lines.contains(&line),
803        }
804    }
805
806    pub fn notify(&mut self, event: Event<'_>) {
807        if let Event::Sink { kind, .. } = &event {
808            debug_assert!(SINK_KINDS.contains(kind), "the sink kind {kind} is not in rt::unit::SINK_KINDS");
809        }
810        if let Some(observer) = self.observer.as_mut() {
811            observer(event);
812        }
813    }
814
815    /// Closes every file any program left open, as the runtime does when the run unit ends, normally
816    /// or by an abend under TRAP(ON). An abend TRAP(OFF) keeps from Language Environment closes
817    /// none (`unclosed`), and each VSAM data set stays marked open for output
818    /// ([`numeric::assumptions::TRAP_OFF_LEAVES_FILES_OPEN`]).
819    pub fn close_all(&mut self, unclosed: bool) -> Result<(), String> {
820        for program in &mut self.programs {
821            for f in program.files.iter_mut().filter_map(Option::take) {
822                end_file(f, unclosed).map_err(|e| format!("closing a file of {}: {e}", program.name))?;
823            }
824        }
825        self.close_external_files(unclosed)
826    }
827
828    /// Closes the EXTERNAL files left open, giving the first failure.
829    fn close_external_files(&mut self, unclosed: bool) -> Result<(), String> {
830        let mut closed = Ok(());
831        for (name, &k) in &self.externals.file_names {
832            if let Some(f) = self.externals.files[k].take()
833                && let Err(e) = end_file(f, unclosed)
834            {
835                closed = closed.and(Err(format!("closing EXTERNAL file {name}: {e}")));
836            }
837        }
838        closed
839    }
840
841    /// Where EXTERNAL record `name` is, or EXTERNAL file `name`'s record area when `file`: storage
842    /// of `size` bytes, zeroed, the first time a program describes it. A description of another
843    /// size ends the run U4038 with IGZ0066S, or IGZ0075S for a file (assumption C180), except under
844    /// gnucobol a shorter record's, which shares the storage with a warning, as cobc's does.
845    pub fn external(&mut self, name: &str, file: bool, size: usize, dialect: Dialect, program: &str) -> Result<usize, Abend> {
846        let key = (file, name.to_owned());
847        if let Some(&(at, had)) = self.externals.storage.get(&key) {
848            return if had == size {
849                Ok(at)
850            } else if size < had && !file && dialect == Dialect::Gnucobol {
851                let _ = writeln!(self.err, "ironwork: EXTERNAL record {name} has {had} bytes in the run unit, and this program describes {size}");
852                Ok(at)
853            } else {
854                let message = if file {
855                    format!("IGZ0075S Inconsistencies were found in EXTERNAL file {name} in program {program}. The following file attributes did not match those of the established external file: the record length. ({had} bytes in the run unit, {size} here)")
856                } else {
857                    format!("IGZ0066S The length of external data record {name} in program {program} did not match the existing length of the record. ({had} bytes in the run unit, {size} here)")
858                };
859                Err(Abend { code: crate::abend::AbendCode::user(4038), message, pos: Pos::default(), file: None })
860            };
861        }
862        let at = self.allocate(size);
863        self.externals.storage.insert(key, (at, size));
864        Ok(at)
865    }
866
867    /// Sets the eight UPSI switches before a program runs, each [`switch_record`] holding 1 for
868    /// on, and marks them as input, since the PARM that sets them is (assumption C411).
869    pub fn set_switches(&mut self, on: [bool; 8]) {
870        for (n, on) in (0..).zip(on) {
871            let at = self.push_temporary(&[u8::from(on)]);
872            self.externals.storage.insert((false, switch_record(n)), (at, 1));
873            self.mark_input(at, 1, true);
874        }
875    }
876
877    /// The run unit's connector for EXTERNAL file `name`.
878    pub fn external_file(&mut self, name: &str) -> Connector {
879        let (files, locked) = (&mut self.externals.files, &mut self.externals.locked);
880        let k = *self.externals.file_names.entry(name.to_owned()).or_insert_with(|| {
881            files.push(None);
882            locked.push(false);
883            files.len() - 1
884        });
885        Connector::External(k)
886    }
887
888    /// Program `me`'s file k is the connector `to`, for as long as the run unit lasts.
889    pub fn connect(&mut self, me: usize, k: usize, to: Connector) {
890        self.connectors.insert((me, k), to);
891    }
892
893    fn connector(&self, mut me: usize, mut k: usize) -> Option<Connector> {
894        let mut to = None;
895        while let Some(&c) = self.connectors.get(&(me, k)) {
896            to = Some(c);
897            match c {
898                Connector::External(_) => break,
899                Connector::Program(p, j) => (me, k) = (p, j),
900            }
901        }
902        to
903    }
904
905    /// Program `me`'s file k, open or not: its own, or the connector it shares.
906    pub fn file(&mut self, me: usize, k: usize) -> &mut Option<Open> {
907        match self.connector(me, k) {
908            None => &mut self.programs[me].files[k],
909            Some(Connector::External(e)) => &mut self.externals.files[e],
910            Some(Connector::Program(p, j)) => &mut self.programs[p].files[j],
911        }
912    }
913
914    /// Whether program `me`'s file k, or the connector it shares, was closed WITH LOCK.
915    pub fn locked(&mut self, me: usize, k: usize) -> &mut bool {
916        match self.connector(me, k) {
917            None => &mut self.programs[me].locked[k],
918            Some(Connector::External(e)) => &mut self.externals.locked[e],
919            Some(Connector::Program(p, j)) => &mut self.programs[p].locked[j],
920        }
921    }
922
923    pub fn file_ref(&self, me: usize, k: usize) -> &Option<Open> {
924        match self.connector(me, k) {
925            None => &self.programs[me].files[k],
926            Some(Connector::External(e)) => &self.externals.files[e],
927            Some(Connector::Program(p, j)) => &self.programs[p].files[j],
928        }
929    }
930
931    pub fn return_code(&self) -> i16 {
932        i16::from_be_bytes([self.mem[RETURN_CODE], self.mem[RETURN_CODE + 1]])
933    }
934
935    /// The current time: seconds since the epoch, and hundredths.
936    pub fn now(&self) -> (i64, u32) {
937        match self.clock {
938            Clock::Fixed(s, h) => (s, h),
939            Clock::System => {
940                let d = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default();
941                (d.as_secs() as i64, d.subsec_millis() / 10)
942            }
943        }
944    }
945}