@portll/cobolwork 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -18
- package/STABILITY.md +76 -0
- package/bin/cobolwork.mjs +30 -7
- package/lib/bms.mjs +21 -7
- package/lib/build.mjs +108 -53
- package/lib/capabilities.mjs +9 -10
- package/lib/cics-commands.mjs +501 -9
- package/lib/compliance.mjs +11 -0
- package/lib/consequence.mjs +13 -0
- package/lib/control-workers.mjs +161 -0
- package/lib/control.mjs +52 -3
- package/lib/dataflow.mjs +176 -125
- package/lib/db2/cursor.mjs +15 -0
- package/lib/db2/read.mjs +170 -0
- package/lib/db2/rules.mjs +77 -0
- package/lib/db2/stmt/alter.mjs +473 -0
- package/lib/db2/stmt/grant.mjs +125 -0
- package/lib/db2/stmt/index.mjs +141 -0
- package/lib/db2/stmt/misc.mjs +325 -0
- package/lib/db2/stmt/routine.mjs +564 -0
- package/lib/db2/stmt/storage.mjs +146 -0
- package/lib/db2/stmt/table.mjs +540 -0
- package/lib/db2/stmt/view.mjs +146 -0
- package/lib/diff.mjs +17 -5
- package/lib/evidence/cli.mjs +14 -3
- package/lib/evidence/record.mjs +1 -1
- package/lib/evidence/store.mjs +44 -31
- package/lib/exec-reading.mjs +51 -0
- package/lib/execution.mjs +3 -2
- package/lib/explain.mjs +2 -0
- package/lib/exploitability.mjs +11 -2
- package/lib/hlasm/asm/data.mjs +13 -1
- package/lib/hlasm/asm/sections.mjs +3 -1
- package/lib/hlasm/instr.mjs +25 -0
- package/lib/hlasm/macro/authorization.mjs +24 -4
- package/lib/hlasm/macro/datasets.mjs +30 -17
- package/lib/hlasm/macro/io.mjs +68 -45
- package/lib/hlasm/macro/le.mjs +2 -2
- package/lib/hlasm/macro/operator.mjs +32 -21
- package/lib/hlasm/macro/program.mjs +126 -84
- package/lib/hlasm/macro/recovery.mjs +10 -5
- package/lib/hlasm/macro/storage.mjs +64 -14
- package/lib/hlasm/macro/structured.mjs +1 -1
- package/lib/hlasm/model.mjs +35 -10
- package/lib/hlasm/mvs38.mjs +47 -0
- package/lib/hlasm/operands.mjs +10 -0
- package/lib/hlasm/optable.mjs +61 -0
- package/lib/hlasm/read.mjs +22 -8
- package/lib/hlasm.mjs +44 -7
- package/lib/ims/dli.mjs +37 -0
- package/lib/ims/macro/dbd.mjs +299 -0
- package/lib/ims/macro/psb.mjs +286 -0
- package/lib/ims/model.mjs +149 -0
- package/lib/ims/operands.mjs +23 -0
- package/lib/ims/read.mjs +37 -0
- package/lib/ims/rules.mjs +135 -0
- package/lib/ironwork.mjs +17 -13
- package/lib/kernel/pds-archive.mjs +256 -0
- package/lib/kernel/registry.mjs +14 -8
- package/lib/kernel/shared-pass.mjs +34 -9
- package/lib/kernel/source-tree.mjs +104 -36
- package/lib/kernel/version-key.mjs +17 -0
- package/lib/layout.mjs +26 -31
- package/lib/parser.mjs +136 -17
- package/lib/pli/cursor.mjs +15 -0
- package/lib/pli/expr.mjs +101 -0
- package/lib/pli/include.mjs +82 -0
- package/lib/pli/layout.mjs +125 -0
- package/lib/pli/lex.mjs +198 -0
- package/lib/pli/program.mjs +280 -0
- package/lib/pli/rules/based.mjs +95 -0
- package/lib/pli/rules/conditions.mjs +68 -0
- package/lib/pli/rules/entry.mjs +130 -0
- package/lib/pli/rules/index.mjs +24 -0
- package/lib/pli/rules/preprocessor.mjs +55 -0
- package/lib/pli/statements.mjs +130 -0
- package/lib/pli/stmt/alloc.mjs +45 -0
- package/lib/pli/stmt/assignment.mjs +56 -0
- package/lib/pli/stmt/call.mjs +104 -0
- package/lib/pli/stmt/conditions.mjs +94 -0
- package/lib/pli/stmt/control.mjs +219 -0
- package/lib/pli/stmt/declare.mjs +149 -0
- package/lib/pli/stmt/exec.mjs +55 -0
- package/lib/pli/stmt/io.mjs +239 -0
- package/lib/pli/stmt/misc.mjs +4 -0
- package/lib/pli/stmt/preprocessor.mjs +242 -0
- package/lib/pli/stmt/procedure.mjs +258 -0
- package/lib/pli/stmt/stream.mjs +283 -0
- package/lib/pli/storage.mjs +129 -0
- package/lib/precompile-check.mjs +124 -0
- package/lib/precompile-cics.mjs +7 -3
- package/lib/reach.mjs +11 -2
- package/lib/revision.json +1 -1
- package/lib/sarif.mjs +41 -3
- package/lib/scan.mjs +7 -1
- package/lib/sets/abend.mjs +16 -6
- package/lib/sets/cics.mjs +15 -35
- package/lib/sets/compile.mjs +41 -15
- package/lib/sets/crypto.mjs +5 -3
- package/lib/sets/ddl.mjs +36 -0
- package/lib/sets/flow.mjs +18 -1
- package/lib/sets/hidden.mjs +5 -3
- package/lib/sets/hlasm.mjs +72 -12
- package/lib/sets/ims.mjs +139 -0
- package/lib/sets/log.mjs +6 -6
- package/lib/sets/opaque.mjs +27 -7
- package/lib/sets/pli.mjs +40 -0
- package/lib/sets/recon.mjs +5 -3
- package/lib/sets/secrets.mjs +5 -3
- package/lib/sets/semantics.mjs +3 -0
- package/lib/sets/web.mjs +36 -22
- package/lib/site.mjs +10 -0
- package/lib/sources.mjs +80 -20
- package/lib/statement-cursor.mjs +67 -0
- package/lib/verify.mjs +3 -2
- package/lib/version.mjs +6 -0
- package/package.json +3 -2
- package/rules/compliance-cobit2019.json +432 -1
- package/rules/compliance-dora.json +414 -1
- package/rules/compliance-ffiec.json +414 -1
- package/rules/compliance-nist80053.json +466 -1
- package/rules/hlasm-optables.json +8024 -0
- package/schema/cobolwork-baseline.schema.json +36 -0
- package/schema/cobolwork-build-provenance.schema.json +187 -0
- package/schema/cobolwork-build.schema.json +382 -0
- package/schema/cobolwork-capabilities.schema.json +239 -0
- package/schema/cobolwork-diff.schema.json +217 -0
- package/schema/cobolwork-evidence.schema.json +161 -0
- package/schema/cobolwork-execution.schema.json +53 -0
- package/schema/cobolwork-explain.schema.json +360 -0
- package/schema/cobolwork-finding.schema.json +465 -0
- package/schema/cobolwork-flow.schema.json +465 -0
- package/schema/cobolwork-gate.schema.json +211 -0
- package/schema/cobolwork-inventory.schema.json +206 -0
- package/schema/cobolwork-parse.schema.json +105 -0
- package/schema/cobolwork-reach.schema.json +74 -0
- package/schema/cobolwork-report.schema.json +559 -0
- package/schema/cobolwork-witness.schema.json +107 -0
- package/schema/cobolwork.baseline.schema.json +101 -0
- package/schema/cobolwork.site.schema.json +116 -0
package/lib/compliance.mjs
CHANGED
|
@@ -71,6 +71,17 @@ for (const d of loaded) {
|
|
|
71
71
|
|
|
72
72
|
export const clausesFor = (ruleId) => byRule.get(ruleId) || [];
|
|
73
73
|
|
|
74
|
+
// The clauses each kind of evidence the tools write answers (feed/catalogue.mjs EVIDENCE).
|
|
75
|
+
const byEvidence = new Map();
|
|
76
|
+
for (const d of loaded) {
|
|
77
|
+
for (const r of d.evidence || []) {
|
|
78
|
+
if (!byEvidence.has(r.evidenceId)) byEvidence.set(r.evidenceId, []);
|
|
79
|
+
byEvidence.get(r.evidenceId).push({ framework: r.framework, clause: r.clause, ...(r.title ? { title: r.title } : {}) });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const evidenceClausesFor = (evidenceId) => byEvidence.get(evidenceId) || [];
|
|
84
|
+
|
|
74
85
|
// The whole map, in the shape the report's other rule maps use.
|
|
75
86
|
export function complianceMap(ruleIds) {
|
|
76
87
|
const out = {};
|
package/lib/consequence.mjs
CHANGED
|
@@ -59,6 +59,7 @@ const RULE_CLASSES = {
|
|
|
59
59
|
'cics-transfer-to-variable-program': [],
|
|
60
60
|
// A failure carried on from: what follows depends on the program, and severity decides.
|
|
61
61
|
'cics-condition-ignored': [],
|
|
62
|
+
'pli-error-condition-ignored': [],
|
|
62
63
|
// Disclosure of stored secrets and a weaker check; severity decides.
|
|
63
64
|
'program-checks-stored-password': [],
|
|
64
65
|
'password-case-folded-before-compare': [],
|
|
@@ -134,6 +135,8 @@ const RULE_CLASSES = {
|
|
|
134
135
|
'hlasm-executes-built-instruction': [],
|
|
135
136
|
'hlasm-cross-memory-service': [],
|
|
136
137
|
'hlasm-runtime-module-name': [ESCALATES],
|
|
138
|
+
// A module FETCH loads by a name held in a variable runs with the program's authority.
|
|
139
|
+
'pli-fetch-title-variable': [ESCALATES],
|
|
137
140
|
// Disclosure in transit; severity decides.
|
|
138
141
|
'web-client-opens-cleartext': [],
|
|
139
142
|
'web-receive-length-exceeds-area': [MUTATES],
|
|
@@ -141,6 +144,16 @@ const RULE_CLASSES = {
|
|
|
141
144
|
'icsf-single-length-des-key': [],
|
|
142
145
|
'icsf-weak-hash': [],
|
|
143
146
|
'icsf-fixed-initialization-vector': [],
|
|
147
|
+
// A definition the generation step or the scheduler refuses stops the program; severity decides.
|
|
148
|
+
'ims-senseg-unknown-segment': [],
|
|
149
|
+
'ims-definition-inconsistent': [],
|
|
150
|
+
// An option granted and not used is authority the program holds, not a change any input makes.
|
|
151
|
+
'ims-procopt-broader-than-used': [],
|
|
152
|
+
// A grant decides who holds a privilege, not that anyone uses it; the PUBLIC grant of a write or an
|
|
153
|
+
// authority is crit, and severity decides the rest.
|
|
154
|
+
'db2-grant-to-public': [],
|
|
155
|
+
'db2-grant-with-grant-option': [],
|
|
156
|
+
'db2-system-authority-granted': [],
|
|
144
157
|
};
|
|
145
158
|
|
|
146
159
|
// Coverage and context assert no defect, so they carry no consequence.
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Control analyses of the programs ahead of the one the flow set is summarising, built in worker
|
|
3
|
+
// threads.
|
|
4
|
+
//
|
|
5
|
+
// A worker parses a file as the directory tree does (tree.parseSpec), runs buildControl on each
|
|
6
|
+
// program in it and posts each analysis kept as lib/control-reuse.mjs keeps a copy's: references into
|
|
7
|
+
// the parse as positions in a fixed walk of it. The main thread restores that onto its own parse of the
|
|
8
|
+
// same file, so the analysis it uses is made of its own objects. The scan stays synchronous: waiting
|
|
9
|
+
// for an answer blocks on a counter the workers raise as they post.
|
|
10
|
+
//
|
|
11
|
+
// A file no worker has started when the main thread reaches it is taken back, and the main thread
|
|
12
|
+
// analyses it itself. Only a started one is waited for.
|
|
13
|
+
import { availableParallelism } from 'node:os';
|
|
14
|
+
import { getHeapStatistics } from 'node:v8';
|
|
15
|
+
import { MessageChannel, Worker, isMainThread, parentPort, receiveMessageOnPort, workerData } from 'node:worker_threads';
|
|
16
|
+
import { buildControl, internFacts, namesOf } from './control.mjs';
|
|
17
|
+
import { keep, parseOrder } from './control-reuse.mjs';
|
|
18
|
+
import { parseInDirectory } from './kernel/source-tree.mjs';
|
|
19
|
+
|
|
20
|
+
export const CONTROL_WORKERS_ENV = 'COBOLWORK_CONTROL_WORKERS';
|
|
21
|
+
// Below this much program text, restoring an analysis on the main thread costs about what building
|
|
22
|
+
// it does: on ACAS, 410 programs, moving every one saved 1 s of 3.3 s.
|
|
23
|
+
export const CONTROL_WORKER_MIN_BYTES = 32 * 1024;
|
|
24
|
+
|
|
25
|
+
// A task is the index of a wanted file. Workers claim tasks in order through a shared counter and
|
|
26
|
+
// start one only once the main thread's limit has passed it, so a task never waits behind a busy
|
|
27
|
+
// worker while another is idle.
|
|
28
|
+
const QUEUED = 0;
|
|
29
|
+
const STARTED = 1;
|
|
30
|
+
const CANCELLED = 2;
|
|
31
|
+
const CLAIM = 0;
|
|
32
|
+
const LIMIT = 1;
|
|
33
|
+
|
|
34
|
+
// The program's file and the copybooks its parse resolved, which the kept analysis names by position.
|
|
35
|
+
export const programFiles = (r, file) => [file, ...r.copies.filter((c) => c.path).map((c) => c.path)];
|
|
36
|
+
|
|
37
|
+
// Each program of a parsed file: { value }, its analysis kept; { none }, no procedure division;
|
|
38
|
+
// { threw }, buildControl threw; { local }, the analysis holds something keep() does not carry.
|
|
39
|
+
export function analyseFile(r, file) {
|
|
40
|
+
const files = programFiles(r, file);
|
|
41
|
+
return r.programs.map((prog) => {
|
|
42
|
+
let ctl;
|
|
43
|
+
try { ctl = buildControl(prog, namesOf(prog).resolve); } catch { return { threw: true }; }
|
|
44
|
+
if (!ctl) return { none: true };
|
|
45
|
+
internFacts(ctl.facts);
|
|
46
|
+
const value = keep(ctl, parseOrder(prog), files);
|
|
47
|
+
return value ? { value } : { local: true };
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function serve({ spec, port, signal, control, states, tasks }) {
|
|
52
|
+
for (;;) {
|
|
53
|
+
const t = Atomics.add(control, CLAIM, 1);
|
|
54
|
+
if (t >= tasks.length) return;
|
|
55
|
+
for (let limit = Atomics.load(control, LIMIT); t >= limit; limit = Atomics.load(control, LIMIT)) Atomics.wait(control, LIMIT, limit);
|
|
56
|
+
if (Atomics.compareExchange(states, t, QUEUED, STARTED) !== QUEUED) continue;
|
|
57
|
+
let programs = null;
|
|
58
|
+
try { programs = analyseFile(parseInDirectory(spec, tasks[t]), tasks[t]); } catch { /* the main thread's own parse reports it */ }
|
|
59
|
+
try { port.postMessage({ t, programs }); } catch { port.postMessage({ t, programs: null }); }
|
|
60
|
+
Atomics.add(signal, 0, 1);
|
|
61
|
+
Atomics.notify(signal, 0);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!isMainThread && workerData?.controlWorker) serve(workerData);
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
// How many workers a scan starts: COBOLWORK_CONTROL_WORKERS, else one fewer than the machine's
|
|
69
|
+
// threads, at most four. Each holds one program's parse and analysis at a time, up to 1.3 GB on
|
|
70
|
+
// programs of 15 MB.
|
|
71
|
+
export function controlWorkerCount() {
|
|
72
|
+
const n = Number(process.env[CONTROL_WORKERS_ENV]);
|
|
73
|
+
if (process.env[CONTROL_WORKERS_ENV] !== undefined && Number.isInteger(n) && n >= 0) return n;
|
|
74
|
+
return Math.max(0, Math.min(4, availableParallelism() - 1));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Analyses of `files` built ahead of the main thread, for those `wanted` selects. take(file, index)
|
|
78
|
+
// returns the file's answer, one entry per program, or null where the main thread must analyse it.
|
|
79
|
+
// Files are asked for in order. Workers start at most `ahead` wanted files past the one the main
|
|
80
|
+
// thread is on, since an answer waiting to be taken holds the whole analysis. Twice the workers by
|
|
81
|
+
// default: a large program's analysis takes as long as the main thread's own work on several files,
|
|
82
|
+
// and on cnafbadboy's Batch, scheduled from its measured costs, one more than the workers leaves the
|
|
83
|
+
// main thread waiting a fifth of its loop and twice the workers about 1%.
|
|
84
|
+
//
|
|
85
|
+
// Every failure a worker can catch is answered. One that runs out of heap dies without answering, and
|
|
86
|
+
// nothing the main thread can read while it waits says so. A worker's heap is as large as the main
|
|
87
|
+
// thread's, so a program that exhausts it would exhaust the main thread's too: the wait for a started
|
|
88
|
+
// analysis is long rather than tuned, and past it the main thread analyses the program itself.
|
|
89
|
+
export function controlAhead(spec, files, wanted, { workers, ahead = 2 * workers, waitMs = 30 * 60_000 }) {
|
|
90
|
+
const tasks = [];
|
|
91
|
+
const indexOf = [];
|
|
92
|
+
files.forEach((f, i) => { if (wanted(f)) { tasks.push(f); indexOf.push(i); } });
|
|
93
|
+
const signal = new Int32Array(new SharedArrayBuffer(4));
|
|
94
|
+
const control = new Int32Array(new SharedArrayBuffer(8));
|
|
95
|
+
const states = new Int32Array(new SharedArrayBuffer(4 * Math.max(1, tasks.length)));
|
|
96
|
+
const maxOldGenerationSizeMb = Math.ceil(getHeapStatistics().heap_size_limit / (1024 * 1024));
|
|
97
|
+
const startWorker = () => {
|
|
98
|
+
const { port1, port2 } = new MessageChannel();
|
|
99
|
+
const worker = new Worker(new URL(import.meta.url), {
|
|
100
|
+
workerData: { controlWorker: true, spec, port: port2, signal, control, states, tasks },
|
|
101
|
+
transferList: [port2],
|
|
102
|
+
resourceLimits: { maxOldGenerationSizeMb },
|
|
103
|
+
});
|
|
104
|
+
worker.on('error', () => {});
|
|
105
|
+
worker.unref();
|
|
106
|
+
port1.unref();
|
|
107
|
+
return { worker, port: port1 };
|
|
108
|
+
};
|
|
109
|
+
const pool = tasks.length ? Array.from({ length: workers }, startWorker) : [];
|
|
110
|
+
// The first task the main thread has neither taken nor passed, and the answers from it on.
|
|
111
|
+
let next = 0;
|
|
112
|
+
const answers = new Map();
|
|
113
|
+
const drain = () => {
|
|
114
|
+
for (const w of pool) {
|
|
115
|
+
for (let m = receiveMessageOnPort(w.port); m; m = receiveMessageOnPort(w.port)) {
|
|
116
|
+
if (m.message.t >= next) answers.set(m.message.t, m.message.programs);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
const pass = () => {
|
|
121
|
+
Atomics.compareExchange(states, next, QUEUED, CANCELLED);
|
|
122
|
+
answers.delete(next++);
|
|
123
|
+
};
|
|
124
|
+
const offer = (upTo) => {
|
|
125
|
+
const limit = Math.min(tasks.length, upTo);
|
|
126
|
+
if (limit <= Atomics.load(control, LIMIT)) return;
|
|
127
|
+
Atomics.store(control, LIMIT, limit);
|
|
128
|
+
Atomics.notify(control, LIMIT);
|
|
129
|
+
};
|
|
130
|
+
return {
|
|
131
|
+
take(file, index) {
|
|
132
|
+
drain();
|
|
133
|
+
while (next < tasks.length && indexOf[next] < index) pass();
|
|
134
|
+
if (next >= tasks.length || indexOf[next] !== index) { offer(next + ahead); return null; }
|
|
135
|
+
const t = next;
|
|
136
|
+
const notStarted = Atomics.compareExchange(states, t, QUEUED, CANCELLED) === QUEUED;
|
|
137
|
+
offer(t + 1 + ahead);
|
|
138
|
+
let programs = null;
|
|
139
|
+
if (!notStarted) {
|
|
140
|
+
const deadline = Date.now() + waitMs;
|
|
141
|
+
for (;;) {
|
|
142
|
+
const seen = Atomics.load(signal, 0);
|
|
143
|
+
drain();
|
|
144
|
+
if (answers.has(t) || Date.now() > deadline) break;
|
|
145
|
+
Atomics.wait(signal, 0, seen, 200);
|
|
146
|
+
}
|
|
147
|
+
programs = answers.get(t) ?? null;
|
|
148
|
+
}
|
|
149
|
+
answers.delete(t);
|
|
150
|
+
next = t + 1;
|
|
151
|
+
return programs;
|
|
152
|
+
},
|
|
153
|
+
close() {
|
|
154
|
+
for (let t = next; t < tasks.length; t++) Atomics.compareExchange(states, t, QUEUED, CANCELLED);
|
|
155
|
+
Atomics.store(control, LIMIT, tasks.length);
|
|
156
|
+
Atomics.notify(control, LIMIT);
|
|
157
|
+
for (const w of pool) { w.port.close(); w.worker.terminate(); }
|
|
158
|
+
answers.clear();
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
package/lib/control.mjs
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
// paragraph, for the same reason.
|
|
22
22
|
import { segmentEnd } from './parser.mjs';
|
|
23
23
|
import { parser, attempt, candidates, leaves, storedRange } from './arith.mjs';
|
|
24
|
+
import { execReading } from './exec-reading.mjs';
|
|
24
25
|
|
|
25
26
|
const WORD = (t, u) => t && t.t === 'word' && t.u === u;
|
|
26
27
|
const PHRASED = new Set(['READ', 'WRITE', 'REWRITE', 'DELETE', 'START', 'RETURN', 'CALL', 'COMPUTE', 'ADD', 'SUBTRACT',
|
|
@@ -267,7 +268,7 @@ export function buildControl(prog, resolve, { extra = null } = {}) {
|
|
|
267
268
|
const twice = new Set();
|
|
268
269
|
for (const l of prog.labels) { if (seenLabel.has(l.name)) twice.add(l.name); seenLabel.add(l.name); }
|
|
269
270
|
const sure = (name) => labelNames.has(name) && !twice.has(name);
|
|
270
|
-
const wordsOf = (e) => e
|
|
271
|
+
const wordsOf = (e) => execReading(e).words;
|
|
271
272
|
// Once IGNORE CONDITION has run, a RETURN or XCTL that fails comes back to the next statement, and
|
|
272
273
|
// whether it has run by then is the order of execution, not of the source. A HANDLE CONDITION sends
|
|
273
274
|
// the failure to its label, but which one is in force is as uncertain, so reach assumes it may not.
|
|
@@ -698,17 +699,23 @@ export function buildControl(prog, resolve, { extra = null } = {}) {
|
|
|
698
699
|
}
|
|
699
700
|
}
|
|
700
701
|
} else if (n.e) {
|
|
701
|
-
// A command
|
|
702
|
+
// A command fills the options the precompile table says CICS returns a value in. One the table
|
|
703
|
+
// does not hold may fill what any option is given directly, except what it sends FROM and the
|
|
702
704
|
// data-values it reads. A name inside that argument's own parentheses - a subscript, a
|
|
703
705
|
// reference modification - is read.
|
|
704
706
|
const reads = (n.e.kind === 'CICS' && CICS_READS[n.e.toks[0]?.u]) || [];
|
|
707
|
+
const direction = n.e.kind === 'CICS' ? execReading(n.e).direction : () => null;
|
|
708
|
+
const fills = (option) => {
|
|
709
|
+
const d = direction(option);
|
|
710
|
+
return d ? d === 'receives' || d === 'both' : option !== 'FROM' && !reads.includes(option);
|
|
711
|
+
};
|
|
705
712
|
let depth = 0;
|
|
706
713
|
let option = null;
|
|
707
714
|
for (let i = 0; i < n.e.toks.length; i++) {
|
|
708
715
|
const t = n.e.toks[i];
|
|
709
716
|
if (t.t === 'sep') { depth += t.v === '(' ? 1 : -1; continue; }
|
|
710
717
|
if (depth === 0) { option = t.t === 'word' ? t.u : option; continue; }
|
|
711
|
-
if (depth === 1 && t.t === 'word' &&
|
|
718
|
+
if (depth === 1 && t.t === 'word' && fills(option)) add(t);
|
|
712
719
|
}
|
|
713
720
|
}
|
|
714
721
|
return out;
|
|
@@ -2976,6 +2983,48 @@ export function sparseFacts(bits) {
|
|
|
2976
2983
|
return out;
|
|
2977
2984
|
}
|
|
2978
2985
|
|
|
2986
|
+
// The facts a program keeps per statement: one copy of each distinct set, held as the numbers of the
|
|
2987
|
+
// facts in it, all in one buffer per program. Neighbouring statements mostly hold the same checks, and
|
|
2988
|
+
// few of a program's checks hold at any one statement: 26 large programs kept 1.5 GB of bitsets, 0.4%
|
|
2989
|
+
// of their bits set.
|
|
2990
|
+
export function internFacts(facts) {
|
|
2991
|
+
const byHash = new Map();
|
|
2992
|
+
const kept = [];
|
|
2993
|
+
const keptAt = new Map();
|
|
2994
|
+
for (const [point, bits] of facts) {
|
|
2995
|
+
let h = 2166136261;
|
|
2996
|
+
for (let i = 0; i < bits.length; i++) h = Math.imul(h ^ bits[i], 16777619);
|
|
2997
|
+
let same = byHash.get(h);
|
|
2998
|
+
if (!same) byHash.set(h, same = []);
|
|
2999
|
+
let k = same.find((x) => x.bits.length === bits.length && x.bits.every((v, i) => v === bits[i]));
|
|
3000
|
+
if (!k) { same.push(k = { bits, held: sparseFacts(bits) }); kept.push(k); }
|
|
3001
|
+
keptAt.set(point, k);
|
|
3002
|
+
}
|
|
3003
|
+
let size = 0;
|
|
3004
|
+
for (const k of kept) if (k.held instanceof Uint16Array) size += k.held.length;
|
|
3005
|
+
const pool = new Uint16Array(size);
|
|
3006
|
+
let at = 0;
|
|
3007
|
+
for (const k of kept) {
|
|
3008
|
+
if (!(k.held instanceof Uint16Array)) continue;
|
|
3009
|
+
pool.set(k.held, at);
|
|
3010
|
+
k.held = pool.subarray(at, at += k.held.length);
|
|
3011
|
+
}
|
|
3012
|
+
for (const [point, k] of keptAt) facts.set(point, k.held);
|
|
3013
|
+
return facts;
|
|
3014
|
+
}
|
|
3015
|
+
|
|
3016
|
+
// The name resolution the flow analysis hands buildControl: the parser's reference resolution, then the
|
|
3017
|
+
// first item of the name, since names inside EXEC blocks never pass through the parser's, then an
|
|
3018
|
+
// INDEXED BY name, which is not a data item.
|
|
3019
|
+
export function namesOf(prog) {
|
|
3020
|
+
const byName = new Map();
|
|
3021
|
+
for (const it of prog.items) if (!byName.has(it.name)) byName.set(it.name, it);
|
|
3022
|
+
const itemOfToken = (tok) => (tok ? prog.resolved.get(tok) || (tok.t === 'word' ? byName.get(tok.u) || null : null) : null);
|
|
3023
|
+
const indexNames = new Set(prog.items.flatMap((it) => it.indexNames || []));
|
|
3024
|
+
const resolve = (tok) => itemOfToken(tok) || (tok && tok.t === 'word' && indexNames.has(tok.u) ? { index: tok.u } : null);
|
|
3025
|
+
return { byName, itemOfToken, indexNames, resolve };
|
|
3026
|
+
}
|
|
3027
|
+
|
|
2979
3028
|
// What a node's checks say at a point, given the facts that hold there: 2 when the value it holds is
|
|
2980
3029
|
// safe for the sink, 1 when a check has run on every route to the point, 0 when neither. The check
|
|
2981
3030
|
// named is the one that decided it: one whose outcome alone makes the value safe, if there is one.
|