1use 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::rc::Rc;
20
21pub const ADDRESS_BASE: u32 = 0x0001_0000;
24pub const RETURN_CODE: usize = 0;
26const RESERVED: usize = 8;
27const ALIGNMENT: usize = 8;
28
29pub struct Loaded<H> {
30 pub compiled: Option<H>,
32 pub name: String,
33 pub base: usize,
34 pub size: usize,
35 pub placed: bool,
38 pub files: Vec<Option<Open>>,
39 pub locked: Vec<bool>,
41 pub initialized: bool,
42 pub active: bool,
43 pub dynamic: bool,
45 pub entry: Option<usize>,
48 pub altered: Vec<Option<usize>>,
50 pub source: Option<PathBuf>,
52}
53
54impl<H> Loaded<H> {
55 pub fn new(compiled: Option<H>, name: String, base: usize, size: usize, files: usize) -> Self {
57 let (locked, files) = (vec![false; files], (0..files).map(|_| None).collect());
58 Self { compiled, name, base, size, placed: true, files, locked, initialized: false, active: false, dynamic: false, entry: None, altered: Vec::new(), source: None }
59 }
60
61 fn set_aside(&mut self) -> Held {
64 let files = self.files.iter_mut().map(Option::take).collect();
65 let locked = std::mem::replace(&mut self.locked, vec![false; self.files.len()]);
66 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) };
67 (self.placed, self.initialized, self.active, self.dynamic) = (false, false, false, false);
68 held
69 }
70
71 fn restore(&mut self, held: Held) {
72 (self.base, self.placed, self.files, self.locked, self.altered) = (held.base, held.placed, held.files, held.locked, held.altered);
73 (self.initialized, self.active, self.dynamic) = (held.initialized, held.active, held.dynamic);
74 }
75}
76
77struct Enclave {
82 programs: Vec<Held>,
83 externals: Externals,
84 connectors: HashMap<(usize, usize), Connector>,
85 heap: Vec<(usize, usize, bool)>,
86 random: Option<u32>,
87 return_code: ([u8; 2], bool),
89}
90
91struct Held {
93 base: usize,
94 placed: bool,
95 files: Vec<Option<Open>>,
96 locked: Vec<bool>,
97 initialized: bool,
98 active: bool,
99 dynamic: bool,
100 altered: Vec<Option<usize>>,
101}
102
103pub enum LoadError {
104 NotFound,
105 Compile(String),
107}
108
109pub struct LoadedProgram<H> {
111 pub compiled: H,
112 pub name: String,
114 pub files: usize,
115 pub size: usize,
116 pub source: Option<PathBuf>,
118 pub recorded: Vec<(String, Option<crate::module::SourceFile>)>,
121}
122
123pub struct FoundClass<C> {
126 pub code: C,
127 pub sources: Vec<String>,
128}
129
130pub trait Loader<H> {
132 fn program(&mut self, name: &str) -> Result<LoadedProgram<H>, LoadError>;
134
135 fn holder(&self, entry: &str) -> Option<String>;
137
138 fn entry(program: &H, name: &str) -> Option<usize>;
140
141 fn shape(program: &H) -> (usize, usize);
143
144 fn nested(program: &H) -> &[String];
146
147 fn source(program: &H, file: usize) -> Option<String>;
149
150 fn class(&mut self, external: &str) -> Result<Option<FoundClass<Rc<ClassCode<H>>>>, String>;
153
154 fn mapset(&mut self, name: &str) -> Option<Result<crate::bms::Mapset, String>>;
157}
158
159pub trait UnitHost<'w> {
162 type Program: Clone;
163 type Loader: Loader<Self::Program>;
164 fn unit(&mut self) -> &mut RunUnit<'w, Self::Program, Self::Loader>;
165}
166
167#[derive(Clone, Copy, Debug)]
168pub enum Clock {
169 System,
170 Fixed(i64, u32),
172}
173
174pub enum Event<'a> {
178 Open { dd: &'a str, mode: OpenMode, path: &'a Path },
179 Close { dd: &'a str, path: &'a Path },
180 Load { program: &'a str, source: Option<&'a Path>, recorded: &'a [(String, Option<crate::module::SourceFile>)] },
182 Paragraph { program: &'a str, name: &'a str, index: usize },
184 Sink { kind: &'static str, file: &'a str, line: u32, operand: &'a str, input: Option<bool> },
189 Statement { file: &'a str, line: u32 },
191}
192
193pub const SINK_KINDS: [&str; 14] = [
196 "cics-dynamic-transfer",
197 "cics-sysid",
198 "connection-target",
199 "dynamic-program-load",
200 "http-header",
201 "log",
202 "os-command",
203 "outbound-host",
204 "outbound-http",
205 "queue-name",
206 "record-key",
207 "record-update",
208 "screen",
209 "web-response",
210];
211
212#[derive(Clone, Debug, PartialEq, Eq)]
215pub enum StatementFilter {
216 All,
217 Lines(HashSet<u32>),
218}
219
220pub type Observer<'w> = Box<dyn FnMut(Event<'_>) + 'w>;
221
222pub const MAX_DEPTH: usize = 100;
224
225#[derive(Default)]
229pub struct Externals {
230 storage: HashMap<(bool, String), (usize, usize)>,
233 files: Vec<Option<Open>>,
234 locked: Vec<bool>,
236 file_names: HashMap<String, usize>,
237}
238
239#[derive(Clone, Copy, Debug, PartialEq, Eq)]
241pub enum Connector {
242 External(usize),
244 Program(usize, usize),
246}
247
248pub const OS_COMMAND_ROUTINES: &[&str] = &["SYSTEM", "C$SYSTEM", "CBL_EXEC_RUN_UNIT", "CBL_GC_HOSTED", "BXPSYSTM"];
251
252pub struct RunUnit<'w, H, L: Loader<H>> {
253 pub mem: Vec<u8>,
254 pub depth: usize,
256 pub programs: Vec<Loaded<H>>,
257 names: HashMap<String, usize>,
258 pub library: L,
259 pub dds: Dds,
260 pub sysin: Option<Box<dyn BufRead + 'w>>,
261 pub clock: Clock,
262 pub out: &'w mut dyn Write,
263 pub err: &'w mut dyn Write,
264 pub cics: Option<crate::cics::Task>,
266 pub eib: usize,
267 pub cics_files: HashMap<String, Open>,
268 pub sql: Option<crate::sql::Session<'w>>,
270 pub le: crate::le::State,
272 pub oo: crate::oo::Objects<Rc<ClassCode<H>>>,
274 pub observer: Option<Observer<'w>>,
276 pub random: Option<u32>,
278 externals: Externals,
279 connectors: HashMap<(usize, usize), Connector>,
281 pub entries: Vec<crate::set::Entry>,
283 pub statements: Option<StatementFilter>,
285 pub taint: Option<Taint>,
287 pub statement_limit: Option<u64>,
289 recent: VecDeque<Started>,
292 overrun: Option<Overrun>,
293 pub(crate) sysin_ended: HashSet<(u16, u32, u32)>,
296 set_aside: Vec<Enclave>,
298}
299
300fn end_file(f: Open, unclosed: bool) -> std::io::Result<()> {
301 if unclosed { f.abandon() } else { f.close() }
302}
303
304const LOOP_WINDOW: usize = 4096;
307
308#[derive(Clone, Copy, PartialEq, Eq)]
310struct Started {
311 program: usize,
312 depth: usize,
313 pos: Pos,
314}
315
316struct Overrun {
319 head: Started,
320 lines: Vec<u32>,
321 grace: usize,
322}
323
324fn loop_of(recent: &VecDeque<Started>, now: Started) -> Overrun {
330 let key = |s: &Started| (s.program, s.depth, s.pos.file, s.pos.line, s.pos.col);
331 let mut seen: HashMap<_, usize> = HashMap::new();
332 for s in recent {
333 *seen.entry(key(s)).or_default() += 1;
334 }
335 let recurring: Vec<&Started> = recent.iter().filter(|s| seen[&key(s)] > 1).collect();
336 let Some(outer) = recurring.iter().map(|s| s.depth).min() else { return Overrun { head: now, lines: Vec::new(), grace: 0 } };
337 let program = recurring.iter().rev().find(|s| s.depth == outer).map_or(now.program, |s| s.program);
338 let frame: Vec<&Started> = recurring.into_iter().filter(|s| s.depth == outer && s.program == program).collect();
339 let file = frame.iter().map(|s| s.pos.file).min().unwrap_or(now.pos.file);
340 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);
341 let mut lines: Vec<u32> = frame.iter().filter(|s| s.pos.file == file).map(|s| s.pos.line).collect();
342 lines.sort_unstable();
343 lines.dedup();
344 Overrun { head, lines, grace: LOOP_WINDOW }
345}
346
347impl<H, L: Loader<H>> RunUnit<'_, H, L> {
348 pub fn write(&mut self, offset: usize, bytes: &[u8]) {
352 self.mem[offset..offset + bytes.len()].copy_from_slice(bytes);
353 self.mark(offset, bytes.len());
354 }
355
356 pub fn write_input(&mut self, offset: usize, bytes: &[u8]) {
358 self.mem[offset..offset + bytes.len()].copy_from_slice(bytes);
359 self.mark_input(offset, bytes.len(), true);
360 }
361
362 pub fn mark(&mut self, offset: usize, len: usize) {
364 if let Some(t) = self.taint.as_mut() {
365 let pending = t.pending();
366 t.set(offset, len, pending);
367 }
368 }
369
370 pub fn mark_input(&mut self, offset: usize, len: usize, input: bool) {
372 if let Some(t) = self.taint.as_mut() {
373 t.set(offset, len, input);
374 }
375 }
376
377 pub fn taint_read(&mut self, loc: Loc) {
379 if let Some(t) = self.taint.as_mut() {
380 t.read(loc.offset, loc.len);
381 }
382 }
383
384 pub fn writing(&mut self, on: bool) -> bool {
386 self.taint.as_mut().is_some_and(|t| t.writing(on))
387 }
388
389 pub fn statement_starts(&mut self) {
391 if let Some(t) = self.taint.as_mut() {
392 t.start_statement();
393 }
394 }
395
396 pub fn take_input(&mut self) {
398 if let Some(t) = self.taint.as_mut() {
399 t.take_input();
400 }
401 }
402
403 pub fn holds_input(&self, offset: usize, len: usize) -> bool {
405 self.taint.as_ref().is_some_and(|t| t.any(offset, len))
406 }
407
408 pub fn pending(&self) -> bool {
410 self.taint.as_ref().is_some_and(Taint::pending)
411 }
412
413 pub fn resume_statement(&mut self, read_before: bool) {
415 if let Some(t) = self.taint.as_mut() {
416 t.resume_statement(read_before);
417 }
418 }
419
420 pub fn unfollowed(&mut self, what: &'static str) {
422 if let Some(t) = self.taint.as_mut() {
423 t.unfollowed(what);
424 }
425 }
426
427 pub fn input_at_sink(&self) -> Option<bool> {
429 self.taint.as_ref().and_then(Taint::at_sink)
430 }
431}
432
433impl<'w, H: Clone, L: Loader<H>> RunUnit<'w, H, L> {
434 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 {
435 Self {
436 mem: vec![0; RESERVED],
437 depth: 0,
438 programs: Vec::new(),
439 names: HashMap::new(),
440 library,
441 dds,
442 sysin,
443 clock,
444 out,
445 err,
446 cics: None,
447 eib: 0,
448 cics_files: HashMap::new(),
449 sql: None,
450 le: crate::le::State::default(),
451 oo: Default::default(),
452 observer: None,
453 random: None,
454 externals: Externals::default(),
455 connectors: HashMap::new(),
456 entries: Vec::new(),
457 statements: None,
458 taint: None,
459 statement_limit: None,
460 recent: VecDeque::new(),
461 overrun: None,
462 sysin_ended: HashSet::new(),
463 set_aside: Vec::new(),
464 }
465 }
466
467 fn allocate(&mut self, size: usize) -> usize {
468 let base = self.mem.len().div_ceil(ALIGNMENT) * ALIGNMENT;
469 self.mem.resize(base + size, 0);
470 base
471 }
472
473 pub fn add_named(&mut self, compiled: Option<H>, name: String, files: usize, size: usize) -> usize {
475 let base = self.allocate(size);
476 let index = self.programs.len();
477 self.names.insert(name.clone(), index);
478 self.programs.push(Loaded::new(compiled, name, base, size, files));
479 index
480 }
481
482 pub fn begin_cics_run_unit(&mut self) {
487 let programs = self.programs.iter_mut().map(Loaded::set_aside).collect();
488 let (externals, connectors) = (std::mem::take(&mut self.externals), std::mem::take(&mut self.connectors));
489 let return_code = ([self.mem[RETURN_CODE], self.mem[RETURN_CODE + 1]], self.holds_input(RETURN_CODE, 2));
490 self.mem[RETURN_CODE..RETURN_CODE + 2].fill(0);
491 self.mark_input(RETURN_CODE, 2, false);
492 let (heap, random) = (std::mem::take(&mut self.le.heap), self.random.take());
493 self.set_aside.push(Enclave { programs, externals, connectors, heap, random, return_code });
494 }
495
496 pub fn end_cics_run_unit(&mut self, xctl: bool) -> Result<(), String> {
502 let mut closed = Ok(());
503 for program in &mut self.programs {
504 for f in program.files.iter_mut().filter_map(Option::take) {
505 if let Err(e) = f.close() {
506 closed = closed.and(Err(format!("closing a file of {}: {e}", program.name)));
507 }
508 }
509 drop(program.set_aside());
510 }
511 closed = closed.and(self.close_external_files(false));
512 let Some(enclave) = self.set_aside.pop() else { return closed };
513 for (program, held) in self.programs.iter_mut().zip(enclave.programs) {
514 program.restore(held);
515 }
516 (self.externals, self.connectors, self.le.heap, self.random) = (enclave.externals, enclave.connectors, enclave.heap, enclave.random);
517 if !xctl {
518 let (bytes, input) = enclave.return_code;
519 self.mem[RETURN_CODE..RETURN_CODE + 2].copy_from_slice(&bytes);
520 self.mark_input(RETURN_CODE, 2, input);
521 }
522 closed
523 }
524
525 pub fn load_entry(&mut self, name: &str, copy: bool) -> Result<(usize, Option<usize>), LoadError> {
529 if let Some(i) = self.find(name) {
530 return Ok((i, self.programs[i].entry));
531 }
532 let name = name.to_ascii_uppercase();
533 let index = match self.programs.iter().position(|p| p.compiled.as_ref().is_some_and(|c| L::entry(c, &name).is_some())) {
534 Some(i) => i,
535 None => {
536 let holder = self.library.holder(&name);
537 self.load(holder.as_deref().unwrap_or(&name))?
538 }
539 };
540 let Some(compiled) = self.programs[index].compiled.clone() else { return Ok((index, None)) };
541 let Some(entry) = L::entry(&compiled, &name) else { return Ok((index, None)) };
542 if !copy {
543 return Ok((index, Some(entry)));
544 }
545 let (files, size) = L::shape(&compiled);
546 let copy = self.add_named(Some(compiled), name, files, size);
547 self.programs[copy].entry = Some(entry);
548 self.programs[copy].source = self.programs[index].source.clone();
549 Ok((copy, Some(entry)))
550 }
551
552 pub fn push_temporary(&mut self, bytes: &[u8]) -> usize {
555 let at = self.allocate(bytes.len());
556 self.write(at, bytes);
557 at
558 }
559
560 pub fn release_temporaries(&mut self, mark: usize) {
563 let external = self.externals.storage.values().all(|&(at, _)| at < mark);
564 if self.programs.iter().all(|p| !p.placed || p.base + p.size <= mark) && self.le.heap_end() <= mark && external {
565 self.mem.truncate(mark.max(RESERVED));
566 if let Some(t) = self.taint.as_mut() {
567 t.truncate(self.mem.len());
568 }
569 }
570 }
571
572 pub fn enter(&mut self, pos: Pos) -> Result<(), Abend> {
574 if self.depth >= MAX_DEPTH {
575 return Err(Abend::ironwork(format!("PERFORM and CALL nest deeper than {MAX_DEPTH}"), pos));
576 }
577 self.depth += 1;
578 Ok(())
579 }
580
581 pub fn activate(&mut self, me: usize, initial: bool) -> (usize, bool) {
585 if !self.programs[me].placed {
586 let base = self.allocate(self.programs[me].size);
587 (self.programs[me].base, self.programs[me].placed) = (base, true);
588 }
589 let program = &mut self.programs[me];
590 program.active = true;
591 (program.base, !program.initialized || initial)
592 }
593
594 pub fn initialized(&mut self, me: usize) {
596 self.programs[me].initialized = true;
597 self.programs[me].altered.clear();
598 }
599
600 pub fn find(&self, name: &str) -> Option<usize> {
601 if name.bytes().any(|b| b.is_ascii_lowercase()) {
602 return self.names.get(&name.to_ascii_uppercase()).copied();
603 }
604 self.names.get(name).copied()
605 }
606
607 pub fn load(&mut self, name: &str) -> Result<usize, LoadError> {
609 let name = name.to_ascii_uppercase();
610 if let Some(i) = self.find(&name) {
611 return Ok(i);
612 }
613 let loaded = self.library.program(&name)?;
614 self.notify(Event::Load { program: &name, source: loaded.source.as_deref(), recorded: &loaded.recorded });
615 let index = self.add_named(Some(loaded.compiled), loaded.name, loaded.files, loaded.size);
616 self.programs[index].source = loaded.source;
617 Ok(index)
618 }
619
620 pub const fn observed(&self) -> bool {
621 self.observer.is_some()
622 }
623
624 pub fn start_statement(&mut self, program: usize, pos: Pos) -> Result<(), Abend> {
630 let Some(left) = self.statement_limit.as_mut() else { return Ok(()) };
631 let now = Started { program, depth: self.depth, pos };
632 if *left > 0 {
633 *left -= 1;
634 if self.recent.len() == LOOP_WINDOW {
635 self.recent.pop_front();
636 }
637 self.recent.push_back(now);
638 return Ok(());
639 }
640 let overrun = self.overrun.get_or_insert_with(|| loop_of(&self.recent, now));
641 let message = "the run reached its statement limit, as a step past its TIME= ends";
642 if now == overrun.head && !overrun.lines.is_empty() {
643 const SHOWN: usize = 24;
644 let mut lines = overrun.lines.iter().take(SHOWN).map(u32::to_string).collect::<Vec<_>>().join(", ");
645 if overrun.lines.len() > SHOWN {
646 lines += &format!(" and {} more", overrun.lines.len() - SHOWN);
647 }
648 return Err(Abend { code: crate::abend::AbendCode::TimeLimit, message: format!("{message}, in the loop over lines {lines}"), pos, file: None });
649 }
650 if overrun.grace == 0 {
651 return Err(Abend { code: crate::abend::AbendCode::TimeLimit, message: message.into(), pos, file: None });
652 }
653 overrun.grace -= 1;
654 Ok(())
655 }
656
657 pub fn traces(&self, line: u32) -> bool {
658 match &self.statements {
659 None => false,
660 Some(StatementFilter::All) => self.observer.is_some(),
661 Some(StatementFilter::Lines(lines)) => self.observer.is_some() && lines.contains(&line),
662 }
663 }
664
665 pub fn notify(&mut self, event: Event<'_>) {
666 if let Event::Sink { kind, .. } = &event {
667 debug_assert!(SINK_KINDS.contains(kind), "the sink kind {kind} is not in rt::unit::SINK_KINDS");
668 }
669 if let Some(observer) = self.observer.as_mut() {
670 observer(event);
671 }
672 }
673
674 pub fn close_all(&mut self, unclosed: bool) -> Result<(), String> {
679 for program in &mut self.programs {
680 for f in program.files.iter_mut().filter_map(Option::take) {
681 end_file(f, unclosed).map_err(|e| format!("closing a file of {}: {e}", program.name))?;
682 }
683 }
684 self.close_external_files(unclosed)
685 }
686
687 fn close_external_files(&mut self, unclosed: bool) -> Result<(), String> {
689 let mut closed = Ok(());
690 for (name, &k) in &self.externals.file_names {
691 if let Some(f) = self.externals.files[k].take()
692 && let Err(e) = end_file(f, unclosed)
693 {
694 closed = closed.and(Err(format!("closing EXTERNAL file {name}: {e}")));
695 }
696 }
697 closed
698 }
699
700 pub fn external(&mut self, name: &str, file: bool, size: usize, dialect: Dialect) -> Result<usize, String> {
705 let key = (file, name.to_owned());
706 if let Some(&(at, had)) = self.externals.storage.get(&key) {
707 return if had == size {
708 Ok(at)
709 } else if size < had && !file && dialect == Dialect::Gnucobol {
710 let _ = writeln!(self.err, "ironwork: EXTERNAL record {name} has {had} bytes in the run unit, and this program describes {size}");
711 Ok(at)
712 } else {
713 let what = if file { "the record area of EXTERNAL file" } else { "EXTERNAL record" };
714 Err(format!("{what} {name} has {had} bytes in the run unit, and this program describes {size}"))
715 };
716 }
717 let at = self.allocate(size);
718 self.externals.storage.insert(key, (at, size));
719 Ok(at)
720 }
721
722 pub fn external_file(&mut self, name: &str) -> Connector {
724 let (files, locked) = (&mut self.externals.files, &mut self.externals.locked);
725 let k = *self.externals.file_names.entry(name.to_owned()).or_insert_with(|| {
726 files.push(None);
727 locked.push(false);
728 files.len() - 1
729 });
730 Connector::External(k)
731 }
732
733 pub fn connect(&mut self, me: usize, k: usize, to: Connector) {
735 self.connectors.insert((me, k), to);
736 }
737
738 fn connector(&self, mut me: usize, mut k: usize) -> Option<Connector> {
739 let mut to = None;
740 while let Some(&c) = self.connectors.get(&(me, k)) {
741 to = Some(c);
742 match c {
743 Connector::External(_) => break,
744 Connector::Program(p, j) => (me, k) = (p, j),
745 }
746 }
747 to
748 }
749
750 pub fn file(&mut self, me: usize, k: usize) -> &mut Option<Open> {
752 match self.connector(me, k) {
753 None => &mut self.programs[me].files[k],
754 Some(Connector::External(e)) => &mut self.externals.files[e],
755 Some(Connector::Program(p, j)) => &mut self.programs[p].files[j],
756 }
757 }
758
759 pub fn locked(&mut self, me: usize, k: usize) -> &mut bool {
761 match self.connector(me, k) {
762 None => &mut self.programs[me].locked[k],
763 Some(Connector::External(e)) => &mut self.externals.locked[e],
764 Some(Connector::Program(p, j)) => &mut self.programs[p].locked[j],
765 }
766 }
767
768 pub fn file_ref(&self, me: usize, k: usize) -> &Option<Open> {
769 match self.connector(me, k) {
770 None => &self.programs[me].files[k],
771 Some(Connector::External(e)) => &self.externals.files[e],
772 Some(Connector::Program(p, j)) => &self.programs[p].files[j],
773 }
774 }
775
776 pub fn return_code(&self) -> i16 {
777 i16::from_be_bytes([self.mem[RETURN_CODE], self.mem[RETURN_CODE + 1]])
778 }
779
780 pub fn now(&self) -> (i64, u32) {
782 match self.clock {
783 Clock::Fixed(s, h) => (s, h),
784 Clock::System => {
785 let d = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default();
786 (d.as_secs() as i64, d.subsec_millis() / 10)
787 }
788 }
789 }
790}