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; 16] = [
196 "cics-dynamic-transfer",
197 "cics-sysid",
198 "connection-target",
199 "dynamic-file-path",
200 "dynamic-program-load",
201 "dynamic-sql",
202 "http-header",
203 "log",
204 "os-command",
205 "outbound-host",
206 "outbound-http",
207 "queue-name",
208 "record-key",
209 "record-update",
210 "screen",
211 "web-response",
212];
213
214#[derive(Clone, Debug, PartialEq, Eq)]
217pub enum StatementFilter {
218 All,
219 Lines(HashSet<u32>),
220}
221
222pub type Observer<'w> = Box<dyn FnMut(Event<'_>) + 'w>;
223
224pub const MAX_DEPTH: usize = 100;
226
227#[derive(Default)]
231pub struct Externals {
232 storage: HashMap<(bool, String), (usize, usize)>,
235 files: Vec<Option<Open>>,
236 locked: Vec<bool>,
238 file_names: HashMap<String, usize>,
239}
240
241#[derive(Clone, Copy, Debug, PartialEq, Eq)]
243pub enum Connector {
244 External(usize),
246 Program(usize, usize),
248}
249
250pub const OS_COMMAND_ROUTINES: &[&str] = &["SYSTEM", "C$SYSTEM", "CBL_EXEC_RUN_UNIT", "CBL_GC_HOSTED", "BXPSYSTM"];
253
254pub struct RunUnit<'w, H, L: Loader<H>> {
255 pub mem: Vec<u8>,
256 pub depth: usize,
258 pub programs: Vec<Loaded<H>>,
259 names: HashMap<String, usize>,
260 pub library: L,
261 pub dds: Dds,
262 pub sysin: Option<Box<dyn BufRead + 'w>>,
263 pub clock: Clock,
264 pub out: &'w mut dyn Write,
265 pub err: &'w mut dyn Write,
266 pub cics: Option<crate::cics::Task>,
268 pub eib: usize,
269 pub cics_files: HashMap<String, Open>,
270 pub sql: Option<crate::sql::Session<'w>>,
272 pub le: crate::le::State,
274 pub oo: crate::oo::Objects<Rc<ClassCode<H>>>,
276 pub observer: Option<Observer<'w>>,
278 pub random: Option<u32>,
280 externals: Externals,
281 connectors: HashMap<(usize, usize), Connector>,
283 pub entries: Vec<crate::set::Entry>,
285 pub statements: Option<StatementFilter>,
287 pub taint: Option<Taint>,
289 pub statement_limit: Option<u64>,
291 recent: VecDeque<Started>,
294 overrun: Option<Overrun>,
295 pub(crate) sysin_ended: HashSet<(u16, u32, u32)>,
298 set_aside: Vec<Enclave>,
300}
301
302fn end_file(f: Open, unclosed: bool) -> std::io::Result<()> {
303 if unclosed { f.abandon() } else { f.close() }
304}
305
306const LOOP_WINDOW: usize = 4096;
309
310#[derive(Clone, Copy, PartialEq, Eq)]
312struct Started {
313 program: usize,
314 depth: usize,
315 pos: Pos,
316}
317
318struct Overrun {
321 head: Started,
322 lines: Vec<u32>,
323 grace: usize,
324}
325
326fn loop_of(recent: &VecDeque<Started>, now: Started) -> Overrun {
332 let key = |s: &Started| (s.program, s.depth, s.pos.file, s.pos.line, s.pos.col);
333 let mut seen: HashMap<_, usize> = HashMap::new();
334 for s in recent {
335 *seen.entry(key(s)).or_default() += 1;
336 }
337 let recurring: Vec<&Started> = recent.iter().filter(|s| seen[&key(s)] > 1).collect();
338 let Some(outer) = recurring.iter().map(|s| s.depth).min() else { return Overrun { head: now, lines: Vec::new(), grace: 0 } };
339 let program = recurring.iter().rev().find(|s| s.depth == outer).map_or(now.program, |s| s.program);
340 let frame: Vec<&Started> = recurring.into_iter().filter(|s| s.depth == outer && s.program == program).collect();
341 let file = frame.iter().map(|s| s.pos.file).min().unwrap_or(now.pos.file);
342 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);
343 let mut lines: Vec<u32> = frame.iter().filter(|s| s.pos.file == file).map(|s| s.pos.line).collect();
344 lines.sort_unstable();
345 lines.dedup();
346 Overrun { head, lines, grace: LOOP_WINDOW }
347}
348
349impl<H, L: Loader<H>> RunUnit<'_, H, L> {
350 pub fn write(&mut self, offset: usize, bytes: &[u8]) {
354 self.mem[offset..offset + bytes.len()].copy_from_slice(bytes);
355 self.mark(offset, bytes.len());
356 }
357
358 pub fn write_input(&mut self, offset: usize, bytes: &[u8]) {
360 self.mem[offset..offset + bytes.len()].copy_from_slice(bytes);
361 self.mark_input(offset, bytes.len(), true);
362 }
363
364 pub fn mark(&mut self, offset: usize, len: usize) {
366 if let Some(t) = self.taint.as_mut() {
367 let pending = t.pending();
368 t.set(offset, len, pending);
369 }
370 }
371
372 pub fn mark_input(&mut self, offset: usize, len: usize, input: bool) {
374 if let Some(t) = self.taint.as_mut() {
375 t.set(offset, len, input);
376 }
377 }
378
379 pub fn taint_read(&mut self, loc: Loc) {
381 if let Some(t) = self.taint.as_mut() {
382 t.read(loc.offset, loc.len);
383 }
384 }
385
386 pub fn writing(&mut self, on: bool) -> bool {
388 self.taint.as_mut().is_some_and(|t| t.writing(on))
389 }
390
391 pub fn statement_starts(&mut self) {
393 if let Some(t) = self.taint.as_mut() {
394 t.start_statement();
395 }
396 }
397
398 pub fn take_input(&mut self) {
400 if let Some(t) = self.taint.as_mut() {
401 t.take_input();
402 }
403 }
404
405 pub fn holds_input(&self, offset: usize, len: usize) -> bool {
407 self.taint.as_ref().is_some_and(|t| t.any(offset, len))
408 }
409
410 pub fn pending(&self) -> bool {
412 self.taint.as_ref().is_some_and(Taint::pending)
413 }
414
415 pub fn resume_statement(&mut self, read_before: bool) {
417 if let Some(t) = self.taint.as_mut() {
418 t.resume_statement(read_before);
419 }
420 }
421
422 pub fn unfollowed(&mut self, what: &'static str) {
424 if let Some(t) = self.taint.as_mut() {
425 t.unfollowed(what);
426 }
427 }
428
429 pub fn input_at_sink(&self) -> Option<bool> {
431 self.taint.as_ref().and_then(Taint::at_sink)
432 }
433}
434
435impl<'w, H: Clone, L: Loader<H>> RunUnit<'w, H, L> {
436 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 {
437 Self {
438 mem: vec![0; RESERVED],
439 depth: 0,
440 programs: Vec::new(),
441 names: HashMap::new(),
442 library,
443 dds,
444 sysin,
445 clock,
446 out,
447 err,
448 cics: None,
449 eib: 0,
450 cics_files: HashMap::new(),
451 sql: None,
452 le: crate::le::State::default(),
453 oo: Default::default(),
454 observer: None,
455 random: None,
456 externals: Externals::default(),
457 connectors: HashMap::new(),
458 entries: Vec::new(),
459 statements: None,
460 taint: None,
461 statement_limit: None,
462 recent: VecDeque::new(),
463 overrun: None,
464 sysin_ended: HashSet::new(),
465 set_aside: Vec::new(),
466 }
467 }
468
469 fn allocate(&mut self, size: usize) -> usize {
470 let base = self.mem.len().div_ceil(ALIGNMENT) * ALIGNMENT;
471 self.mem.resize(base + size, 0);
472 base
473 }
474
475 pub fn add_named(&mut self, compiled: Option<H>, name: String, files: usize, size: usize) -> usize {
477 let base = self.allocate(size);
478 let index = self.programs.len();
479 self.names.insert(name.clone(), index);
480 self.programs.push(Loaded::new(compiled, name, base, size, files));
481 index
482 }
483
484 pub fn begin_cics_run_unit(&mut self) {
489 let programs = self.programs.iter_mut().map(Loaded::set_aside).collect();
490 let (externals, connectors) = (std::mem::take(&mut self.externals), std::mem::take(&mut self.connectors));
491 let return_code = ([self.mem[RETURN_CODE], self.mem[RETURN_CODE + 1]], self.holds_input(RETURN_CODE, 2));
492 self.mem[RETURN_CODE..RETURN_CODE + 2].fill(0);
493 self.mark_input(RETURN_CODE, 2, false);
494 let (heap, random) = (std::mem::take(&mut self.le.heap), self.random.take());
495 self.set_aside.push(Enclave { programs, externals, connectors, heap, random, return_code });
496 }
497
498 pub fn end_cics_run_unit(&mut self, xctl: bool) -> Result<(), String> {
504 let mut closed = Ok(());
505 for program in &mut self.programs {
506 for f in program.files.iter_mut().filter_map(Option::take) {
507 if let Err(e) = f.close() {
508 closed = closed.and(Err(format!("closing a file of {}: {e}", program.name)));
509 }
510 }
511 drop(program.set_aside());
512 }
513 closed = closed.and(self.close_external_files(false));
514 let Some(enclave) = self.set_aside.pop() else { return closed };
515 for (program, held) in self.programs.iter_mut().zip(enclave.programs) {
516 program.restore(held);
517 }
518 (self.externals, self.connectors, self.le.heap, self.random) = (enclave.externals, enclave.connectors, enclave.heap, enclave.random);
519 if !xctl {
520 let (bytes, input) = enclave.return_code;
521 self.mem[RETURN_CODE..RETURN_CODE + 2].copy_from_slice(&bytes);
522 self.mark_input(RETURN_CODE, 2, input);
523 }
524 closed
525 }
526
527 pub fn load_entry(&mut self, name: &str, copy: bool) -> Result<(usize, Option<usize>), LoadError> {
531 if let Some(i) = self.find(name) {
532 return Ok((i, self.programs[i].entry));
533 }
534 let name = name.to_ascii_uppercase();
535 let index = match self.programs.iter().position(|p| p.compiled.as_ref().is_some_and(|c| L::entry(c, &name).is_some())) {
536 Some(i) => i,
537 None => {
538 let holder = self.library.holder(&name);
539 self.load(holder.as_deref().unwrap_or(&name))?
540 }
541 };
542 let Some(compiled) = self.programs[index].compiled.clone() else { return Ok((index, None)) };
543 let Some(entry) = L::entry(&compiled, &name) else { return Ok((index, None)) };
544 if !copy {
545 return Ok((index, Some(entry)));
546 }
547 let (files, size) = L::shape(&compiled);
548 let copy = self.add_named(Some(compiled), name, files, size);
549 self.programs[copy].entry = Some(entry);
550 self.programs[copy].source = self.programs[index].source.clone();
551 Ok((copy, Some(entry)))
552 }
553
554 pub fn push_temporary(&mut self, bytes: &[u8]) -> usize {
557 let at = self.allocate(bytes.len());
558 self.write(at, bytes);
559 at
560 }
561
562 pub fn release_temporaries(&mut self, mark: usize) {
565 let external = self.externals.storage.values().all(|&(at, _)| at < mark);
566 if self.programs.iter().all(|p| !p.placed || p.base + p.size <= mark) && self.le.heap_end() <= mark && external {
567 self.mem.truncate(mark.max(RESERVED));
568 if let Some(t) = self.taint.as_mut() {
569 t.truncate(self.mem.len());
570 }
571 }
572 }
573
574 pub fn enter(&mut self, pos: Pos) -> Result<(), Abend> {
576 if self.depth >= MAX_DEPTH {
577 return Err(Abend::ironwork(format!("PERFORM and CALL nest deeper than {MAX_DEPTH}"), pos));
578 }
579 self.depth += 1;
580 Ok(())
581 }
582
583 pub fn activate(&mut self, me: usize, initial: bool) -> (usize, bool) {
587 if !self.programs[me].placed {
588 let base = self.allocate(self.programs[me].size);
589 (self.programs[me].base, self.programs[me].placed) = (base, true);
590 }
591 let program = &mut self.programs[me];
592 program.active = true;
593 (program.base, !program.initialized || initial)
594 }
595
596 pub fn initialized(&mut self, me: usize) {
598 self.programs[me].initialized = true;
599 self.programs[me].altered.clear();
600 }
601
602 pub fn find(&self, name: &str) -> Option<usize> {
603 if name.bytes().any(|b| b.is_ascii_lowercase()) {
604 return self.names.get(&name.to_ascii_uppercase()).copied();
605 }
606 self.names.get(name).copied()
607 }
608
609 pub fn load(&mut self, name: &str) -> Result<usize, LoadError> {
611 let name = name.to_ascii_uppercase();
612 if let Some(i) = self.find(&name) {
613 return Ok(i);
614 }
615 let loaded = self.library.program(&name)?;
616 self.notify(Event::Load { program: &name, source: loaded.source.as_deref(), recorded: &loaded.recorded });
617 let index = self.add_named(Some(loaded.compiled), loaded.name, loaded.files, loaded.size);
618 self.programs[index].source = loaded.source;
619 Ok(index)
620 }
621
622 pub const fn observed(&self) -> bool {
623 self.observer.is_some()
624 }
625
626 pub fn start_statement(&mut self, program: usize, pos: Pos) -> Result<(), Abend> {
632 let Some(left) = self.statement_limit.as_mut() else { return Ok(()) };
633 let now = Started { program, depth: self.depth, pos };
634 if *left > 0 {
635 *left -= 1;
636 if self.recent.len() == LOOP_WINDOW {
637 self.recent.pop_front();
638 }
639 self.recent.push_back(now);
640 return Ok(());
641 }
642 let overrun = self.overrun.get_or_insert_with(|| loop_of(&self.recent, now));
643 let message = "the run reached its statement limit, as a step past its TIME= ends";
644 if now == overrun.head && !overrun.lines.is_empty() {
645 const SHOWN: usize = 24;
646 let mut lines = overrun.lines.iter().take(SHOWN).map(u32::to_string).collect::<Vec<_>>().join(", ");
647 if overrun.lines.len() > SHOWN {
648 lines += &format!(" and {} more", overrun.lines.len() - SHOWN);
649 }
650 return Err(Abend { code: crate::abend::AbendCode::TimeLimit, message: format!("{message}, in the loop over lines {lines}"), pos, file: None });
651 }
652 if overrun.grace == 0 {
653 return Err(Abend { code: crate::abend::AbendCode::TimeLimit, message: message.into(), pos, file: None });
654 }
655 overrun.grace -= 1;
656 Ok(())
657 }
658
659 pub fn traces(&self, line: u32) -> bool {
660 match &self.statements {
661 None => false,
662 Some(StatementFilter::All) => self.observer.is_some(),
663 Some(StatementFilter::Lines(lines)) => self.observer.is_some() && lines.contains(&line),
664 }
665 }
666
667 pub fn notify(&mut self, event: Event<'_>) {
668 if let Event::Sink { kind, .. } = &event {
669 debug_assert!(SINK_KINDS.contains(kind), "the sink kind {kind} is not in rt::unit::SINK_KINDS");
670 }
671 if let Some(observer) = self.observer.as_mut() {
672 observer(event);
673 }
674 }
675
676 pub fn close_all(&mut self, unclosed: bool) -> Result<(), String> {
681 for program in &mut self.programs {
682 for f in program.files.iter_mut().filter_map(Option::take) {
683 end_file(f, unclosed).map_err(|e| format!("closing a file of {}: {e}", program.name))?;
684 }
685 }
686 self.close_external_files(unclosed)
687 }
688
689 fn close_external_files(&mut self, unclosed: bool) -> Result<(), String> {
691 let mut closed = Ok(());
692 for (name, &k) in &self.externals.file_names {
693 if let Some(f) = self.externals.files[k].take()
694 && let Err(e) = end_file(f, unclosed)
695 {
696 closed = closed.and(Err(format!("closing EXTERNAL file {name}: {e}")));
697 }
698 }
699 closed
700 }
701
702 pub fn external(&mut self, name: &str, file: bool, size: usize, dialect: Dialect) -> Result<usize, String> {
707 let key = (file, name.to_owned());
708 if let Some(&(at, had)) = self.externals.storage.get(&key) {
709 return if had == size {
710 Ok(at)
711 } else if size < had && !file && dialect == Dialect::Gnucobol {
712 let _ = writeln!(self.err, "ironwork: EXTERNAL record {name} has {had} bytes in the run unit, and this program describes {size}");
713 Ok(at)
714 } else {
715 let what = if file { "the record area of EXTERNAL file" } else { "EXTERNAL record" };
716 Err(format!("{what} {name} has {had} bytes in the run unit, and this program describes {size}"))
717 };
718 }
719 let at = self.allocate(size);
720 self.externals.storage.insert(key, (at, size));
721 Ok(at)
722 }
723
724 pub fn external_file(&mut self, name: &str) -> Connector {
726 let (files, locked) = (&mut self.externals.files, &mut self.externals.locked);
727 let k = *self.externals.file_names.entry(name.to_owned()).or_insert_with(|| {
728 files.push(None);
729 locked.push(false);
730 files.len() - 1
731 });
732 Connector::External(k)
733 }
734
735 pub fn connect(&mut self, me: usize, k: usize, to: Connector) {
737 self.connectors.insert((me, k), to);
738 }
739
740 fn connector(&self, mut me: usize, mut k: usize) -> Option<Connector> {
741 let mut to = None;
742 while let Some(&c) = self.connectors.get(&(me, k)) {
743 to = Some(c);
744 match c {
745 Connector::External(_) => break,
746 Connector::Program(p, j) => (me, k) = (p, j),
747 }
748 }
749 to
750 }
751
752 pub fn file(&mut self, me: usize, k: usize) -> &mut Option<Open> {
754 match self.connector(me, k) {
755 None => &mut self.programs[me].files[k],
756 Some(Connector::External(e)) => &mut self.externals.files[e],
757 Some(Connector::Program(p, j)) => &mut self.programs[p].files[j],
758 }
759 }
760
761 pub fn locked(&mut self, me: usize, k: usize) -> &mut bool {
763 match self.connector(me, k) {
764 None => &mut self.programs[me].locked[k],
765 Some(Connector::External(e)) => &mut self.externals.locked[e],
766 Some(Connector::Program(p, j)) => &mut self.programs[p].locked[j],
767 }
768 }
769
770 pub fn file_ref(&self, me: usize, k: usize) -> &Option<Open> {
771 match self.connector(me, k) {
772 None => &self.programs[me].files[k],
773 Some(Connector::External(e)) => &self.externals.files[e],
774 Some(Connector::Program(p, j)) => &self.programs[p].files[j],
775 }
776 }
777
778 pub fn return_code(&self) -> i16 {
779 i16::from_be_bytes([self.mem[RETURN_CODE], self.mem[RETURN_CODE + 1]])
780 }
781
782 pub fn now(&self) -> (i64, u32) {
784 match self.clock {
785 Clock::Fixed(s, h) => (s, h),
786 Clock::System => {
787 let d = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default();
788 (d.as_secs() as i64, d.subsec_millis() / 10)
789 }
790 }
791 }
792}