@cdxgen/cdxgen 13.2.0 → 13.3.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 +28 -12
- package/bin/cdxgen.js +227 -119
- package/bin/evinse.js +5 -0
- package/bin/sign.js +88 -14
- package/bin/tracebom.js +64 -4
- package/bin/validate.js +29 -7
- package/bin/verify.js +79 -39
- package/data/remediations.json +24 -0
- package/lib/cli/bomAssembly.js +105 -15
- package/lib/cli/cliOptions.js +93 -1
- package/lib/cli/index.js +43 -4
- package/lib/cli/jsBom.js +49 -24
- package/lib/cli/jvmBom.js +46 -12
- package/lib/cli/managedBom.js +112 -41
- package/lib/cli/nativeBom.js +125 -14
- package/lib/core/env.js +5 -6
- package/lib/core/fs.js +174 -6
- package/lib/core/metadataProperties.js +97 -0
- package/lib/core/projectConfig.js +31 -4
- package/lib/ecosystems/bunutils.js +269 -101
- package/lib/ecosystems/caxa.js +434 -2
- package/lib/ecosystems/cmakeResolver.js +66 -0
- package/lib/ecosystems/cppEvidence.js +64 -31
- package/lib/ecosystems/ecosystems.js +19 -8
- package/lib/ecosystems/gradleutils.js +42 -7
- package/lib/ecosystems/jsEvidence.js +19 -20
- package/lib/ecosystems/kosi.js +10 -3
- package/lib/ecosystems/npmutils.js +110 -9
- package/lib/ecosystems/parsers-dotnet.js +51 -10
- package/lib/ecosystems/parsers-go.js +47 -9
- package/lib/ecosystems/parsers-js.js +171 -50
- package/lib/ecosystems/parsers-misc.js +747 -161
- package/lib/ecosystems/parsers-python.js +26 -14
- package/lib/ecosystems/parsers-rust.js +24 -18
- package/lib/ecosystems/rubyutils.js +1426 -166
- package/lib/ecosystems/rusi.js +32 -1
- package/lib/evinser/evinser.js +436 -128
- package/lib/evinser/swiftsem.js +1914 -302
- package/lib/helpers/bomSigner.js +652 -221
- package/lib/inventory/atomUtils.js +568 -31
- package/lib/inventory/cdxrs.js +103 -12
- package/lib/inventory/depsUtils.js +183 -11
- package/lib/inventory/display.js +4 -5
- package/lib/inventory/dosai.js +550 -13
- package/lib/inventory/dynamic.js +39 -1
- package/lib/inventory/evidenceUtils.js +14 -6
- package/lib/inventory/fetchBatch.js +116 -17
- package/lib/inventory/protobom.js +8 -1
- package/lib/inventory/purl.js +152 -3
- package/lib/inventory/sbomDocument.js +2 -70
- package/lib/inventory/source.js +37 -19
- package/lib/inventory/traceRunner.js +210 -21
- package/lib/managers/binary.js +115 -24
- package/lib/managers/docker.js +440 -20
- package/lib/managers/oci.js +39 -1
- package/lib/parsers/cmakeLists.js +452 -0
- package/lib/parsers/largeJson.js +454 -0
- package/lib/parsers/npmrc.js +19 -1
- package/lib/stages/postgen/postgen.js +68 -15
- package/lib/validator/bomValidator.js +6 -16
- package/lib/validator/index.js +48 -10
- package/lib/validator/reporters/console.js +3 -0
- package/lib/validator/reporters/json.js +1 -0
- package/lib/validator/reporters/sarif.js +7 -0
- package/package.json +20 -20
- package/types/lib/cli/bomAssembly.d.ts +2 -1
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +33 -1
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts +55 -0
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/metadataProperties.d.ts +39 -0
- package/types/lib/core/metadataProperties.d.ts.map +1 -0
- package/types/lib/core/projectConfig.d.ts +6 -2
- package/types/lib/core/projectConfig.d.ts.map +1 -1
- package/types/lib/ecosystems/bunutils.d.ts +12 -5
- package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
- package/types/lib/ecosystems/caxa.d.ts +14 -0
- package/types/lib/ecosystems/caxa.d.ts.map +1 -1
- package/types/lib/ecosystems/cmakeResolver.d.ts +23 -0
- package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
- package/types/lib/ecosystems/cppEvidence.d.ts +1 -1
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/golem.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +20 -4
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/jsEvidence.d.ts +5 -2
- package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts +13 -0
- package/types/lib/ecosystems/kosi.d.ts.map +1 -1
- package/types/lib/ecosystems/npmutils.d.ts +42 -0
- package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-go.d.ts +10 -0
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts +10 -4
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts +67 -3
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts +143 -2
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts +13 -0
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +59 -9
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/evinser/swiftsem.d.ts +280 -15
- package/types/lib/evinser/swiftsem.d.ts.map +1 -1
- package/types/lib/helpers/bomSigner.d.ts +106 -13
- package/types/lib/helpers/bomSigner.d.ts.map +1 -1
- package/types/lib/inventory/atomUtils.d.ts +145 -3
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/cdxrs.d.ts +8 -3
- package/types/lib/inventory/cdxrs.d.ts.map +1 -1
- package/types/lib/inventory/depsUtils.d.ts +15 -0
- package/types/lib/inventory/depsUtils.d.ts.map +1 -1
- package/types/lib/inventory/display.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +64 -1
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dynamic.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
- package/types/lib/inventory/plugins.d.ts.map +1 -1
- package/types/lib/inventory/protobom.d.ts.map +1 -1
- package/types/lib/inventory/purl.d.ts +71 -2
- package/types/lib/inventory/purl.d.ts.map +1 -1
- package/types/lib/inventory/sbomDocument.d.ts.map +1 -1
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/traceRunner.d.ts +47 -1
- package/types/lib/inventory/traceRunner.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts +30 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +29 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts +14 -0
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/parsers/cmakeLists.d.ts +76 -0
- package/types/lib/parsers/cmakeLists.d.ts.map +1 -0
- package/types/lib/parsers/largeJson.d.ts +52 -0
- package/types/lib/parsers/largeJson.d.ts.map +1 -0
- package/types/lib/parsers/npmrc.d.ts +5 -0
- package/types/lib/parsers/npmrc.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/index.d.ts +10 -1
- package/types/lib/validator/index.d.ts.map +1 -1
- package/types/lib/validator/reporters/console.d.ts.map +1 -1
- package/types/lib/validator/reporters/json.d.ts.map +1 -1
- package/types/lib/validator/reporters/sarif.d.ts.map +1 -1
|
@@ -1,17 +1,29 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
lstatSync,
|
|
3
|
+
readdirSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
realpathSync,
|
|
6
|
+
statSync,
|
|
7
|
+
} from "node:fs";
|
|
2
8
|
import { tmpdir, totalmem } from "node:os";
|
|
3
9
|
import { delimiter as _delimiter, dirname, join, resolve } from "node:path";
|
|
4
10
|
import process from "node:process";
|
|
5
11
|
|
|
6
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
DEBUG_MODE,
|
|
14
|
+
isSecureMode,
|
|
15
|
+
readEnvironmentVariable,
|
|
16
|
+
} from "../core/activity.js";
|
|
7
17
|
import {
|
|
8
18
|
safeExistsSync,
|
|
9
19
|
safeMkdtempSync,
|
|
10
20
|
safeRmSync,
|
|
11
21
|
safeSpawnSync,
|
|
22
|
+
TIMEOUT_MS,
|
|
12
23
|
} from "../core/fs.js";
|
|
13
24
|
import { TRACE_MODE } from "../core/logger.js";
|
|
14
25
|
import { dirNameStr, isWin } from "../core/paths.js";
|
|
26
|
+
import { readJsonFile } from "../parsers/largeJson.js";
|
|
15
27
|
|
|
16
28
|
const ASTGEN_DEFAULT_IGNORE_DIRS = [
|
|
17
29
|
"venv",
|
|
@@ -57,6 +69,256 @@ export const ATOM_NATIVE_PACKAGES = new Set([
|
|
|
57
69
|
|
|
58
70
|
const ATOM_PHP_LANGUAGES = new Set(["php"]);
|
|
59
71
|
|
|
72
|
+
/** atom `-l` values that parse C/C++ headers without function bodies. */
|
|
73
|
+
const ATOM_HEADER_LANGUAGES = new Set(["h", "hpp", "i"]);
|
|
74
|
+
|
|
75
|
+
/** atom `-l` values handled by the C/C++ frontend. */
|
|
76
|
+
const ATOM_C_LANGUAGES = new Set([
|
|
77
|
+
"c",
|
|
78
|
+
"cpp",
|
|
79
|
+
"c++",
|
|
80
|
+
"newc",
|
|
81
|
+
...ATOM_HEADER_LANGUAGES,
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
const COMPILE_COMMANDS_FILE = "compile_commands.json";
|
|
85
|
+
|
|
86
|
+
/** Build directories, under the scan root, searched for a compilation database. */
|
|
87
|
+
const COMPILE_COMMANDS_BUILD_DIRS = ["build", "out", "builddir"];
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Characters a Windows shell command line would interpret: `&`, `|`, `<` and
|
|
91
|
+
* `>` chain or redirect commands, `%` expands variables even inside quotes,
|
|
92
|
+
* `^` escapes, `!` expands under delayed expansion, and a quote breaks the
|
|
93
|
+
* quoting Node applies to arguments when `shell` is on. A database is often
|
|
94
|
+
* discovered inside the scanned tree, so its path is not trusted input.
|
|
95
|
+
*/
|
|
96
|
+
const UNSAFE_SHELL_CHARS_RE = /[\r\n\t&|<>%^!"]/;
|
|
97
|
+
|
|
98
|
+
function compileCommandsIn(dir) {
|
|
99
|
+
for (const candidate of [
|
|
100
|
+
join(dir, COMPILE_COMMANDS_FILE),
|
|
101
|
+
join(dir, "build", COMPILE_COMMANDS_FILE),
|
|
102
|
+
]) {
|
|
103
|
+
try {
|
|
104
|
+
if (statSync(candidate).isFile()) {
|
|
105
|
+
return candidate;
|
|
106
|
+
}
|
|
107
|
+
} catch (_err) {
|
|
108
|
+
// not there
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Locate the JSON compilation database (`compile_commands.json`) of a C/C++
|
|
116
|
+
* project, which lets atom parse each file with the include paths, macros and
|
|
117
|
+
* language its build uses.
|
|
118
|
+
*
|
|
119
|
+
* An explicit `options.compileCommands` (a file, or a directory holding one
|
|
120
|
+
* directly or under `build/`) wins. Otherwise the scan root, `build/`, `out/`,
|
|
121
|
+
* `builddir/`, `cmake-build-*` and the directory of `options.cmakeCache` are
|
|
122
|
+
* searched. A database can come with the code it describes, and atom asks the
|
|
123
|
+
* GCC or Clang driver it names (from the PATH, or an absolute path outside
|
|
124
|
+
* the project) for its predefined macros, so in secure mode only an explicit
|
|
125
|
+
* database is used.
|
|
126
|
+
*
|
|
127
|
+
* @param {string} src Project scan root
|
|
128
|
+
* @param {Object} options CLI options
|
|
129
|
+
* @returns {string|undefined} Absolute path of the database
|
|
130
|
+
*/
|
|
131
|
+
export function findCompileCommands(src, options = {}) {
|
|
132
|
+
if (options.compileCommands) {
|
|
133
|
+
const requested = resolve(`${options.compileCommands}`);
|
|
134
|
+
let found;
|
|
135
|
+
try {
|
|
136
|
+
found = statSync(requested).isDirectory()
|
|
137
|
+
? compileCommandsIn(requested)
|
|
138
|
+
: requested;
|
|
139
|
+
} catch (_err) {
|
|
140
|
+
found = undefined;
|
|
141
|
+
}
|
|
142
|
+
if (!found) {
|
|
143
|
+
console.warn(
|
|
144
|
+
`No compilation database at ${requested}. Parsing the C/C++ sources without one.`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
return found;
|
|
148
|
+
}
|
|
149
|
+
if (isSecureMode || !src) {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
const root = resolve(src);
|
|
153
|
+
let rootIsDir = false;
|
|
154
|
+
try {
|
|
155
|
+
rootIsDir = statSync(root).isDirectory();
|
|
156
|
+
} catch (_err) {
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
if (!rootIsDir) {
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
const dirs = [root, ...COMPILE_COMMANDS_BUILD_DIRS.map((d) => join(root, d))];
|
|
163
|
+
try {
|
|
164
|
+
dirs.push(
|
|
165
|
+
...readdirSync(root, { withFileTypes: true })
|
|
166
|
+
.filter((e) => e.isDirectory() && e.name.startsWith("cmake-build-"))
|
|
167
|
+
.map((e) => join(root, e.name))
|
|
168
|
+
.sort(),
|
|
169
|
+
);
|
|
170
|
+
} catch (_err) {
|
|
171
|
+
// an unreadable root has no build directories to offer
|
|
172
|
+
}
|
|
173
|
+
if (options.cmakeCache) {
|
|
174
|
+
dirs.push(dirname(resolve(`${options.cmakeCache}`)));
|
|
175
|
+
}
|
|
176
|
+
for (const dir of dirs) {
|
|
177
|
+
const candidate = join(dir, COMPILE_COMMANDS_FILE);
|
|
178
|
+
try {
|
|
179
|
+
if (statSync(candidate).isFile()) {
|
|
180
|
+
return candidate;
|
|
181
|
+
}
|
|
182
|
+
} catch (_err) {
|
|
183
|
+
// not there
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const atomFrontendArgKeysCache = new Map();
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Split an atom command that may carry a leading argument (`node
|
|
193
|
+
* script.js`, a custom `ATOM_CMD`) into its binary and that argument. The
|
|
194
|
+
* first space only splits when it can: a binary under a path with spaces
|
|
195
|
+
* (`C:\Program Files\...\node.exe script.js`) would otherwise be cut
|
|
196
|
+
* mid-path and the shell told to run `C:\Program`. A command name without a
|
|
197
|
+
* path separator (`node script.js`) resolves through the PATH, so it splits
|
|
198
|
+
* at its first space as before; otherwise the first prefix that exists as a
|
|
199
|
+
* file is the binary, and when none does the first space keeps the old
|
|
200
|
+
* behaviour.
|
|
201
|
+
*
|
|
202
|
+
* @param {string} command The atom command, possibly with an argument
|
|
203
|
+
* @returns {[string, string|undefined]} The binary and its leading argument, when present
|
|
204
|
+
*/
|
|
205
|
+
export function splitAtomCommand(command) {
|
|
206
|
+
const firstSpace = `${command}`.indexOf(" ");
|
|
207
|
+
if (firstSpace === -1) {
|
|
208
|
+
return [`${command}`, undefined];
|
|
209
|
+
}
|
|
210
|
+
const head = command.slice(0, firstSpace);
|
|
211
|
+
if (!/[/\\]/.test(head)) {
|
|
212
|
+
return [head, command.slice(firstSpace + 1)];
|
|
213
|
+
}
|
|
214
|
+
let splitAt = firstSpace;
|
|
215
|
+
let index = firstSpace;
|
|
216
|
+
while ((index = command.indexOf(" ", index + 1)) !== -1) {
|
|
217
|
+
try {
|
|
218
|
+
if (statSync(command.slice(0, index)).isFile()) {
|
|
219
|
+
splitAt = index;
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
} catch (_err) {
|
|
223
|
+
// not a file; a later space may still split at the real binary
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return [command.slice(0, splitAt), command.slice(splitAt + 1)];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* The `--frontend-args` keys the installed atom accepts for a language, as
|
|
231
|
+
* `atom --frontend-args-keys -l <language>` lists them. Asked once per atom
|
|
232
|
+
* command and language. An atom that cannot list them (a release without
|
|
233
|
+
* `--frontend-args`, or one that fails to start) accepts none.
|
|
234
|
+
*
|
|
235
|
+
* @param {string} language atom `-l` value
|
|
236
|
+
* @returns {Set<string>} Supported keys
|
|
237
|
+
*/
|
|
238
|
+
export function atomFrontendArgKeys(language) {
|
|
239
|
+
const command = getAtomCommand();
|
|
240
|
+
const cacheKey = `${command}\u0000${language}`;
|
|
241
|
+
if (atomFrontendArgKeysCache.has(cacheKey)) {
|
|
242
|
+
return atomFrontendArgKeysCache.get(cacheKey);
|
|
243
|
+
}
|
|
244
|
+
const [bin, leadingArg] = splitAtomCommand(command);
|
|
245
|
+
const args = ["--frontend-args-keys", "-l", `${language}`];
|
|
246
|
+
if (leadingArg !== undefined) {
|
|
247
|
+
args.unshift(leadingArg);
|
|
248
|
+
}
|
|
249
|
+
const env = { ...process.env };
|
|
250
|
+
if (readEnvironmentVariable("ATOM_JAVA_HOME")) {
|
|
251
|
+
env.JAVA_HOME = readEnvironmentVariable("ATOM_JAVA_HOME");
|
|
252
|
+
}
|
|
253
|
+
const keys = new Set();
|
|
254
|
+
const result = safeSpawnSync(bin, args, {
|
|
255
|
+
shell: isWin,
|
|
256
|
+
env,
|
|
257
|
+
timeout: 120000,
|
|
258
|
+
});
|
|
259
|
+
if (result?.status === 0 && result.stdout) {
|
|
260
|
+
let inTable = false;
|
|
261
|
+
for (const line of `${result.stdout}`.split(/\r?\n/)) {
|
|
262
|
+
if (line.startsWith("----")) {
|
|
263
|
+
inTable = true;
|
|
264
|
+
} else if (inTable && /^[a-z][a-z0-9-]*\s/.test(line)) {
|
|
265
|
+
keys.add(line.split(/\s+/)[0]);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (DEBUG_MODE && !keys.size) {
|
|
270
|
+
console.log(
|
|
271
|
+
`The installed atom did not list its --frontend-args keys for ${language}.`,
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
atomFrontendArgKeysCache.set(cacheKey, keys);
|
|
275
|
+
return keys;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* The `--frontend-args` atom takes for a C/C++ language: the project's
|
|
280
|
+
* compilation database, when there is one and the installed atom reads it
|
|
281
|
+
* (atom 4 and later). An older atom is given the arguments it has always
|
|
282
|
+
* been given, and parses the sources without the database.
|
|
283
|
+
*
|
|
284
|
+
* @param {string} src Project scan root
|
|
285
|
+
* @param {string} language atom `-l` value
|
|
286
|
+
* @param {Object} options CLI options
|
|
287
|
+
* @returns {string[]} Arguments to add to the atom command
|
|
288
|
+
*/
|
|
289
|
+
export function atomCompileCommandsArgs(src, language, options = {}) {
|
|
290
|
+
if (!ATOM_C_LANGUAGES.has(`${language}`.toLowerCase())) {
|
|
291
|
+
return [];
|
|
292
|
+
}
|
|
293
|
+
const database = findCompileCommands(src, options);
|
|
294
|
+
if (!database) {
|
|
295
|
+
return [];
|
|
296
|
+
}
|
|
297
|
+
// --frontend-args separates its key=value pairs with commas
|
|
298
|
+
if (database.includes(",")) {
|
|
299
|
+
console.warn(
|
|
300
|
+
`The compilation database path ${database} contains a comma, which atom cannot take. Parsing the C/C++ sources without it.`,
|
|
301
|
+
);
|
|
302
|
+
return [];
|
|
303
|
+
}
|
|
304
|
+
// The argument reaches a Windows shell command line through `shell: isWin`
|
|
305
|
+
// in executeAtom, and the path is routinely discovered inside the scanned
|
|
306
|
+
// tree, so a database whose path a shell would interpret is never passed on.
|
|
307
|
+
if (UNSAFE_SHELL_CHARS_RE.test(database)) {
|
|
308
|
+
console.warn(
|
|
309
|
+
`The compilation database path ${database} contains characters a shell would interpret. Parsing the C/C++ sources without it.`,
|
|
310
|
+
);
|
|
311
|
+
return [];
|
|
312
|
+
}
|
|
313
|
+
if (!atomFrontendArgKeys(language).has("compile-commands")) {
|
|
314
|
+
return [];
|
|
315
|
+
}
|
|
316
|
+
if (DEBUG_MODE) {
|
|
317
|
+
console.log(`Passing the compilation database ${database} to atom.`);
|
|
318
|
+
}
|
|
319
|
+
return ["--frontend-args", `compile-commands=${database}`];
|
|
320
|
+
}
|
|
321
|
+
|
|
60
322
|
// Absolute ceiling on atom's heap. Past this point a larger heap buys a
|
|
61
323
|
// collector that runs less often rather than an analysis that succeeds where it
|
|
62
324
|
// otherwise would not, while the reservation itself is what pushes a build
|
|
@@ -610,12 +872,38 @@ export function resolveAtomProvider(opts = {}) {
|
|
|
610
872
|
}
|
|
611
873
|
|
|
612
874
|
/**
|
|
613
|
-
* Returns `"native"` or `"jar"` for the
|
|
614
|
-
*
|
|
615
|
-
*
|
|
875
|
+
* Returns `"native"` or `"jar"` for the atom provider that will actually run.
|
|
876
|
+
*
|
|
877
|
+
* The platform decides which provider atom prefers, but the dispatcher falls
|
|
878
|
+
* back to the jar package when the platform's native package is not installed
|
|
879
|
+
* (optional dependencies skipped, a failed download). The two take the heap
|
|
880
|
+
* ceiling differently, and a native-image runtime option on a jar run reaches
|
|
881
|
+
* atom as an unknown argument that fails every slice. So the kind follows what
|
|
882
|
+
* is installed where cdxgen can see it: the native binary means native, the jar
|
|
883
|
+
* package without it means jar. With neither in view (a custom `ATOM_CMD`, an
|
|
884
|
+
* install elsewhere) the platform's kind stands.
|
|
885
|
+
*
|
|
886
|
+
* Also used to gate Java/JDK advice, so users on the five native platforms are
|
|
887
|
+
* not told to install a JDK for a failure that has nothing to do with Java.
|
|
616
888
|
*/
|
|
617
889
|
export function atomProviderKind() {
|
|
618
|
-
|
|
890
|
+
const { kind } = resolveAtomProvider();
|
|
891
|
+
if (kind !== "native" || readEnvironmentVariable("ATOM_CMD")) {
|
|
892
|
+
return kind;
|
|
893
|
+
}
|
|
894
|
+
const atomHome = readEnvironmentVariable("ATOM_HOME");
|
|
895
|
+
if (atomHome) {
|
|
896
|
+
// A jar install's ATOM_HOME is its plugins/ directory, which carries lib/;
|
|
897
|
+
// a native sub-package directory does not.
|
|
898
|
+
return safeExistsSync(join(atomHome, "lib")) ? "jar" : kind;
|
|
899
|
+
}
|
|
900
|
+
if (resolveDirectAtomBinaryPath()) {
|
|
901
|
+
return "native";
|
|
902
|
+
}
|
|
903
|
+
const jarPlugins = atomSubPackageCandidates("atom-jar").some((dir) =>
|
|
904
|
+
safeExistsSync(join(dir, "plugins")),
|
|
905
|
+
);
|
|
906
|
+
return jarPlugins ? "jar" : kind;
|
|
619
907
|
}
|
|
620
908
|
|
|
621
909
|
/**
|
|
@@ -683,6 +971,25 @@ export function resolveDirectAtomBinaryPath() {
|
|
|
683
971
|
}
|
|
684
972
|
const folder = preferredPkg.split("/")[1];
|
|
685
973
|
const exeName = isWin ? "atom.exe" : "atom";
|
|
974
|
+
for (const dir of atomSubPackageCandidates(folder)) {
|
|
975
|
+
const candidate = join(dir, "bin", exeName);
|
|
976
|
+
if (safeExistsSync(candidate)) {
|
|
977
|
+
return candidate;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
return undefined;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
/**
|
|
984
|
+
* Directories where an `@appthreat/atom-*` sub-package may be installed next to
|
|
985
|
+
* the dispatcher cdxgen uses, for npm, pnpm and global layouts: a sibling of
|
|
986
|
+
* `@appthreat/atom`, nested under it, in a pnpm virtual store, or a sibling of
|
|
987
|
+
* the dispatcher's real (symlink-resolved) directory.
|
|
988
|
+
*
|
|
989
|
+
* @param {string} folder Sub-package folder name, e.g. `atom-jar`
|
|
990
|
+
* @returns {string[]} Candidate package directories
|
|
991
|
+
*/
|
|
992
|
+
function atomSubPackageCandidates(folder) {
|
|
686
993
|
const version = readAtomVersion();
|
|
687
994
|
const roots = [join(dirNameStr, "node_modules")];
|
|
688
995
|
if (readEnvironmentVariable("GLOBAL_NODE_MODULES_PATH")) {
|
|
@@ -690,18 +997,9 @@ export function resolveDirectAtomBinaryPath() {
|
|
|
690
997
|
}
|
|
691
998
|
const candidates = [];
|
|
692
999
|
for (const root of roots) {
|
|
693
|
-
candidates.push(join(root, "@appthreat", folder
|
|
1000
|
+
candidates.push(join(root, "@appthreat", folder));
|
|
694
1001
|
candidates.push(
|
|
695
|
-
join(
|
|
696
|
-
root,
|
|
697
|
-
"@appthreat",
|
|
698
|
-
"atom",
|
|
699
|
-
"node_modules",
|
|
700
|
-
"@appthreat",
|
|
701
|
-
folder,
|
|
702
|
-
"bin",
|
|
703
|
-
exeName,
|
|
704
|
-
),
|
|
1002
|
+
join(root, "@appthreat", "atom", "node_modules", "@appthreat", folder),
|
|
705
1003
|
);
|
|
706
1004
|
if (version) {
|
|
707
1005
|
candidates.push(
|
|
@@ -712,18 +1010,19 @@ export function resolveDirectAtomBinaryPath() {
|
|
|
712
1010
|
"node_modules",
|
|
713
1011
|
"@appthreat",
|
|
714
1012
|
folder,
|
|
715
|
-
"bin",
|
|
716
|
-
exeName,
|
|
717
1013
|
),
|
|
718
1014
|
);
|
|
719
1015
|
}
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
1016
|
+
// pnpm links @appthreat/atom to its store entry, where the sub-packages
|
|
1017
|
+
// are its siblings.
|
|
1018
|
+
try {
|
|
1019
|
+
const atomDir = realpathSync(join(root, "@appthreat", "atom"));
|
|
1020
|
+
candidates.push(join(dirname(atomDir), folder));
|
|
1021
|
+
} catch {
|
|
1022
|
+
// not installed under this root
|
|
724
1023
|
}
|
|
725
1024
|
}
|
|
726
|
-
return
|
|
1025
|
+
return [...new Set(candidates)];
|
|
727
1026
|
}
|
|
728
1027
|
|
|
729
1028
|
function readAtomVersion() {
|
|
@@ -874,6 +1173,93 @@ function applyAtomHeapLimit(args, env) {
|
|
|
874
1173
|
}
|
|
875
1174
|
}
|
|
876
1175
|
|
|
1176
|
+
// How long cdxgen waits past atom's own time limit for the dispatcher to stop
|
|
1177
|
+
// the runtime and exit. It covers the dispatcher's SIGTERM-to-SIGKILL grace (10
|
|
1178
|
+
// seconds) and the runtime's exit. A short limit (a test, a smoke run) gets a
|
|
1179
|
+
// quarter of itself instead.
|
|
1180
|
+
const ATOM_STOP_GRACE_MS = 30_000;
|
|
1181
|
+
|
|
1182
|
+
/**
|
|
1183
|
+
* Compute how long atom may run, and how long cdxgen waits for it.
|
|
1184
|
+
*
|
|
1185
|
+
* cdxgen's spawn timeout kills only the process cdxgen started, and for atom
|
|
1186
|
+
* that is the npm dispatcher. The runtime under it (the native binary or the
|
|
1187
|
+
* JVM) used to survive that kill, re-parented and still holding its heap. atom
|
|
1188
|
+
* therefore gets its own limit, `ATOM_TIMEOUT`, which the dispatcher enforces on
|
|
1189
|
+
* the runtime, and cdxgen's spawn timeout sits a grace period beyond it as the
|
|
1190
|
+
* last resort.
|
|
1191
|
+
*
|
|
1192
|
+
* An explicit `ATOM_TIMEOUT` (milliseconds) sets atom's limit independently of
|
|
1193
|
+
* `CDXGEN_TIMEOUT_MS`, which keeps bounding every other tool. Otherwise atom's
|
|
1194
|
+
* limit is `CDXGEN_TIMEOUT_MS` less the grace period.
|
|
1195
|
+
*
|
|
1196
|
+
* @returns {{atomTimeoutMs: number, spawnTimeoutMs: number}} atom's own limit and cdxgen's spawn timeout for it
|
|
1197
|
+
*/
|
|
1198
|
+
export function atomTimeouts() {
|
|
1199
|
+
const graceFor = (ms) => Math.min(ATOM_STOP_GRACE_MS, Math.floor(ms / 4));
|
|
1200
|
+
const configured = Number.parseInt(
|
|
1201
|
+
readEnvironmentVariable("ATOM_TIMEOUT"),
|
|
1202
|
+
10,
|
|
1203
|
+
);
|
|
1204
|
+
if (configured > 0) {
|
|
1205
|
+
return {
|
|
1206
|
+
atomTimeoutMs: configured,
|
|
1207
|
+
spawnTimeoutMs: configured + graceFor(configured),
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
return {
|
|
1211
|
+
atomTimeoutMs: TIMEOUT_MS - graceFor(TIMEOUT_MS),
|
|
1212
|
+
spawnTimeoutMs: TIMEOUT_MS,
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
function formatDuration(ms) {
|
|
1217
|
+
if (ms >= 60_000) {
|
|
1218
|
+
const minutes = Math.round(ms / 6_000) / 10;
|
|
1219
|
+
return `${minutes} minute${minutes === 1 ? "" : "s"}`;
|
|
1220
|
+
}
|
|
1221
|
+
return `${Math.round(ms / 100) / 10} seconds`;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
// The status the atom dispatcher exits with when ATOM_TIMEOUT stops atom.
|
|
1225
|
+
const ATOM_TIMEOUT_EXIT_CODE = 124;
|
|
1226
|
+
|
|
1227
|
+
/**
|
|
1228
|
+
* Whether an atom run ended because it ran out of time.
|
|
1229
|
+
*
|
|
1230
|
+
* The dispatcher reports its own limit with status 124. cdxgen's spawn timeout
|
|
1231
|
+
* shows up as `ETIMEDOUT`. A dispatcher older than the status code stops atom
|
|
1232
|
+
* with SIGTERM and exits 1, so status 1 at or past the limit counts as well,
|
|
1233
|
+
* unless atom's own output shows it failed for another reason first (an
|
|
1234
|
+
* exhausted heap, a crash), which deserves its own diagnosis.
|
|
1235
|
+
*
|
|
1236
|
+
* @param {Object} result spawnSync result
|
|
1237
|
+
* @param {number} elapsedMs How long the run took
|
|
1238
|
+
* @param {number} atomTimeoutMs atom's time limit
|
|
1239
|
+
* @returns {boolean} true when the run hit a time limit
|
|
1240
|
+
*/
|
|
1241
|
+
export function atomRunTimedOut(result, elapsedMs, atomTimeoutMs) {
|
|
1242
|
+
if (result?.status === ATOM_TIMEOUT_EXIT_CODE) {
|
|
1243
|
+
return true;
|
|
1244
|
+
}
|
|
1245
|
+
if (result?.error?.code === "ETIMEDOUT") {
|
|
1246
|
+
return true;
|
|
1247
|
+
}
|
|
1248
|
+
if (result?.status !== 1 || elapsedMs < atomTimeoutMs) {
|
|
1249
|
+
return false;
|
|
1250
|
+
}
|
|
1251
|
+
const output = `${result.stdout || ""}\n${result.stderr || ""}`;
|
|
1252
|
+
return !ATOM_FAILURE_MARKERS.some((marker) => output.includes(marker));
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
// Output that means atom failed by itself, whatever the clock says.
|
|
1256
|
+
const ATOM_FAILURE_MARKERS = [
|
|
1257
|
+
"OutOfMemoryError",
|
|
1258
|
+
"A fatal error has been detected by the Java Runtime Environment",
|
|
1259
|
+
"The crash happened outside the Java Virtual Machine in native code",
|
|
1260
|
+
"Failure: ",
|
|
1261
|
+
];
|
|
1262
|
+
|
|
877
1263
|
/**
|
|
878
1264
|
* Execute the atom tool against a source directory or file with the given arguments.
|
|
879
1265
|
*
|
|
@@ -914,14 +1300,12 @@ export function executeAtom(src, args, extra_env = {}) {
|
|
|
914
1300
|
...extra_env,
|
|
915
1301
|
};
|
|
916
1302
|
// Bound the heap while argv still starts at atom's own arguments: the
|
|
917
|
-
//
|
|
1303
|
+
// split below prepends the launcher script, which has to stay first.
|
|
918
1304
|
applyAtomHeapLimit(args, env);
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
args.unshift(tmpA[1]);
|
|
924
|
-
}
|
|
1305
|
+
const [resolvedBin, leadingArg] = splitAtomCommand(ATOM_BIN);
|
|
1306
|
+
if (leadingArg !== undefined) {
|
|
1307
|
+
ATOM_BIN = resolvedBin;
|
|
1308
|
+
args.unshift(leadingArg);
|
|
925
1309
|
}
|
|
926
1310
|
if (DEBUG_MODE) {
|
|
927
1311
|
console.log("Executing", ATOM_BIN);
|
|
@@ -952,12 +1336,33 @@ export function executeAtom(src, args, extra_env = {}) {
|
|
|
952
1336
|
".bin",
|
|
953
1337
|
)}`;
|
|
954
1338
|
}
|
|
1339
|
+
const { atomTimeoutMs, spawnTimeoutMs } = atomTimeouts();
|
|
1340
|
+
env.ATOM_TIMEOUT = String(atomTimeoutMs);
|
|
1341
|
+
// Name cdxgen as the supervisor: the dispatcher, and atom under it, stop when
|
|
1342
|
+
// cdxgen is gone (killed by a caller's own timeout, say) instead of analysing
|
|
1343
|
+
// on for nobody.
|
|
1344
|
+
env.ATOM_PARENT_PID = String(process.pid);
|
|
1345
|
+
const startedAt = Date.now();
|
|
955
1346
|
const result = safeSpawnSync(ATOM_BIN, args, {
|
|
956
1347
|
cwd,
|
|
957
1348
|
shell: isWin,
|
|
958
1349
|
killSignal: "SIGKILL",
|
|
959
1350
|
env,
|
|
1351
|
+
timeout: spawnTimeoutMs,
|
|
960
1352
|
});
|
|
1353
|
+
if (atomRunTimedOut(result, Date.now() - startedAt, atomTimeoutMs)) {
|
|
1354
|
+
console.warn(
|
|
1355
|
+
`WARN: atom did not finish within ${formatDuration(atomTimeoutMs)} and was stopped, so its results are missing or incomplete. Allow more time with ATOM_TIMEOUT (milliseconds, atom only) or CDXGEN_TIMEOUT_MS (every external command).`,
|
|
1356
|
+
);
|
|
1357
|
+
// A stopped atom leaves its graph half written, and the next slice of the
|
|
1358
|
+
// same run would try to reuse it and fail to load it.
|
|
1359
|
+
const atomFileIndex = args.indexOf("-o");
|
|
1360
|
+
const atomFile = atomFileIndex >= 0 ? args[atomFileIndex + 1] : undefined;
|
|
1361
|
+
if (atomFile && safeExistsSync(atomFile)) {
|
|
1362
|
+
safeRmSync(atomFile, { recursive: true, force: true });
|
|
1363
|
+
}
|
|
1364
|
+
return false;
|
|
1365
|
+
}
|
|
961
1366
|
const isJarKind = atomProviderKind() === "jar";
|
|
962
1367
|
if (result.stderr) {
|
|
963
1368
|
if (
|
|
@@ -1036,6 +1441,130 @@ export function executeAtom(src, args, extra_env = {}) {
|
|
|
1036
1441
|
return isSupported && !result.error && result.status === 0;
|
|
1037
1442
|
}
|
|
1038
1443
|
|
|
1444
|
+
const CHUNK_MTIME_SLACK_MS = 2000;
|
|
1445
|
+
|
|
1446
|
+
/**
|
|
1447
|
+
* The numbered chunks atom wrote beside a reachables slices file.
|
|
1448
|
+
*
|
|
1449
|
+
* atom writes reachables at most 1000 flows per file: `<base>.json`, then
|
|
1450
|
+
* `<base>_1.json`, `<base>_2.json` and so on. Chunks are taken until the first
|
|
1451
|
+
* gap. A chunk older than the base file was left by an earlier run with more
|
|
1452
|
+
* flows (atom before 4.0.0 did not remove those), so it ends the sequence
|
|
1453
|
+
* instead of being merged.
|
|
1454
|
+
*
|
|
1455
|
+
* @param {string} slicesFile Path of the base reachables slices file
|
|
1456
|
+
* @returns {string[]} Chunk file paths, in order
|
|
1457
|
+
*/
|
|
1458
|
+
export function reachablesChunkFiles(slicesFile) {
|
|
1459
|
+
const baseMtimeMs = mtimeMsOf(slicesFile);
|
|
1460
|
+
if (!slicesFile?.endsWith(".json") || baseMtimeMs === undefined) {
|
|
1461
|
+
return [];
|
|
1462
|
+
}
|
|
1463
|
+
const stem = slicesFile.slice(0, -".json".length);
|
|
1464
|
+
// atom writes the base file first, so its chunks are never older. The slack
|
|
1465
|
+
// covers filesystems that store modification times in whole seconds or two;
|
|
1466
|
+
// it assumes no earlier run wrote chunks to the same path within that window.
|
|
1467
|
+
// atom 4 and cdxgen both remove old chunks before atom writes new ones, so
|
|
1468
|
+
// the mtime test only matters for slices files produced elsewhere.
|
|
1469
|
+
const oldestChunkMtimeMs = baseMtimeMs - CHUNK_MTIME_SLACK_MS;
|
|
1470
|
+
const chunks = [];
|
|
1471
|
+
for (let index = 1; ; index++) {
|
|
1472
|
+
const chunk = `${stem}_${index}.json`;
|
|
1473
|
+
const chunkMtimeMs = mtimeMsOf(chunk);
|
|
1474
|
+
if (chunkMtimeMs === undefined || chunkMtimeMs < oldestChunkMtimeMs) {
|
|
1475
|
+
break;
|
|
1476
|
+
}
|
|
1477
|
+
chunks.push(chunk);
|
|
1478
|
+
}
|
|
1479
|
+
return chunks;
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
// A file that disappears between listing and stat ends the sequence, not the run.
|
|
1483
|
+
function mtimeMsOf(file) {
|
|
1484
|
+
if (!file || !safeExistsSync(file)) {
|
|
1485
|
+
return undefined;
|
|
1486
|
+
}
|
|
1487
|
+
try {
|
|
1488
|
+
return statSync(file).mtimeMs;
|
|
1489
|
+
} catch {
|
|
1490
|
+
return undefined;
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
/**
|
|
1495
|
+
* Remove the numbered chunks beside a reachables slices file, before atom
|
|
1496
|
+
* writes a new set there, so no chunk of an earlier run survives next to it.
|
|
1497
|
+
*
|
|
1498
|
+
* @param {string} slicesFile Path of the base reachables slices file
|
|
1499
|
+
*/
|
|
1500
|
+
export function removeReachablesChunkFiles(slicesFile) {
|
|
1501
|
+
if (!slicesFile?.endsWith(".json")) {
|
|
1502
|
+
return;
|
|
1503
|
+
}
|
|
1504
|
+
const stem = slicesFile.slice(0, -".json".length);
|
|
1505
|
+
for (let index = 1; safeExistsSync(`${stem}_${index}.json`); index++) {
|
|
1506
|
+
safeRmSync(`${stem}_${index}.json`, { force: true });
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
/**
|
|
1511
|
+
* Read an atom slices file, or warn and return `undefined` when it cannot be
|
|
1512
|
+
* parsed. A run stopped while writing (a timeout, a kill) can leave a
|
|
1513
|
+
* truncated file, and the evidence from the other slices is still worth
|
|
1514
|
+
* keeping.
|
|
1515
|
+
*
|
|
1516
|
+
* @param {string} slicesFile Path of the slices file
|
|
1517
|
+
* @param {string} sliceType Slice type, for the warning
|
|
1518
|
+
* @returns {*} The parsed slices, or `undefined`
|
|
1519
|
+
*/
|
|
1520
|
+
export function readSlicesFile(slicesFile, sliceType) {
|
|
1521
|
+
if (!slicesFile || !safeExistsSync(slicesFile)) {
|
|
1522
|
+
return undefined;
|
|
1523
|
+
}
|
|
1524
|
+
try {
|
|
1525
|
+
return readJsonFile(slicesFile);
|
|
1526
|
+
} catch (e) {
|
|
1527
|
+
console.warn(
|
|
1528
|
+
`WARN: Ignoring the ${sliceType} slices file ${slicesFile}, which is incomplete or not valid JSON (${e.message}).`,
|
|
1529
|
+
);
|
|
1530
|
+
return undefined;
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
/**
|
|
1535
|
+
* Read a reachables slices file together with its chunks.
|
|
1536
|
+
*
|
|
1537
|
+
* Reading only the base file silently dropped every flow past the first
|
|
1538
|
+
* thousand. A chunk that cannot be parsed ends the sequence, keeping the flows
|
|
1539
|
+
* read before it.
|
|
1540
|
+
*
|
|
1541
|
+
* @param {string} slicesFile Path of the base reachables slices file
|
|
1542
|
+
* @returns {Object[]|Object|undefined} Every flow, in the base file's shape (an array, or an object with `reachables`)
|
|
1543
|
+
*/
|
|
1544
|
+
export function readReachablesSlices(slicesFile) {
|
|
1545
|
+
const data = readSlicesFile(slicesFile, "reachables");
|
|
1546
|
+
if (data === undefined) {
|
|
1547
|
+
return undefined;
|
|
1548
|
+
}
|
|
1549
|
+
const chunks = reachablesChunkFiles(slicesFile);
|
|
1550
|
+
if (!chunks.length) {
|
|
1551
|
+
return data;
|
|
1552
|
+
}
|
|
1553
|
+
const flowsOf = (slices) =>
|
|
1554
|
+
Array.isArray(slices) ? slices : slices?.reachables || [];
|
|
1555
|
+
const flows = [...flowsOf(data)];
|
|
1556
|
+
for (const chunk of chunks) {
|
|
1557
|
+
const chunkData = readSlicesFile(chunk, "reachables");
|
|
1558
|
+
if (chunkData === undefined) {
|
|
1559
|
+
break;
|
|
1560
|
+
}
|
|
1561
|
+
for (const flow of flowsOf(chunkData)) {
|
|
1562
|
+
flows.push(flow);
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
return Array.isArray(data) || !data ? flows : { ...data, reachables: flows };
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1039
1568
|
/**
|
|
1040
1569
|
* Find the imported modules in the application with atom parsedeps command
|
|
1041
1570
|
*
|
|
@@ -1068,6 +1597,14 @@ export function findAppModules(
|
|
|
1068
1597
|
"--slice-outfile",
|
|
1069
1598
|
resolve(slicesFile),
|
|
1070
1599
|
];
|
|
1600
|
+
// The header languages parse without function bodies. Their AST cache, kept
|
|
1601
|
+
// in <src>/.chen, would be replayed by a later full C/C++ run over the same
|
|
1602
|
+
// directory (evinse, or `-t c --deep`) on atom releases that do not key the
|
|
1603
|
+
// cache on that difference, leaving that run with no method bodies.
|
|
1604
|
+
if (ATOM_HEADER_LANGUAGES.has(`${language}`.toLowerCase())) {
|
|
1605
|
+
args.push("--no-ast-cache");
|
|
1606
|
+
}
|
|
1607
|
+
args.push(...atomCompileCommandsArgs(src, language, options));
|
|
1071
1608
|
args.push(resolve(src));
|
|
1072
1609
|
executeAtom(src, args, buildAtomCommandEnv(options, language));
|
|
1073
1610
|
if (safeExistsSync(slicesFile)) {
|