Skip to main content

ironwork_exec/
oo.rs

1//! Object-oriented COBOL, compiled and checked. A class written in COBOL becomes code the
2//! interpreter runs within the run unit: each method is compiled as a program whose LINKAGE SECTION
3//! ends with the records of the OBJECT or FACTORY WORKING-STORAGE it works on, and each
4//! INVOKE finds its method by name and Java signature, as the JNI does. Running is in
5//! machine/oo.rs; a Java class is checked here and never run.
6
7use crate::layout::{Kind, Layout, Resolved};
8use crate::{Check, Compiled};
9use numeric::Options;
10use numeric::assumptions::{OO_OPTIONS_REQUIRED, OO_OPTIONS_SEVERITY, REFERENCES_KEPT};
11use std::collections::{HashMap, HashSet};
12use std::rc::Rc;
13use syntax::ast::*;
14use syntax::{Error, Pos};
15
16pub const JAVA_LANG_OBJECT: &str = "java.lang.Object";
17
18/// Objects a run unit may create before it abends; they are never freed.
19pub const MAX_OBJECTS: usize = 1_000_000;
20/// References a run unit may make before it abends; each is kept, so that an expired one can say
21/// where it expired.
22pub const MAX_REFERENCES: usize = 1 << 23;
23/// Run-unit memory past which creating objects and classes abends.
24pub const MAX_MEMORY: usize = 1 << 30;
25
26/// The frame a global reference belongs to.
27const GLOBAL: u32 = u32::MAX;
28
29/// The run unit's classes and objects, the references to them, and the JNI environment once a
30/// program uses JNIENVPTR.
31#[derive(Default)]
32pub struct Objects {
33    pub(crate) classes: Vec<LoadedClass>,
34    pub(crate) objects: Vec<Instance>,
35    pub(crate) jni: Option<usize>,
36    /// Every reference made, local or global; an object reference holds a reference's position
37    /// plus one, and positions are never reused.
38    references: Vec<Reference>,
39    /// Local reference frames above the run unit's own, innermost last.
40    frames: Vec<LocalFrame>,
41    serials: u32,
42    /// How references were made and how they expired, each told once.
43    events: Vec<String>,
44    event_index: HashMap<String, u32>,
45}
46
47struct Reference {
48    /// The object's position plus one.
49    object: u32,
50    /// The serial of the local frame it belongs to (0 for the run unit's own), or GLOBAL.
51    frame: u32,
52    made: u32,
53    /// The event that freed it, plus one; 0 while it is valid.
54    freed: u32,
55}
56
57struct LocalFrame {
58    serial: u32,
59    /// The first reference made after the frame was pushed.
60    first: u32,
61    /// Pushed by PushLocalFrame, not by a method's invocation.
62    pushed: bool,
63}
64
65/// What an object reference's four bytes name.
66pub(crate) enum Referent {
67    Null,
68    Object(u32),
69    /// A reference that was freed, and the message that says how.
70    Expired(String),
71    Unknown,
72}
73
74pub(crate) struct LoadedClass {
75    pub external: String,
76    /// None for a Java class, java.lang.Object among them.
77    pub code: Option<Rc<ClassCode>>,
78    pub parent: Option<usize>,
79    /// The factory object's reference.
80    pub factory_object: u32,
81    /// Loaded storage of the factory data, and of each method's WORKING-STORAGE once it has run.
82    pub factory_data: Option<usize>,
83    pub methods: Vec<Option<usize>>,
84}
85
86pub(crate) struct Instance {
87    pub class: usize,
88    /// A factory object, on which INVOKE runs factory methods.
89    pub factory: bool,
90    /// The loaded storage of each COBOL class's instance data, by class.
91    pub parts: Vec<(usize, usize)>,
92}
93
94impl Objects {
95    pub(crate) fn find(&self, external: &str) -> Option<usize> {
96        self.classes.iter().position(|c| c.external == external)
97    }
98
99    /// An object by its position plus one.
100    pub(crate) fn object(&self, id: u32) -> Option<&Instance> {
101        (id as usize).checked_sub(1).and_then(|i| self.objects.get(i))
102    }
103
104    pub(crate) fn add_object(&mut self, object: Instance) -> Result<u32, String> {
105        if self.objects.len() >= MAX_OBJECTS {
106            return Err(format!("the run unit created more than {MAX_OBJECTS} objects, which ironwork for COBOL never frees"));
107        }
108        self.objects.push(object);
109        Ok(self.objects.len() as u32)
110    }
111
112    /// An event's number, the same for the same words.
113    pub(crate) fn event(&mut self, text: String) -> u32 {
114        if let Some(&n) = self.event_index.get(&text) {
115            return n;
116        }
117        let n = self.events.len() as u32;
118        self.event_index.insert(text.clone(), n);
119        self.events.push(text);
120        n
121    }
122
123    pub(crate) fn told(&self, event: u32) -> &str {
124        self.events.get(event as usize).map_or("", String::as_str)
125    }
126
127    fn make(&mut self, object: u32, frame: u32, made: u32) -> Result<u32, String> {
128        if self.references.len() >= MAX_REFERENCES {
129            return Err(format!(
130                "the run unit made more than {MAX_REFERENCES} object references, which ironwork for COBOL keeps so that an expired one can say where it expired (see {REFERENCES_KEPT})"
131            ));
132        }
133        self.references.push(Reference { object, frame, made, freed: 0 });
134        Ok(self.references.len() as u32)
135    }
136
137    /// A local reference in the innermost frame.
138    pub(crate) fn local(&mut self, object: u32, made: u32) -> Result<u32, String> {
139        let frame = self.frames.last().map_or(0, |f| f.serial);
140        self.make(object, frame, made)
141    }
142
143    /// A local reference in the frame with this serial.
144    pub(crate) fn local_in(&mut self, frame: u32, object: u32, made: u32) -> Result<u32, String> {
145        self.make(object, frame, made)
146    }
147
148    pub(crate) fn global(&mut self, object: u32, made: u32) -> Result<u32, String> {
149        self.make(object, GLOBAL, made)
150    }
151
152    /// A new local frame, for a method's invocation or PushLocalFrame: its depth and serial.
153    pub(crate) fn push_frame(&mut self, pushed: bool) -> (usize, u32) {
154        self.serials = if self.serials >= GLOBAL - 1 { 1 } else { self.serials + 1 };
155        self.frames.push(LocalFrame { serial: self.serials, first: self.references.len() as u32, pushed });
156        (self.frames.len() - 1, self.serials)
157    }
158
159    /// Pops the frame at `depth` and every frame above it, freeing their local references.
160    pub(crate) fn pop_frames(&mut self, depth: usize, event: u32) {
161        let Some(first) = self.frames.get(depth).map(|f| f.first as usize) else { return };
162        let serials: Vec<u32> = self.frames.drain(depth..).map(|f| f.serial).collect();
163        for r in self.references.iter_mut().skip(first) {
164            if r.freed == 0 && serials.contains(&r.frame) {
165                r.freed = event + 1;
166            }
167        }
168    }
169
170    /// The depth of the innermost frame when PushLocalFrame pushed it.
171    pub(crate) fn pushed_frame(&self) -> Option<usize> {
172        self.frames.last().filter(|f| f.pushed).map(|_| self.frames.len() - 1)
173    }
174
175    pub(crate) fn referent(&self, value: u32) -> Referent {
176        if value == 0 {
177            return Referent::Null;
178        }
179        match self.references.get(value as usize - 1) {
180            None => Referent::Unknown,
181            Some(r) if r.freed == 0 => Referent::Object(r.object),
182            Some(r) => Referent::Expired(format!("{}; it {}", self.described(r), self.told(r.freed - 1))),
183        }
184    }
185
186    pub(crate) fn is_global(&self, value: u32) -> bool {
187        (value as usize).checked_sub(1).and_then(|i| self.references.get(i)).is_some_and(|r| r.frame == GLOBAL)
188    }
189
190    /// Frees a valid reference, as DeleteLocalRef or DeleteGlobalRef does.
191    pub(crate) fn free(&mut self, value: u32, event: u32) {
192        if let Some(r) = (value as usize).checked_sub(1).and_then(|i| self.references.get_mut(i)) {
193            r.freed = event + 1;
194        }
195    }
196
197    /// `a local reference to an Account object, made by ...`
198    fn described(&self, r: &Reference) -> String {
199        let kind = if r.frame == GLOBAL { "global" } else { "local" };
200        let object = match self.object(r.object) {
201            Some(o) if o.factory => format!("the factory object of {}", self.classes[o.class].external),
202            Some(o) => {
203                let class = &self.classes[o.class].external;
204                let article = if class.starts_with(['A', 'E', 'I', 'O', 'U', 'a', 'e', 'i', 'o', 'u']) { "an" } else { "a" };
205                format!("{article} {class} object")
206            }
207            None => "an object".into(),
208        };
209        format!("a {kind} reference to {object}, {}", self.told(r.made))
210    }
211}
212
213/// A COBOL class, compiled.
214pub(crate) struct ClassCode {
215    /// The external name of the class it inherits.
216    pub parent: String,
217    pub factory: Option<Part>,
218    pub object: Option<Part>,
219    pub methods: Vec<MethodCode>,
220}
221
222/// FACTORY or OBJECT WORKING-STORAGE, laid out as a program's, and where each record starts in it.
223pub(crate) struct Part {
224    pub data: Rc<Compiled>,
225    pub records: Vec<u32>,
226}
227
228pub(crate) struct MethodCode {
229    pub name: String,
230    pub factory: bool,
231    /// Java types of the parameters and of the returned item, as a JNI signature spells them.
232    pub params: Vec<String>,
233    pub returns: Option<String>,
234    pub code: Rc<Compiled>,
235    /// LINKAGE records the method declares; the records of its paragraph's data follow them.
236    pub own_records: usize,
237}
238
239/// A class definition's source compiles when each of its methods does.
240pub(crate) fn compile_class_definition(program: Program, flags: &[String]) -> Result<Compiled, Vec<Error>> {
241    let mut shell = program.clone();
242    shell.oo = None;
243    let mut compiled = crate::compile_program(shell, flags, false)?;
244    let (_, diagnostics) = class_code(&program, flags)?;
245    compiled.diagnostics.extend(diagnostics);
246    compiled.program = program;
247    Ok(compiled)
248}
249
250/// A class definition, INVOKE or an object reference; the JNI reached through JNIENVPTR alone is
251/// not object-oriented syntax.
252fn object_oriented(program: &Program) -> bool {
253    let files = program.files.iter().flat_map(|f| f.records.iter());
254    let mut data = program.working_storage.iter().chain(&program.local_storage).chain(&program.linkage).chain(files);
255    let mut invoke = false;
256    for p in &program.paragraphs {
257        each(&p.statements, &mut |s| invoke |= matches!(s, Stmt::Invoke(_)));
258    }
259    program.oo.as_deref().is_some_and(|o| o.class().is_some()) || invoke || data.any(|e| e.usage == Some(Usage::ObjectReference))
260}
261
262/// IBM's rules for the options a program is compiled with (see [`OO_OPTIONS_REQUIRED`]): object-
263/// oriented syntax needs THREAD, DLL, RENT and DBCS, NORENT conflicts with THREAD and DLL, and under
264/// THREAD a program is RECURSIVE, not INITIAL, contains no program, and SORTs or MERGEs no file. A
265/// method answers only for its statements; its class answers for the options. A missing option and
266/// the NORENT conflict are warnings, the rest errors (see [`OO_OPTIONS_SEVERITY`]).
267pub(crate) fn option_rules(program: &Program, options: &Options, errors: &mut Vec<Error>) {
268    let oo = program.oo.as_deref();
269    let method = oo.and_then(Oo::method).is_some();
270    let who = match oo.and_then(Oo::class) {
271        Some(c) => format!("class {}", c.name),
272        None => format!("program {}", program.id),
273    };
274    if !method {
275        let forcing: Vec<&str> = [(options.thread, "THREAD"), (options.dll, "DLL")].into_iter().filter(|(on, _)| *on).map(|(_, o)| o).collect();
276        if !options.rent && !forcing.is_empty() {
277            errors.push(Error::warning(Pos::default(), format!("NORENT conflicts with {}, which IBM compiles only as RENT (see {OO_OPTIONS_REQUIRED})", forcing.join(" and "))));
278        }
279        if object_oriented(program) {
280            let missing: Vec<&str> = [(options.thread, "THREAD"), (options.dll, "DLL"), (options.rent || !forcing.is_empty(), "RENT"), (options.dbcs, "DBCS")]
281                .into_iter()
282                .filter(|(on, _)| !*on)
283                .map(|(_, o)| o)
284                .collect();
285            if !missing.is_empty() {
286                errors.push(Error::warning(
287                    Pos::default(),
288                    format!(
289                        "{who} uses object-oriented syntax, which IBM compiles only with THREAD, DLL, RENT and DBCS: {} missing from its CBL or PROCESS cards (see {OO_OPTIONS_REQUIRED} and {OO_OPTIONS_SEVERITY})",
290                        missing.join(", ")
291                    ),
292                ));
293            }
294        }
295    }
296    if !options.thread {
297        return;
298    }
299    if !method && oo.and_then(Oo::class).is_none() {
300        if !program.recursive {
301            errors.push(Error::at(Pos::default(), format!("{who} is compiled with THREAD, which requires RECURSIVE in its PROGRAM-ID paragraph")));
302        }
303        if program.initial {
304            errors.push(Error::at(Pos::default(), format!("{who} is INITIAL, which THREAD does not allow")));
305        }
306        if let Some(inner) = program.nested.first() {
307            errors.push(Error::at(Pos::default(), format!("{who} contains program {inner}, and THREAD does not allow nested programs")));
308        }
309    }
310    for p in &program.paragraphs {
311        each(&p.statements, &mut |s| {
312            if let Stmt::Sorting(so) = s
313                && let Sorting::Sort(st) = &**so
314                && (st.merge || program.files.iter().any(|f| f.name == st.subject.name))
315            {
316                let verb = if st.merge { "MERGE" } else { "SORT of a file" };
317                errors.push(Error::at(st.pos, format!("{verb} is not allowed in a program compiled with THREAD")));
318            }
319        });
320    }
321}
322
323pub(crate) fn refuse_to_run(program: &Program) -> Result<(), crate::Abend> {
324    match program.oo.as_ref().and_then(|o| o.class()) {
325        Some(c) => Err(crate::Abend { code: crate::abend::AbendCode::Ironwork, message: format!("{} is a class definition: run a program that uses it", c.name), pos: c.pos }),
326        None => Ok(()),
327    }
328}
329
330/// The external name a class definition defines.
331pub(crate) fn defined_class(program: &Program) -> Option<String> {
332    let oo = program.oo.as_ref()?;
333    let class = oo.class()?;
334    Some(oo.external(&class.name).map_or_else(|| external_class_name(&class.name), str::to_owned))
335}
336
337fn records(entries: &[DataEntry]) -> usize {
338    entries.iter().filter(|e| e.level == 1 || e.level == 77).count()
339}
340
341/// Every name a method declares, which hides a name of its paragraph's data.
342fn declared_names(program: &Program) -> HashSet<String> {
343    let files = program.files.iter().flat_map(|f| f.records.iter());
344    program
345        .working_storage
346        .iter()
347        .chain(&program.local_storage)
348        .chain(&program.linkage)
349        .chain(files)
350        .flat_map(|e| e.name.iter().chain(&e.indexed_by))
351        .cloned()
352        .collect()
353}
354
355/// A class definition's code, and the warnings and informational messages it compiled with.
356pub(crate) fn class_code(program: &Program, flags: &[String]) -> Result<(ClassCode, Vec<Error>), Vec<Error>> {
357    let Some(oo) = program.oo.as_deref() else { return Err(vec![Error::at(Pos::default(), "not a class definition")]) };
358    let Some(def) = oo.class() else { return Err(vec![Error::at(Pos::default(), "not a class definition")]) };
359    let mut errors = Vec::new();
360    let mut options = Options::default();
361    for option in &program.options {
362        options.apply(option).ok();
363    }
364    for flag in flags {
365        options.apply_flag(flag).ok();
366    }
367    option_rules(program, &options, &mut errors);
368    let external = defined_class(program).unwrap_or_default();
369    let parent = match oo.external(&def.inherits) {
370        Some(e) => e.to_owned(),
371        None => {
372            errors.push(Error::at(def.pos, format!("{}: the class a class INHERITS must be named in its REPOSITORY paragraph", def.inherits)));
373            String::new()
374        }
375    };
376    if def.inherits == def.name || parent == external {
377        errors.push(Error::at(def.pos, format!("class {} cannot inherit from itself", def.name)));
378    }
379    let mut base = program.clone();
380    base.oo = None;
381    let mut code = ClassCode { parent, factory: None, object: None, methods: Vec::new() };
382    for (factory, part) in [(true, &def.factory), (false, &def.object)] {
383        let Some(part) = part else { continue };
384        if let Some(e) = part.working_storage.iter().find(|e| !e.indexed_by.is_empty()) {
385            errors.push(Error::at(e.pos, "INDEXED BY in FACTORY or OBJECT data is not supported yet"));
386            continue;
387        }
388        let mut data = base.clone();
389        data.working_storage = part.working_storage.clone();
390        data.oo = Some(Box::new(Oo { repository: oo.repository.clone(), unit: OoUnit::Program }));
391        match crate::compile_program(data, flags, false) {
392            Ok(c) => {
393                let offsets = c.layout.items.iter().filter(|i| i.parent.is_none()).take(records(&part.working_storage)).map(|i| i.offset).collect();
394                let compiled = Part { data: Rc::new(c), records: offsets };
395                if factory { code.factory = Some(compiled) } else { code.object = Some(compiled) }
396            }
397            Err(e) => errors.extend(e),
398        }
399        for m in &part.methods {
400            match method_code(program, m, part, factory, flags) {
401                Ok(mc) => {
402                    errors.extend(mc.code.diagnostics.iter().cloned());
403                    code.methods.push(mc);
404                }
405                Err(e) => errors.extend(e),
406            }
407        }
408    }
409    for (k, m) in code.methods.iter().enumerate() {
410        if let Some(twin) = code.methods[..k].iter().find(|o| o.name == m.name && o.params == m.params) {
411            let kind = |f: bool| if f { "factory" } else { "instance" };
412            let pos = m.code.program.oo.as_deref().and_then(Oo::method).map_or(def.pos, |m| m.pos);
413            errors.push(Error::at(pos, format!("{} method \"{}\" has the same parameter types as {} method \"{}\"", kind(m.factory), m.name, kind(twin.factory), twin.name)));
414        }
415    }
416    if crate::refused(&errors, &options) { Err(errors) } else { Ok((code, errors)) }
417}
418
419/// A method compiled with its paragraph's data after its own LINKAGE records; a name the method
420/// declares itself hides the paragraph's.
421fn method_code(class: &Program, method: &Program, part: &ClassPart, factory: bool, flags: &[String]) -> Result<MethodCode, Vec<Error>> {
422    let mut p = method.clone();
423    p.sources = class.sources.clone();
424    p.options = class.options.clone();
425    let own_records = records(&p.linkage);
426    let hidden = declared_names(&p);
427    let hide = |n: &mut String| {
428        if hidden.contains(n) {
429            n.insert(0, ' ');
430        }
431    };
432    for e in &part.working_storage {
433        let mut e = e.clone();
434        e.value = None;
435        e.name.iter_mut().for_each(hide);
436        e.redefines.iter_mut().for_each(hide);
437        e.depending_on.iter_mut().for_each(|r| hide(&mut r.name));
438        p.linkage.push(e);
439    }
440    let pos = method.oo.as_deref().and_then(Oo::method).map_or(part.pos, |m| m.pos);
441    let name = method.id.clone();
442    let compiled = crate::compile(p, flags)?;
443    let mut errors = Vec::new();
444    let layout = &compiled.layout;
445    let oo = compiled.program.oo.as_deref();
446    let own = |n: &str| layout.linkage_roots.iter().take(own_records).copied().find(|&i| layout.items[i].name.as_deref() == Some(n));
447    let mut params = Vec::new();
448    for param in &compiled.program.using {
449        if !param.by_value {
450            errors.push(Error::at(pos, format!("method \"{name}\" receives {} BY REFERENCE: a method's parameters are BY VALUE", param.name)));
451        }
452        match own(&param.name).map(|i| item_type(layout, oo, i)) {
453            Some(Ok(t)) => params.push(t),
454            Some(Err(m)) => errors.push(Error::at(pos, format!("method \"{name}\" parameter {}: {m}", param.name))),
455            None => errors.push(Error::at(pos, format!("method \"{name}\" parameter {}: not a record of the method's own LINKAGE SECTION", param.name))),
456        }
457    }
458    let mut shared = Vec::new();
459    for paragraph in &compiled.program.paragraphs {
460        each(&paragraph.statements, &mut |s| {
461            if let Stmt::Set { set: SetStmt::AddressOf { targets, .. }, pos } = s {
462                for r in targets {
463                    if let Ok(Resolved::Item(i)) = layout.resolve(&r.name, &r.qualifiers, r.pos)
464                        && layout.items[i].linkage.is_some_and(|l| l as usize >= own_records)
465                    {
466                        shared.push(Error::at(*pos, format!("SET ADDRESS OF {}: FACTORY and OBJECT data is WORKING-STORAGE, not LINKAGE", r.name)));
467                    }
468                }
469            }
470        });
471    }
472    errors.extend(shared);
473    let returns = match &compiled.program.returning {
474        None => None,
475        Some(r) => match own(r).map(|i| item_type(layout, oo, i)) {
476            Some(Ok(t)) => Some(t),
477            Some(Err(m)) => {
478                errors.push(Error::at(pos, format!("method \"{name}\" RETURNING {r}: {m}")));
479                None
480            }
481            None => {
482                errors.push(Error::at(pos, format!("method \"{name}\" RETURNING {r}: not a record of the method's own LINKAGE SECTION")));
483                None
484            }
485        },
486    };
487    if !errors.is_empty() {
488        return Err(errors.into_iter().map(|e| e.in_files(&class.sources)).collect());
489    }
490    Ok(MethodCode { name, factory, params, returns, code: Rc::new(compiled), own_records })
491}
492
493/// The Java type of a class-name's objects, as a JNI signature spells it.
494pub(crate) fn class_type(external: &str) -> String {
495    match external {
496        "jstring" | "java.lang.String" => "Ljava/lang/String;".into(),
497        "jbooleanArray" => "[Z".into(),
498        "jbyteArray" => "[B".into(),
499        "jshortArray" => "[S".into(),
500        "jintArray" => "[I".into(),
501        "jlongArray" => "[J".into(),
502        "jcharArray" => "[C".into(),
503        "jfloatArray" => "[F".into(),
504        "jdoubleArray" => "[D".into(),
505        "jobjectArray" => "[Ljava/lang/Object;".into(),
506        e => match e.strip_prefix("jobjectArray:") {
507            Some(element) => format!("[L{};", element.replace('.', "/")),
508            None => format!("L{};", e.replace('.', "/")),
509        },
510    }
511}
512
513/// A PIC X item with exactly the two condition-names IBM's boolean needs.
514fn boolean(layout: &Layout, item: usize) -> bool {
515    let byte = |l: &Literal| match l {
516        Literal::Hex(b) if b.len() == 1 => Some(b[0]),
517        Literal::Figurative(Figurative::LowValue) => Some(0x00),
518        Literal::Figurative(Figurative::HighValue) => Some(0xFF),
519        _ => None,
520    };
521    let conditions: Vec<_> = layout.conditions.iter().filter(|c| c.item == item).collect();
522    let is = |low: u8, high: Option<u8>| {
523        conditions.iter().any(|c| matches!(c.values.as_slice(), [(l, h)] if byte(l) == Some(low) && h.as_ref().and_then(byte) == high))
524    };
525    conditions.len() == 2 && is(0x00, None) && is(0x01, Some(0xFF))
526}
527
528/// The Java type an item passes as, as a JNI signature spells it; a universal object reference
529/// passes as java.lang.Object.
530pub(crate) fn item_type(layout: &Layout, oo: Option<&Oo>, item: usize) -> Result<String, String> {
531    let it = &layout.items[item];
532    Ok(match it.kind {
533        Kind::Alnum { .. } if it.size == 1 => (if boolean(layout, item) { "Z" } else { "B" }).into(),
534        Kind::Binary { digits, scale: 0, signed: true, .. } => (match digits {
535            1..=4 => "S",
536            5..=9 => "I",
537            _ => "J",
538        })
539        .into(),
540        Kind::Float(zarch::hfp::Precision::Short) => "F".into(),
541        Kind::Float(_) => "D".into(),
542        Kind::National if it.size == 2 => "C".into(),
543        Kind::ObjectReference => match &it.object_class {
544            None => format!("L{};", JAVA_LANG_OBJECT.replace('.', "/")),
545            Some(c) => class_type(oo.and_then(|o| o.external(c)).ok_or_else(|| format!("class {c} is not named in the REPOSITORY paragraph"))?),
546        },
547        _ => return Err("not a type Java shares with COBOL: PIC X for byte or boolean, a signed binary integer, COMP-1, COMP-2, PIC N for char, or an object reference".into()),
548    })
549}
550
551/// The Java type of an INVOKE argument: an item, a one-character reference modification, LENGTH OF,
552/// or one of the literals IBM lists.
553pub(crate) fn operand_type(layout: &Layout, oo: Option<&Oo>, op: &Operand) -> Result<String, String> {
554    match op {
555        Operand::Ref(r) => {
556            let Ok(Resolved::Item(i)) = layout.resolve(&r.name, &r.qualifiers, r.pos) else { return Err(format!("{} is not a data item", r.name)) };
557            match &r.refmod {
558                None => item_type(layout, oo, i),
559                Some(_) if layout.items[i].kind == Kind::National => Err("a reference-modified national argument is not supported yet".into()),
560                Some(rm) if matches!(rm.length.as_deref(), Some(Expr::Operand(Operand::Literal(Literal::Number(n)))) if n == "1") => Ok("C".into()),
561                Some(_) => Err("a reference-modified argument must be one character long".into()),
562            }
563        }
564        Operand::LengthOf(_) => Ok("I".into()),
565        Operand::Literal(Literal::Number(t)) if !t.contains('.') && t.trim_start_matches(['+', '-']).len() <= 9 => Ok("I".into()),
566        Operand::Literal(Literal::Figurative(Figurative::Zero)) => Ok("I".into()),
567        Operand::Literal(Literal::Figurative(Figurative::Space | Figurative::Quote | Figurative::HighValue | Figurative::LowValue)) => Ok("B".into()),
568        Operand::Literal(Literal::Alnum(s)) if s.chars().count() == 1 => Ok("B".into()),
569        Operand::Literal(Literal::National(s)) if s.chars().count() == 1 => Ok("C".into()),
570        _ => Err("not an argument Java takes: an item of a type Java shares, LENGTH OF, an integer literal of up to nine digits, or a one-character literal".into()),
571    }
572}
573
574fn is_named(r: &Ref, name: &str) -> bool {
575    r.name == name && r.qualifiers.is_empty() && r.subscripts.is_empty() && r.refmod.is_none()
576}
577
578/// SELF and JNIENVPTR, which are not data items of the program.
579pub(crate) fn special_register(layout: &Layout, r: &Ref) -> bool {
580    (is_named(r, "SELF") || is_named(r, "JNIENVPTR")) && layout.resolve(&r.name, &r.qualifiers, r.pos).is_err()
581}
582
583/// A class-name of the REPOSITORY used where a data item could also be named.
584fn class_name<'o>(layout: &Layout, oo: Option<&'o Oo>, r: &Ref) -> Option<&'o str> {
585    let external = oo?.external(&r.name)?;
586    (r.qualifiers.is_empty() && r.subscripts.is_empty() && r.refmod.is_none() && layout.resolve(&r.name, &r.qualifiers, r.pos).is_err()).then_some(external)
587}
588
589impl Check<'_> {
590    pub(crate) fn invoke(&mut self, i: &Invoke) {
591        let oo = self.program.oo.as_deref();
592        let in_method = oo.and_then(Oo::method).is_some();
593        let err = |m: String| Error::at(i.pos, m);
594        let special = is_named(&i.target, "SELF") || is_named(&i.target, "SUPER");
595        let class = class_name(self.layout, oo, &i.target).is_some();
596        let mut typed = false;
597        if special && self.layout.resolve(&i.target.name, &[], i.pos).is_err() {
598            if !in_method {
599                self.errors.push(err(format!("INVOKE {}: SELF and SUPER can be used only in a method", i.target.name)));
600            }
601        } else if !class {
602            self.reference(&i.target);
603            match self.layout.resolve(&i.target.name, &i.target.qualifiers, i.target.pos) {
604                Ok(Resolved::Item(k)) if self.layout.items[k].kind == Kind::ObjectReference => typed = self.layout.items[k].object_class.is_some(),
605                Ok(_) => self.errors.push(err(format!("INVOKE {}: not an object reference or a class named in the REPOSITORY paragraph", i.target.name))),
606                Err(_) => {}
607            }
608        }
609        match &i.method {
610            InvokeMethod::New => {
611                if !class {
612                    self.errors.push(err(format!("INVOKE {} NEW: NEW takes a class-name from the REPOSITORY paragraph", i.target.name)));
613                }
614                match &i.returning {
615                    None => self.errors.push(err("INVOKE ... NEW needs RETURNING an object reference".into())),
616                    Some(r) if !matches!(self.layout.resolve(&r.name, &r.qualifiers, r.pos), Ok(Resolved::Item(k)) if self.layout.items[k].kind == Kind::ObjectReference) => {
617                        self.errors.push(err(format!("INVOKE ... NEW RETURNING {}: not an object reference", r.name)));
618                    }
619                    Some(_) => {}
620                }
621            }
622            InvokeMethod::Named(name) if name.is_empty() => self.errors.push(err("INVOKE with an empty method name".into())),
623            InvokeMethod::Named(_) => {}
624            InvokeMethod::Identifier(r) => {
625                self.reference(r);
626                if let Ok(Resolved::Item(k)) = self.layout.resolve(&r.name, &r.qualifiers, r.pos)
627                    && !matches!(self.layout.items[k].kind, Kind::Alnum { .. } | Kind::National | Kind::Group)
628                {
629                    self.errors.push(err(format!("INVOKE ... {}: a method name is held in an alphanumeric or national item", r.name)));
630                }
631                if typed {
632                    self.errors.push(err(format!("INVOKE {} {}: a method named by a data item is invoked on a universal object reference", i.target.name, r.name)));
633                }
634            }
635        }
636        for op in &i.using {
637            self.operand(op);
638            if let Err(m) = operand_type(self.layout, oo, op) {
639                self.errors.push(err(format!("INVOKE argument: {m}")));
640            }
641        }
642        if let Some(r) = &i.returning {
643            self.reference(r);
644            if r.refmod.is_some() {
645                self.errors.push(err(format!("INVOKE ... RETURNING {}: not reference-modified", r.name)));
646            }
647            if let (false, Ok(Resolved::Item(k))) = (i.method == InvokeMethod::New, self.layout.resolve(&r.name, &r.qualifiers, r.pos))
648                && let Err(m) = item_type(self.layout, oo, k)
649            {
650                self.errors.push(err(format!("INVOKE ... RETURNING {}: {m}", r.name)));
651            }
652        }
653        self.statements(i.on_exception.as_deref().unwrap_or_default());
654        self.statements(i.not_on_exception.as_deref().unwrap_or_default());
655    }
656}
657
658#[derive(Clone, Copy, PartialEq, Eq)]
659enum Side {
660    Object,
661    ProgramPointer,
662    Null,
663    Other,
664}
665
666/// The rules for object references, SELF, EXIT METHOD and the rest of the object-oriented syntax,
667/// over a program or method that has otherwise checked.
668struct Rules<'a> {
669    layout: &'a Layout,
670    method: bool,
671    uses_oo: bool,
672    errors: &'a mut Vec<Error>,
673}
674
675pub(crate) fn check(layout: &Layout, program: &Program, errors: &mut Vec<Error>) {
676    let oo = program.oo.as_deref();
677    let method = oo.and_then(Oo::method).is_some();
678    let mut rules = Rules { layout, method, uses_oo: oo.is_some_and(|o| !o.repository.is_empty()), errors };
679    for item in &layout.items {
680        if item.kind == Kind::ObjectReference {
681            rules.uses_oo = true;
682        }
683        if let Some(c) = &item.object_class
684            && oo.and_then(|o| o.external(c)).is_none()
685        {
686            rules.errors.push(Error::at(item.pos, format!("OBJECT REFERENCE {c}: the class must be named in the REPOSITORY paragraph")));
687        }
688    }
689    if method && let Some(f) = program.files.first() {
690        rules.errors.push(Error::at(f.pos, "a method's FILE SECTION can define only EXTERNAL files, which ironwork for COBOL does not support yet"));
691    }
692    for p in &program.paragraphs {
693        rules.statements(&p.statements);
694    }
695    // The Report Writer precompiler's code MOVEs each CONTROL and SOURCE item.
696    for report in &program.report_writer.reports {
697        report.controls.iter().for_each(|c| rules.plain(&Operand::Ref(c.clone()), c.pos));
698        for e in report.groups.iter().flat_map(|g| &g.entries) {
699            if let Some(syntax::report::Content::Source(x)) = &e.content {
700                rules.expr(x, e.pos);
701            }
702        }
703    }
704    let exec = program.exec_declarations.iter().map(|b| (b.kind, b.pos)).chain(execs(&program.paragraphs));
705    for (kind, pos) in exec {
706        if method {
707            rules.errors.push(Error::at(pos, "a class definition cannot contain EXEC statements"));
708        } else if kind == ExecKind::Cics && rules.uses_oo {
709            rules.errors.push(Error::at(pos, "a program that uses object-oriented syntax cannot contain EXEC CICS"));
710        }
711    }
712}
713
714fn execs(paragraphs: &[Paragraph]) -> Vec<(ExecKind, Pos)> {
715    let mut out = Vec::new();
716    for p in paragraphs {
717        each(&p.statements, &mut |s| {
718            if let Stmt::Exec(b) = s {
719                out.push((b.kind, b.pos));
720            }
721        });
722    }
723    out
724}
725
726/// Every statement, and every statement nested in it.
727fn each(stmts: &[Stmt], f: &mut dyn FnMut(&Stmt)) {
728    for s in stmts {
729        f(s);
730        for body in bodies(s) {
731            each(body, f);
732        }
733    }
734}
735
736fn opt(o: &Option<Vec<Stmt>>) -> &[Stmt] {
737    o.as_deref().unwrap_or_default()
738}
739
740fn handlers(h: &Handlers) -> [&[Stmt]; 2] {
741    [opt(&h.on), opt(&h.not_on)]
742}
743
744pub(crate) fn bodies(s: &Stmt) -> Vec<&[Stmt]> {
745    match s {
746        Stmt::If { then, otherwise, .. } => vec![then, otherwise],
747        Stmt::PerformInline { body, .. } => vec![body],
748        Stmt::Evaluate { whens, other, .. } => whens.iter().map(|w| w.body.as_slice()).chain([other.as_slice()]).collect(),
749        Stmt::Compute { size_error: Some(se), .. } => vec![&se.on, &se.not_on],
750        Stmt::Arith(a) => a.size_error.iter().flat_map(|se| [se.on.as_slice(), se.not_on.as_slice()]).collect(),
751        Stmt::Read(r) => handlers(&r.at_end).into_iter().chain(handlers(&r.invalid)).collect(),
752        Stmt::Write { invalid, end_of_page, .. } => handlers(invalid).into_iter().chain(handlers(end_of_page)).collect(),
753        Stmt::Rewrite { invalid, .. } | Stmt::Delete { invalid, .. } | Stmt::Start { invalid, .. } => handlers(invalid).to_vec(),
754        Stmt::Call(c) => vec![opt(&c.on_exception), opt(&c.not_on_exception)],
755        Stmt::Invoke(i) => vec![opt(&i.on_exception), opt(&i.not_on_exception)],
756        Stmt::String(st) => vec![opt(&st.on_overflow), opt(&st.not_on_overflow)],
757        Stmt::Unstring(u) => vec![opt(&u.on_overflow), opt(&u.not_on_overflow)],
758        Stmt::Search(se) => se.whens.iter().map(|(_, b)| b.as_slice()).chain([opt(&se.at_end)]).collect(),
759        Stmt::Sorting(so) => match &**so {
760            Sorting::Return { at_end, .. } => handlers(at_end).to_vec(),
761            _ => Vec::new(),
762        },
763        _ => Vec::new(),
764    }
765}
766
767impl Rules<'_> {
768    fn statements(&mut self, stmts: &[Stmt]) {
769        for s in stmts {
770            self.statement(s);
771            for body in bodies(s) {
772                self.statements(body);
773            }
774        }
775    }
776
777    fn statement(&mut self, s: &Stmt) {
778        match s {
779            Stmt::Move { from, to, pos } => {
780                self.plain(from, *pos);
781                to.iter().for_each(|r| self.receiver(r, *pos));
782            }
783            Stmt::Display { items, pos, .. } => items.iter().for_each(|o| self.plain(o, *pos)),
784            Stmt::Compute { targets, expr, pos, .. } => {
785                targets.iter().for_each(|t| self.receiver(&t.r, *pos));
786                self.expr(expr, *pos);
787            }
788            Stmt::Arith(a) => {
789                for (t, e) in &a.computations {
790                    self.receiver(&t.r, a.pos);
791                    self.expr(e, a.pos);
792                }
793            }
794            Stmt::Initialize { targets, pos } | Stmt::Set { set: SetStmt::UpDown { targets, .. }, pos } => targets.iter().for_each(|r| self.receiver(r, *pos)),
795            Stmt::Accept { target, pos, .. } => self.receiver(target, *pos),
796            Stmt::String(st) => {
797                st.sources.iter().for_each(|(o, _)| self.plain(o, st.pos));
798                self.receiver(&st.into, st.pos);
799            }
800            Stmt::Unstring(u) => {
801                self.plain(&Operand::Ref(u.source.clone()), u.pos);
802                u.into.iter().for_each(|i| self.receiver(&i.target, u.pos));
803            }
804            Stmt::Inspect(i) => self.receiver(&i.target, i.pos),
805            Stmt::If { cond, pos, .. } => self.cond(cond, *pos),
806            Stmt::PerformInline { repeat, pos, .. } | Stmt::PerformProc { repeat, pos, .. } => match repeat {
807                Loop::Until { cond, .. } => self.cond(cond, *pos),
808                Loop::Varying { varying, after, .. } => std::iter::once(&**varying).chain(after).for_each(|v| self.cond(&v.until, *pos)),
809                _ => {}
810            },
811            Stmt::Evaluate { subjects, whens, pos, .. } => {
812                for subject in subjects {
813                    match subject {
814                        Subject::Cond(c) => self.cond(c, *pos),
815                        Subject::Expr(e) => self.expr(e, *pos),
816                        Subject::Bool(_) => {}
817                    }
818                }
819                for w in whens {
820                    for object in w.alternatives.iter().flatten() {
821                        match object {
822                            Object::Cond(c) => self.cond(c, *pos),
823                            Object::Value { from, thru, .. } => thru.iter().chain([from]).for_each(|e| self.expr(e, *pos)),
824                            _ => {}
825                        }
826                    }
827                }
828            }
829            Stmt::Search(se) => se.whens.iter().for_each(|(c, _)| self.cond(c, se.pos)),
830            Stmt::Set { set, pos } => self.set(set, *pos),
831            Stmt::ExitMethod { pos } if !self.method => self.errors.push(Error::at(*pos, "EXIT METHOD can be used only in a method")),
832            Stmt::ExitProgram { pos } if self.method => self.errors.push(Error::at(*pos, "EXIT PROGRAM cannot be used in a method: use EXIT METHOD or GOBACK")),
833            Stmt::Invoke(_) => self.uses_oo = true,
834            Stmt::Sorting(so) => match &**so {
835                Sorting::Release { from: Some(op), pos, .. } => self.plain(op, *pos),
836                Sorting::Return { into: Some(r), pos, .. } => self.receiver(r, *pos),
837                _ => {}
838            },
839            _ => {}
840        }
841    }
842
843    fn is_self(&self, r: &Ref) -> bool {
844        is_named(r, "SELF") && self.layout.resolve(&r.name, &[], r.pos).is_err()
845    }
846
847    fn side_of_ref(&mut self, r: &Ref, pos: Pos) -> Side {
848        if self.is_self(r) {
849            if !self.method {
850                self.errors.push(Error::at(pos, "SELF can be used only in a method"));
851            }
852            return Side::Object;
853        }
854        match self.layout.resolve(&r.name, &r.qualifiers, r.pos) {
855            Ok(Resolved::Item(i)) if self.layout.items[i].kind == Kind::ObjectReference => Side::Object,
856            Ok(Resolved::Item(i)) if self.layout.items[i].kind == Kind::ProgramPointer => Side::ProgramPointer,
857            _ => Side::Other,
858        }
859    }
860
861    fn side(&mut self, op: &Operand, pos: Pos) -> Side {
862        match op {
863            Operand::Ref(r) => self.side_of_ref(r, pos),
864            Operand::Literal(Literal::Figurative(Figurative::Null)) => Side::Null,
865            _ => Side::Other,
866        }
867    }
868
869    /// An operand of a statement that takes no object reference or function-pointer.
870    fn plain(&mut self, op: &Operand, pos: Pos) {
871        let what = match self.side(op, pos) {
872            Side::Object => "an object reference",
873            Side::ProgramPointer => "a function-pointer or procedure-pointer",
874            _ => return,
875        };
876        let name = if let Operand::Ref(r) = op { r.name.as_str() } else { "" };
877        self.errors.push(Error::at(pos, format!("{name} is {what}: it can be used only in SET, INVOKE, CALL and a relation condition")));
878    }
879
880    fn receiver(&mut self, r: &Ref, pos: Pos) {
881        if is_named(r, "JNIENVPTR") && self.layout.resolve(&r.name, &[], r.pos).is_err() {
882            self.errors.push(Error::at(pos, "JNIENVPTR cannot receive a value"));
883        } else if self.is_self(r) {
884            self.errors.push(Error::at(pos, "SELF cannot receive a value"));
885        } else {
886            self.plain(&Operand::Ref(r.clone()), pos);
887        }
888    }
889
890    fn expr(&mut self, e: &Expr, pos: Pos) {
891        match e {
892            Expr::Operand(op) => self.plain(op, pos),
893            Expr::Neg(inner) => self.expr(inner, pos),
894            Expr::Bin(a, _, b) => {
895                self.expr(a, pos);
896                self.expr(b, pos);
897            }
898        }
899    }
900
901    fn cond(&mut self, c: &Cond, pos: Pos) {
902        match c {
903            Cond::Rel(a, op, b) => {
904                let side = |r: &mut Self, e: &Expr| match e {
905                    Expr::Operand(o) => r.side(o, pos),
906                    _ => Side::Other,
907                };
908                let (x, y) = (side(self, a), side(self, b));
909                let handle = |s: Side| matches!(s, Side::Object | Side::ProgramPointer);
910                if !handle(x) && !handle(y) {
911                    self.expr(a, pos);
912                    self.expr(b, pos);
913                    return;
914                }
915                if !matches!(op, RelOp::Eq | RelOp::Ne) {
916                    self.errors.push(Error::at(pos, "object references and function-pointers compare only as equal or not equal"));
917                }
918                let fits = |s: Side, other: Side| s == other || s == Side::Null || other == Side::Null;
919                if !fits(x, y) {
920                    self.errors.push(Error::at(pos, "an object reference compares with another object reference, SELF or NULL; a function-pointer with another or NULL"));
921                }
922            }
923            Cond::Class(e, _) => self.expr(e, pos),
924            Cond::Not(inner) => self.cond(inner, pos),
925            Cond::And(a, b) | Cond::Or(a, b) => {
926                self.cond(a, pos);
927                self.cond(b, pos);
928            }
929            Cond::Name(_) | Cond::NameOrRel { .. } => {}
930        }
931    }
932
933    fn set(&mut self, set: &SetStmt, pos: Pos) {
934        let SetStmt::To { targets, value } = set else { return };
935        let value_side = self.side(value, pos);
936        for r in targets {
937            if is_named(r, "JNIENVPTR") && self.layout.resolve(&r.name, &[], r.pos).is_err() {
938                self.errors.push(Error::at(pos, "JNIENVPTR cannot receive a value"));
939                continue;
940            }
941            if self.is_self(r) {
942                self.errors.push(Error::at(pos, "SELF cannot receive a value"));
943                continue;
944            }
945            let message = match (self.side_of_ref(r, pos), value_side) {
946                (Side::Object, Side::Object | Side::Null) | (Side::ProgramPointer, Side::ProgramPointer | Side::Null) => continue,
947                (Side::Object, _) => "an object reference takes another object reference, SELF or NULL",
948                (Side::ProgramPointer, _) => "a function-pointer takes another function-pointer or NULL (SET TO ENTRY is not supported yet)",
949                (_, Side::Object | Side::ProgramPointer) => "an object reference or function-pointer can be set only into its own kind",
950                _ => continue,
951            };
952            self.errors.push(Error::at(pos, format!("SET {} TO: {message}", r.name)));
953        }
954    }
955}
956
957/// Where a COBOL class definition is found: among the programs already read, then in the program
958/// libraries, as a member named with the class's simple name or its full name with periods as
959/// underscores. None means a Java class. See [`numeric::assumptions::CLASS_SEARCH`].
960pub(crate) fn find_class(library: &mut crate::unit::Library, external: &str) -> Result<Option<Program>, String> {
961    if external == JAVA_LANG_OBJECT {
962        return Ok(None);
963    }
964    if let Some(i) = library.programs.iter().position(|p| defined_class(p).as_deref() == Some(external)) {
965        return Ok(Some(library.programs.remove(i)));
966    }
967    let member = |n: &str| !n.is_empty() && n.chars().all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$');
968    let simple = external.rsplit('.').next().unwrap_or(external).to_owned();
969    let mut names = vec![simple, external.replace('.', "_")];
970    names.dedup();
971    names.retain(|n| member(n));
972    for dir in library.dirs.clone() {
973        for name in &names {
974            for variant in [name.clone(), name.to_ascii_lowercase(), name.to_ascii_uppercase()] {
975                for ext in ["", ".cbl", ".CBL", ".cob", ".COB"] {
976                    let path = dir.join(format!("{variant}{ext}"));
977                    if !path.is_file() {
978                        continue;
979                    }
980                    let text = std::fs::read(&path).map(|b| syntax::copy::decode(&b)).map_err(|e| format!("{}: {e}", path.display()))?;
981                    let mut programs = syntax::parse_all_with(&text, &library.copy.with_program(&path)).map_err(|e| format!("class {external} does not compile: {}", e.place(&path.display().to_string())))?;
982                    if defined_class(&programs[0]).as_deref() == Some(external) {
983                        return Ok(Some(programs.remove(0)));
984                    }
985                }
986            }
987        }
988    }
989    Ok(None)
990}