@cdxgen/cdxgen 13.2.0 → 13.4.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 +33 -14
- package/bin/cdxgen.js +310 -138
- package/bin/evinse.js +22 -0
- package/bin/repl.js +86 -0
- package/bin/sign.js +119 -15
- package/bin/tracebom.js +69 -5
- package/bin/validate.js +107 -19
- package/bin/verify.js +172 -63
- package/data/helpers/init.gradle +46 -21
- package/data/remediations.json +85 -0
- package/data/rules/dependency-sources.yaml +67 -0
- package/data/rules/scala-evidence.yaml +115 -0
- package/lib/audit/index.js +38 -6
- package/lib/cli/bomAssembly.js +105 -15
- package/lib/cli/cliOptions.js +93 -1
- package/lib/cli/index.js +156 -58
- package/lib/cli/jsBom.js +99 -53
- package/lib/cli/jvmBom.js +708 -147
- package/lib/cli/managedBom.js +415 -224
- package/lib/cli/nativeBom.js +264 -44
- package/lib/core/activity.js +12 -0
- package/lib/core/env.js +22 -15
- package/lib/core/fs.js +176 -7
- package/lib/core/httpClient.js +159 -24
- package/lib/core/metadataProperties.js +97 -0
- package/lib/core/pluginRun.js +184 -0
- package/lib/core/processTree.js +100 -0
- package/lib/core/projectConfig.js +31 -4
- package/lib/core/runState.js +39 -0
- package/lib/core/toml.js +72 -0
- package/lib/ecosystems/asarutils.js +4 -2
- package/lib/ecosystems/bunutils.js +274 -108
- package/lib/ecosystems/cargoCache.js +231 -8
- package/lib/ecosystems/caxa.js +434 -2
- package/lib/ecosystems/cmakeResolver.js +76 -21
- package/lib/ecosystems/cppBuildContext.js +631 -0
- package/lib/ecosystems/cppEvidence.js +399 -51
- package/lib/ecosystems/cppIncludeResolver.js +294 -0
- package/lib/ecosystems/denoutils.js +93 -66
- package/lib/ecosystems/ecosystems.js +1983 -793
- package/lib/ecosystems/golem.js +28 -12
- package/lib/ecosystems/gradleutils.js +356 -37
- package/lib/ecosystems/jsEvidence.js +104 -44
- package/lib/ecosystems/kosi.js +31 -7
- package/lib/ecosystems/npmutils.js +174 -9
- package/lib/ecosystems/parsers-dotnet.js +616 -159
- package/lib/ecosystems/parsers-elm.js +46 -28
- package/lib/ecosystems/parsers-gleam.js +3 -4
- package/lib/ecosystems/parsers-go.js +371 -70
- package/lib/ecosystems/parsers-js.js +263 -76
- package/lib/ecosystems/parsers-julia.js +2 -3
- package/lib/ecosystems/parsers-jvm.js +204 -12
- package/lib/ecosystems/parsers-misc.js +929 -216
- package/lib/ecosystems/parsers-python.js +167 -24
- package/lib/ecosystems/parsers-rust.js +28 -22
- package/lib/ecosystems/parsers-terraform.js +1573 -19
- package/lib/ecosystems/pythonutils.js +102 -6
- package/lib/ecosystems/remote/huggingface.js +3 -0
- package/lib/ecosystems/rubyutils.js +1492 -182
- package/lib/ecosystems/rusi.js +54 -10
- package/lib/ecosystems/sbtutils.js +243 -187
- package/lib/ecosystems/scalacliutils.js +342 -0
- package/lib/ecosystems/scalasem.js +2618 -0
- package/lib/ecosystems/terraformRegistry.js +288 -0
- package/lib/ecosystems/utils.js +2 -0
- package/lib/ecosystems/vendoredCode.js +156 -0
- package/lib/evinser/evinser.js +1008 -191
- package/lib/evinser/swiftsem.js +1914 -302
- package/lib/helpers/agenticToolutils.js +2 -2
- package/lib/helpers/bomSigner.js +1101 -222
- package/lib/inventory/aiCollector.js +4 -1
- package/lib/inventory/analyzer.js +110 -127
- package/lib/inventory/atomUtils.js +703 -43
- package/lib/inventory/auditCategories.js +7 -0
- package/lib/inventory/buildToolRateLimit.js +144 -0
- package/lib/inventory/cacheCommands.js +2 -1
- package/lib/inventory/cdxrs.js +103 -12
- package/lib/inventory/cmakeBuildDirs.js +254 -0
- package/lib/inventory/deps.js +501 -124
- package/lib/inventory/depsUtils.js +183 -11
- package/lib/inventory/display.js +4 -5
- package/lib/inventory/dosai.js +798 -57
- package/lib/inventory/dosaiParsers.js +310 -15
- package/lib/inventory/dynamic.js +90 -6
- package/lib/inventory/evidenceUtils.js +166 -113
- package/lib/inventory/fetchBatch.js +690 -55
- package/lib/inventory/fetchDiskCache.js +597 -0
- package/lib/inventory/forgeEnricher.js +14 -5
- package/lib/inventory/jvmLocalRepos.js +917 -0
- package/lib/inventory/mcpConfigParser.js +2 -3
- package/lib/inventory/parentSelection.js +146 -0
- package/lib/inventory/protobom.js +8 -1
- package/lib/inventory/purl.js +152 -3
- package/lib/inventory/rustFormulationParser.js +3 -4
- package/lib/inventory/sbomDocument.js +2 -70
- package/lib/inventory/scalaCoords.js +153 -0
- package/lib/inventory/source.js +45 -27
- package/lib/inventory/spdx.js +101 -0
- package/lib/inventory/traceRunner.js +295 -25
- package/lib/inventory/tracedCrypto.js +294 -0
- package/lib/managers/binary.js +115 -24
- package/lib/managers/docker.js +467 -46
- package/lib/managers/oci.js +63 -3
- package/lib/parsers/cmakeCache.js +69 -0
- package/lib/parsers/cmakeLists.js +466 -0
- package/lib/parsers/cmakePresets.js +400 -0
- package/lib/parsers/compileCommands.js +557 -0
- package/lib/parsers/largeJson.js +454 -0
- package/lib/parsers/npmrc.js +19 -1
- package/lib/parsers/strictJson.js +322 -0
- package/lib/parsers/terraformConfig.js +1073 -0
- package/lib/parsers/terraformSource.js +652 -0
- package/lib/server/server.js +23 -0
- package/lib/stages/postgen/introspection/markers.js +1 -1
- package/lib/stages/postgen/postgen.js +83 -16
- package/lib/stages/postgen/specVersionCompat.js +32 -0
- package/lib/validator/bomValidator.js +6 -16
- package/lib/validator/index.js +64 -11
- package/lib/validator/reporters/console.js +3 -0
- package/lib/validator/reporters/json.js +2 -0
- package/lib/validator/reporters/sarif.js +7 -0
- package/package.json +23 -23
- package/types/lib/audit/index.d.ts.map +1 -1
- 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 +24 -1
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts +7 -4
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts +3 -1
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/activity.d.ts.map +1 -1
- package/types/lib/core/env.d.ts +9 -0
- 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/httpClient.d.ts +13 -8
- package/types/lib/core/httpClient.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/pluginRun.d.ts +97 -0
- package/types/lib/core/pluginRun.d.ts.map +1 -0
- package/types/lib/core/processTree.d.ts +36 -0
- package/types/lib/core/processTree.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/core/runState.d.ts +29 -0
- package/types/lib/core/runState.d.ts.map +1 -0
- package/types/lib/core/toml.d.ts +20 -0
- package/types/lib/core/toml.d.ts.map +1 -0
- package/types/lib/ecosystems/asarutils.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/cargoCache.d.ts +12 -0
- package/types/lib/ecosystems/cargoCache.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 +28 -3
- package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
- package/types/lib/ecosystems/cppBuildContext.d.ts +55 -0
- package/types/lib/ecosystems/cppBuildContext.d.ts.map +1 -0
- package/types/lib/ecosystems/cppEvidence.d.ts +17 -1
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/cppIncludeResolver.d.ts +92 -0
- package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -0
- package/types/lib/ecosystems/denoutils.d.ts +9 -3
- package/types/lib/ecosystems/denoutils.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts +62 -13
- 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 +70 -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 +57 -0
- package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-dotnet.d.ts +118 -4
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-gleam.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-go.d.ts +54 -2
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts +16 -5
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts +46 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts +70 -4
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts +53 -3
- 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/parsers-terraform.d.ts +38 -21
- package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +27 -2
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
- package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts +164 -3
- 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/ecosystems/sbtutils.d.ts +88 -2
- package/types/lib/ecosystems/sbtutils.d.ts.map +1 -1
- package/types/lib/ecosystems/scalacliutils.d.ts +56 -0
- package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -0
- package/types/lib/ecosystems/scalasem.d.ts +311 -0
- package/types/lib/ecosystems/scalasem.d.ts.map +1 -0
- package/types/lib/ecosystems/terraformRegistry.d.ts +9 -0
- package/types/lib/ecosystems/terraformRegistry.d.ts.map +1 -0
- package/types/lib/ecosystems/utils.d.ts +2 -2
- package/types/lib/ecosystems/utils.d.ts.map +1 -1
- package/types/lib/ecosystems/vendoredCode.d.ts +32 -0
- package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +132 -15
- 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 +182 -13
- package/types/lib/helpers/bomSigner.d.ts.map +1 -1
- package/types/lib/inventory/aiCollector.d.ts.map +1 -1
- package/types/lib/inventory/analyzer.d.ts.map +1 -1
- package/types/lib/inventory/atomUtils.d.ts +219 -5
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/auditCategories.d.ts +3 -0
- package/types/lib/inventory/auditCategories.d.ts.map +1 -1
- package/types/lib/inventory/buildToolRateLimit.d.ts +38 -0
- package/types/lib/inventory/buildToolRateLimit.d.ts.map +1 -0
- package/types/lib/inventory/cacheCommands.d.ts +2 -1
- package/types/lib/inventory/cacheCommands.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/cmakeBuildDirs.d.ts +58 -0
- package/types/lib/inventory/cmakeBuildDirs.d.ts.map +1 -0
- package/types/lib/inventory/deps.d.ts +59 -1
- package/types/lib/inventory/deps.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 +121 -9
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dosaiParsers.d.ts +53 -7
- package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
- package/types/lib/inventory/dynamic.d.ts +10 -0
- package/types/lib/inventory/dynamic.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts +19 -4
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/fetchBatch.d.ts +83 -14
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
- package/types/lib/inventory/fetchDiskCache.d.ts +169 -0
- package/types/lib/inventory/fetchDiskCache.d.ts.map +1 -0
- package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
- package/types/lib/inventory/jvmLocalRepos.d.ts +178 -0
- package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -0
- package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/parentSelection.d.ts +34 -0
- package/types/lib/inventory/parentSelection.d.ts.map +1 -0
- 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/rustFormulationParser.d.ts.map +1 -1
- package/types/lib/inventory/sbomDocument.d.ts.map +1 -1
- package/types/lib/inventory/scalaCoords.d.ts +80 -0
- package/types/lib/inventory/scalaCoords.d.ts.map +1 -0
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/spdx.d.ts +9 -0
- package/types/lib/inventory/spdx.d.ts.map +1 -1
- package/types/lib/inventory/traceRunner.d.ts +59 -1
- package/types/lib/inventory/traceRunner.d.ts.map +1 -1
- package/types/lib/inventory/tracedCrypto.d.ts +42 -0
- package/types/lib/inventory/tracedCrypto.d.ts.map +1 -0
- 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 +17 -1
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/parsers/cmakeCache.d.ts +16 -0
- package/types/lib/parsers/cmakeCache.d.ts.map +1 -1
- package/types/lib/parsers/cmakeLists.d.ts +77 -0
- package/types/lib/parsers/cmakeLists.d.ts.map +1 -0
- package/types/lib/parsers/cmakePresets.d.ts +78 -0
- package/types/lib/parsers/cmakePresets.d.ts.map +1 -0
- package/types/lib/parsers/compileCommands.d.ts +115 -0
- package/types/lib/parsers/compileCommands.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/parsers/strictJson.d.ts +46 -0
- package/types/lib/parsers/strictJson.d.ts.map +1 -0
- package/types/lib/parsers/terraformConfig.d.ts +70 -0
- package/types/lib/parsers/terraformConfig.d.ts.map +1 -0
- package/types/lib/parsers/terraformSource.d.ts +66 -0
- package/types/lib/parsers/terraformSource.d.ts.map +1 -0
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/index.d.ts +18 -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
- package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +0 -462
|
@@ -1,17 +1,37 @@
|
|
|
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
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
delimiter as _delimiter,
|
|
11
|
+
basename,
|
|
12
|
+
dirname,
|
|
13
|
+
join,
|
|
14
|
+
resolve,
|
|
15
|
+
} from "node:path";
|
|
4
16
|
import process from "node:process";
|
|
5
17
|
|
|
6
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
DEBUG_MODE,
|
|
20
|
+
isSecureMode,
|
|
21
|
+
readEnvironmentVariable,
|
|
22
|
+
} from "../core/activity.js";
|
|
7
23
|
import {
|
|
8
24
|
safeExistsSync,
|
|
9
25
|
safeMkdtempSync,
|
|
10
26
|
safeRmSync,
|
|
11
27
|
safeSpawnSync,
|
|
28
|
+
TIMEOUT_MS,
|
|
12
29
|
} from "../core/fs.js";
|
|
13
30
|
import { TRACE_MODE } from "../core/logger.js";
|
|
14
31
|
import { dirNameStr, isWin } from "../core/paths.js";
|
|
32
|
+
import { formatDuration, reapProcessTree } from "../core/processTree.js";
|
|
33
|
+
import { readJsonFile } from "../parsers/largeJson.js";
|
|
34
|
+
import { cmakeBuildDirCandidates } from "./cmakeBuildDirs.js";
|
|
15
35
|
|
|
16
36
|
const ASTGEN_DEFAULT_IGNORE_DIRS = [
|
|
17
37
|
"venv",
|
|
@@ -57,6 +77,243 @@ export const ATOM_NATIVE_PACKAGES = new Set([
|
|
|
57
77
|
|
|
58
78
|
const ATOM_PHP_LANGUAGES = new Set(["php"]);
|
|
59
79
|
|
|
80
|
+
/** atom `-l` values that parse C/C++ headers without function bodies. */
|
|
81
|
+
const ATOM_HEADER_LANGUAGES = new Set(["h", "hpp", "i"]);
|
|
82
|
+
|
|
83
|
+
/** atom `-l` values handled by the C/C++ frontend. */
|
|
84
|
+
const ATOM_C_LANGUAGES = new Set([
|
|
85
|
+
"c",
|
|
86
|
+
"cpp",
|
|
87
|
+
"c++",
|
|
88
|
+
"newc",
|
|
89
|
+
...ATOM_HEADER_LANGUAGES,
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
const COMPILE_COMMANDS_FILE = "compile_commands.json";
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Characters a Windows shell command line would interpret: `&`, `|`, `<` and
|
|
96
|
+
* `>` chain or redirect commands, `%` expands variables even inside quotes,
|
|
97
|
+
* `^` escapes, `!` expands under delayed expansion, and a quote breaks the
|
|
98
|
+
* quoting Node applies to arguments when `shell` is on. A database is often
|
|
99
|
+
* discovered inside the scanned tree, so its path is not trusted input.
|
|
100
|
+
*/
|
|
101
|
+
const UNSAFE_SHELL_CHARS_RE = /[\r\n\t&|<>%^!"]/;
|
|
102
|
+
|
|
103
|
+
function compileCommandsIn(dir) {
|
|
104
|
+
for (const candidate of [
|
|
105
|
+
join(dir, COMPILE_COMMANDS_FILE),
|
|
106
|
+
join(dir, "build", COMPILE_COMMANDS_FILE),
|
|
107
|
+
]) {
|
|
108
|
+
try {
|
|
109
|
+
if (statSync(candidate).isFile()) {
|
|
110
|
+
return candidate;
|
|
111
|
+
}
|
|
112
|
+
} catch (_err) {
|
|
113
|
+
// not there
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Locate the JSON compilation database (`compile_commands.json`) of a C/C++
|
|
121
|
+
* project, which lets atom parse each file with the include paths, macros and
|
|
122
|
+
* language its build uses.
|
|
123
|
+
*
|
|
124
|
+
* An explicit `options.compileCommands` (a file, or a directory holding one
|
|
125
|
+
* directly or under `build/`) wins. Otherwise the scan root and the project's
|
|
126
|
+
* build directories are searched: the directory of `options.cmakeCache`, the
|
|
127
|
+
* build directories of its CMake presets, and the conventional `build`,
|
|
128
|
+
* `out`, `builddir` and `cmake-build-` directories (see
|
|
129
|
+
* `cmakeBuildDirCandidates`). A database can come with the code it describes,
|
|
130
|
+
* and atom asks the
|
|
131
|
+
* GCC or Clang driver it names (from the PATH, or an absolute path outside
|
|
132
|
+
* the project) for its predefined macros, so in secure mode only an explicit
|
|
133
|
+
* database is used.
|
|
134
|
+
*
|
|
135
|
+
* @param {string} src Project scan root
|
|
136
|
+
* @param {Object} options CLI options
|
|
137
|
+
* @returns {string|undefined} Absolute path of the database
|
|
138
|
+
*/
|
|
139
|
+
export function findCompileCommands(src, options = {}) {
|
|
140
|
+
if (options.compileCommands) {
|
|
141
|
+
const requested = resolve(`${options.compileCommands}`);
|
|
142
|
+
let found;
|
|
143
|
+
try {
|
|
144
|
+
found = statSync(requested).isDirectory()
|
|
145
|
+
? compileCommandsIn(requested)
|
|
146
|
+
: requested;
|
|
147
|
+
} catch (_err) {
|
|
148
|
+
found = undefined;
|
|
149
|
+
}
|
|
150
|
+
if (!found) {
|
|
151
|
+
console.warn(
|
|
152
|
+
`No compilation database at ${requested}. Parsing the C/C++ sources without one.`,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
return found;
|
|
156
|
+
}
|
|
157
|
+
if (isSecureMode || !src) {
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
const root = resolve(src);
|
|
161
|
+
let rootIsDir = false;
|
|
162
|
+
try {
|
|
163
|
+
rootIsDir = statSync(root).isDirectory();
|
|
164
|
+
} catch (_err) {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
if (!rootIsDir) {
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
const dirs = [root, ...cmakeBuildDirCandidates(root, options)];
|
|
171
|
+
for (const dir of dirs) {
|
|
172
|
+
const candidate = join(dir, COMPILE_COMMANDS_FILE);
|
|
173
|
+
try {
|
|
174
|
+
if (statSync(candidate).isFile()) {
|
|
175
|
+
return candidate;
|
|
176
|
+
}
|
|
177
|
+
} catch (_err) {
|
|
178
|
+
// not there
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const atomFrontendArgKeysCache = new Map();
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Split an atom command that may carry a leading argument (`node
|
|
188
|
+
* script.js`, a custom `ATOM_CMD`) into its binary and that argument. The
|
|
189
|
+
* first space only splits when it can: a binary under a path with spaces
|
|
190
|
+
* (`C:\Program Files\...\node.exe script.js`) would otherwise be cut
|
|
191
|
+
* mid-path and the shell told to run `C:\Program`. A command name without a
|
|
192
|
+
* path separator (`node script.js`) resolves through the PATH, so it splits
|
|
193
|
+
* at its first space as before; otherwise the first prefix that exists as a
|
|
194
|
+
* file is the binary, and when none does the first space keeps the old
|
|
195
|
+
* behaviour.
|
|
196
|
+
*
|
|
197
|
+
* @param {string} command The atom command, possibly with an argument
|
|
198
|
+
* @returns {[string, string|undefined]} The binary and its leading argument, when present
|
|
199
|
+
*/
|
|
200
|
+
export function splitAtomCommand(command) {
|
|
201
|
+
const firstSpace = `${command}`.indexOf(" ");
|
|
202
|
+
if (firstSpace === -1) {
|
|
203
|
+
return [`${command}`, undefined];
|
|
204
|
+
}
|
|
205
|
+
const head = command.slice(0, firstSpace);
|
|
206
|
+
if (!/[/\\]/.test(head)) {
|
|
207
|
+
return [head, command.slice(firstSpace + 1)];
|
|
208
|
+
}
|
|
209
|
+
let splitAt = firstSpace;
|
|
210
|
+
let index = firstSpace;
|
|
211
|
+
while ((index = command.indexOf(" ", index + 1)) !== -1) {
|
|
212
|
+
try {
|
|
213
|
+
if (statSync(command.slice(0, index)).isFile()) {
|
|
214
|
+
splitAt = index;
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
} catch (_err) {
|
|
218
|
+
// not a file; a later space may still split at the real binary
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return [command.slice(0, splitAt), command.slice(splitAt + 1)];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* The `--frontend-args` keys the installed atom accepts for a language, as
|
|
226
|
+
* `atom --frontend-args-keys -l <language>` lists them. Asked once per atom
|
|
227
|
+
* command and language. An atom that cannot list them (a release without
|
|
228
|
+
* `--frontend-args`, or one that fails to start) accepts none.
|
|
229
|
+
*
|
|
230
|
+
* @param {string} language atom `-l` value
|
|
231
|
+
* @returns {Set<string>} Supported keys
|
|
232
|
+
*/
|
|
233
|
+
export function atomFrontendArgKeys(language) {
|
|
234
|
+
const command = getAtomCommand();
|
|
235
|
+
const cacheKey = `${command}\u0000${language}`;
|
|
236
|
+
if (atomFrontendArgKeysCache.has(cacheKey)) {
|
|
237
|
+
return atomFrontendArgKeysCache.get(cacheKey);
|
|
238
|
+
}
|
|
239
|
+
const [bin, leadingArg] = splitAtomCommand(command);
|
|
240
|
+
const args = ["--frontend-args-keys", "-l", `${language}`];
|
|
241
|
+
if (leadingArg !== undefined) {
|
|
242
|
+
args.unshift(leadingArg);
|
|
243
|
+
}
|
|
244
|
+
const env = { ...process.env };
|
|
245
|
+
if (readEnvironmentVariable("ATOM_JAVA_HOME")) {
|
|
246
|
+
env.JAVA_HOME = readEnvironmentVariable("ATOM_JAVA_HOME");
|
|
247
|
+
}
|
|
248
|
+
const keys = new Set();
|
|
249
|
+
const result = safeSpawnSync(bin, args, {
|
|
250
|
+
shell: isWin,
|
|
251
|
+
env,
|
|
252
|
+
timeout: 120000,
|
|
253
|
+
});
|
|
254
|
+
if (result?.status === 0 && result.stdout) {
|
|
255
|
+
let inTable = false;
|
|
256
|
+
for (const line of `${result.stdout}`.split(/\r?\n/)) {
|
|
257
|
+
if (line.startsWith("----")) {
|
|
258
|
+
inTable = true;
|
|
259
|
+
} else if (inTable && /^[a-z][a-z0-9-]*\s/.test(line)) {
|
|
260
|
+
keys.add(line.split(/\s+/)[0]);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (DEBUG_MODE && !keys.size) {
|
|
265
|
+
console.log(
|
|
266
|
+
`The installed atom did not list its --frontend-args keys for ${language}.`,
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
atomFrontendArgKeysCache.set(cacheKey, keys);
|
|
270
|
+
return keys;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* The `--frontend-args` atom takes for a C/C++ language: the project's
|
|
275
|
+
* compilation database, when there is one and the installed atom reads it
|
|
276
|
+
* (atom 4 and later). An older atom is given the arguments it has always
|
|
277
|
+
* been given, and parses the sources without the database.
|
|
278
|
+
*
|
|
279
|
+
* @param {string} src Project scan root
|
|
280
|
+
* @param {string} language atom `-l` value
|
|
281
|
+
* @param {Object} options CLI options
|
|
282
|
+
* @returns {string[]} Arguments to add to the atom command
|
|
283
|
+
*/
|
|
284
|
+
export function atomCompileCommandsArgs(src, language, options = {}) {
|
|
285
|
+
if (!ATOM_C_LANGUAGES.has(`${language}`.toLowerCase())) {
|
|
286
|
+
return [];
|
|
287
|
+
}
|
|
288
|
+
const database = findCompileCommands(src, options);
|
|
289
|
+
if (!database) {
|
|
290
|
+
return [];
|
|
291
|
+
}
|
|
292
|
+
// --frontend-args separates its key=value pairs with commas
|
|
293
|
+
if (database.includes(",")) {
|
|
294
|
+
console.warn(
|
|
295
|
+
`The compilation database path ${database} contains a comma, which atom cannot take. Parsing the C/C++ sources without it.`,
|
|
296
|
+
);
|
|
297
|
+
return [];
|
|
298
|
+
}
|
|
299
|
+
// The argument reaches a Windows shell command line through `shell: isWin`
|
|
300
|
+
// in executeAtom, and the path is routinely discovered inside the scanned
|
|
301
|
+
// tree, so a database whose path a shell would interpret is never passed on.
|
|
302
|
+
if (UNSAFE_SHELL_CHARS_RE.test(database)) {
|
|
303
|
+
console.warn(
|
|
304
|
+
`The compilation database path ${database} contains characters a shell would interpret. Parsing the C/C++ sources without it.`,
|
|
305
|
+
);
|
|
306
|
+
return [];
|
|
307
|
+
}
|
|
308
|
+
if (!atomFrontendArgKeys(language).has("compile-commands")) {
|
|
309
|
+
return [];
|
|
310
|
+
}
|
|
311
|
+
if (DEBUG_MODE) {
|
|
312
|
+
console.log(`Passing the compilation database ${database} to atom.`);
|
|
313
|
+
}
|
|
314
|
+
return ["--frontend-args", `compile-commands=${database}`];
|
|
315
|
+
}
|
|
316
|
+
|
|
60
317
|
// Absolute ceiling on atom's heap. Past this point a larger heap buys a
|
|
61
318
|
// collector that runs less often rather than an analysis that succeeds where it
|
|
62
319
|
// otherwise would not, while the reservation itself is what pushes a build
|
|
@@ -489,18 +746,32 @@ export function buildAtomCommandEnv(options = {}, language = "") {
|
|
|
489
746
|
// that does not exist on native platforms, and atom 3.0.x crashes parsing
|
|
490
747
|
// that bogus value. Forward the resolved php-parse location through the env
|
|
491
748
|
// so executeAtom can bypass the dispatcher for PHP (see
|
|
492
|
-
// resolveDirectAtomBinaryPath).
|
|
749
|
+
// resolveDirectAtomBinaryPath). PHP_ASTGEN_BIN additionally names the
|
|
750
|
+
// batch-capable generator (phpastgen.js): chen parses the whole tree, vendor
|
|
751
|
+
// included, in one generator run instead of one php interpreter per file,
|
|
752
|
+
// which on windows is the difference between minutes and hours. Computed
|
|
753
|
+
// independently of exclude patterns.
|
|
493
754
|
const phpParseBin = ATOM_PHP_LANGUAGES.has(normalizedLanguage)
|
|
494
755
|
? resolvePhpParseBin()
|
|
495
756
|
: undefined;
|
|
757
|
+
const phpAstgenBin = ATOM_PHP_LANGUAGES.has(normalizedLanguage)
|
|
758
|
+
? resolvePhpAstgenBin()
|
|
759
|
+
: undefined;
|
|
760
|
+
const phpEnv = {};
|
|
761
|
+
if (phpParseBin) {
|
|
762
|
+
phpEnv.PHP_PARSER_BIN = phpParseBin;
|
|
763
|
+
}
|
|
764
|
+
if (phpAstgenBin) {
|
|
765
|
+
phpEnv.PHP_ASTGEN_BIN = phpAstgenBin;
|
|
766
|
+
}
|
|
496
767
|
if (!excludePatterns.length) {
|
|
497
|
-
return
|
|
768
|
+
return phpEnv;
|
|
498
769
|
}
|
|
499
770
|
const chenIgnoreDirs = mergeCsvValues(
|
|
500
771
|
readEnvironmentVariable("CHEN_IGNORE_DIRS"),
|
|
501
772
|
extractIgnoreDirsFromExcludePatterns(excludePatterns, true),
|
|
502
773
|
);
|
|
503
|
-
const env = {};
|
|
774
|
+
const env = { ...phpEnv };
|
|
504
775
|
if (chenIgnoreDirs) {
|
|
505
776
|
env.CHEN_IGNORE_DIRS = chenIgnoreDirs;
|
|
506
777
|
}
|
|
@@ -526,9 +797,6 @@ export function buildAtomCommandEnv(options = {}, language = "") {
|
|
|
526
797
|
env.ASTGEN_IGNORE_FILE_PATTERN = astgenIgnoreFilePattern;
|
|
527
798
|
}
|
|
528
799
|
}
|
|
529
|
-
if (phpParseBin) {
|
|
530
|
-
env.PHP_PARSER_BIN = phpParseBin;
|
|
531
|
-
}
|
|
532
800
|
return env;
|
|
533
801
|
}
|
|
534
802
|
|
|
@@ -610,12 +878,93 @@ export function resolveAtomProvider(opts = {}) {
|
|
|
610
878
|
}
|
|
611
879
|
|
|
612
880
|
/**
|
|
613
|
-
* Returns `"native"` or `"jar"` for the
|
|
614
|
-
*
|
|
615
|
-
*
|
|
881
|
+
* Returns `"native"` or `"jar"` for the atom provider that will actually run.
|
|
882
|
+
*
|
|
883
|
+
* The platform decides which provider atom prefers, but the dispatcher falls
|
|
884
|
+
* back to the jar package when the platform's native package is not installed
|
|
885
|
+
* (optional dependencies skipped, a failed download). The two take the heap
|
|
886
|
+
* ceiling differently, and a native-image runtime option on a jar run reaches
|
|
887
|
+
* atom as an unknown argument that fails every slice. So the kind follows what
|
|
888
|
+
* is installed where cdxgen can see it: the native binary means native, the jar
|
|
889
|
+
* package without it means jar. An `ATOM_CMD` is classified by what it names
|
|
890
|
+
* (see `atomCommandKind`). With nothing in view (an install elsewhere) the
|
|
891
|
+
* platform's kind stands.
|
|
892
|
+
*
|
|
893
|
+
* Also used to gate Java/JDK advice, so users on the five native platforms are
|
|
894
|
+
* not told to install a JDK for a failure that has nothing to do with Java.
|
|
616
895
|
*/
|
|
617
896
|
export function atomProviderKind() {
|
|
618
|
-
|
|
897
|
+
const { kind } = resolveAtomProvider();
|
|
898
|
+
if (readEnvironmentVariable("ATOM_CMD")) {
|
|
899
|
+
return atomCommandKind(readEnvironmentVariable("ATOM_CMD"), kind);
|
|
900
|
+
}
|
|
901
|
+
if (kind !== "native") {
|
|
902
|
+
return kind;
|
|
903
|
+
}
|
|
904
|
+
const atomHome = readEnvironmentVariable("ATOM_HOME");
|
|
905
|
+
if (atomHome) {
|
|
906
|
+
// A jar install's ATOM_HOME is its plugins/ directory, which carries lib/;
|
|
907
|
+
// a native sub-package directory does not.
|
|
908
|
+
return safeExistsSync(join(atomHome, "lib")) ? "jar" : kind;
|
|
909
|
+
}
|
|
910
|
+
if (resolveDirectAtomBinaryPath()) {
|
|
911
|
+
return "native";
|
|
912
|
+
}
|
|
913
|
+
const jarPlugins = atomSubPackageCandidates("atom-jar").some((dir) =>
|
|
914
|
+
safeExistsSync(join(dir, "plugins")),
|
|
915
|
+
);
|
|
916
|
+
return jarPlugins ? "jar" : kind;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
/**
|
|
920
|
+
* Whether `ATOM_CMD` launches a jar or follows the platform.
|
|
921
|
+
*
|
|
922
|
+
* The jar launchers (`plugins/bin/atom`, `plugins/bin/atom.bat`) sit next to a
|
|
923
|
+
* `plugins/lib` directory holding atom's jars, so a launcher with that sibling,
|
|
924
|
+
* or any `.bat` (only the jar payload ships one), runs on the JVM whatever the
|
|
925
|
+
* platform is. Everything else, including the npm dispatcher (`node
|
|
926
|
+
* .../index.js`, its `atom`/`atom.cmd` bin shims) and a native binary, follows
|
|
927
|
+
* the platform's kind. A GraalVM `-XX:` heap argument handed to a jar launcher
|
|
928
|
+
* is rejected by atom's option parser before any analysis starts, and a
|
|
929
|
+
* `JAVA_TOOL_OPTIONS` ceiling handed to a native image is silently ignored, so
|
|
930
|
+
* the distinction decides whether the heap is bounded at all.
|
|
931
|
+
*
|
|
932
|
+
* @param {string} command The `ATOM_CMD` value
|
|
933
|
+
* @param {"native"|"jar"} platformKind Kind the platform prefers
|
|
934
|
+
* @returns {"native"|"jar"}
|
|
935
|
+
*/
|
|
936
|
+
export function atomCommandKind(command, platformKind) {
|
|
937
|
+
const [bin, leadingArg] = splitAtomCommand(command);
|
|
938
|
+
const runsScript =
|
|
939
|
+
leadingArg !== undefined && /^node(\.exe)?$/i.test(basename(bin));
|
|
940
|
+
let launcher = runsScript ? leadingArg : bin;
|
|
941
|
+
if (/\.bat$/i.test(launcher)) {
|
|
942
|
+
return "jar";
|
|
943
|
+
}
|
|
944
|
+
try {
|
|
945
|
+
// npm bin entries are symlinks into the package (`/usr/local/bin/atom` ->
|
|
946
|
+
// `.../@appthreat/atom/index.js`), and `/usr/local` has a `lib` of its own.
|
|
947
|
+
launcher = realpathSync(launcher);
|
|
948
|
+
} catch {
|
|
949
|
+
return platformKind;
|
|
950
|
+
}
|
|
951
|
+
const binDir = dirname(launcher);
|
|
952
|
+
if (basename(binDir) !== "bin") {
|
|
953
|
+
return platformKind;
|
|
954
|
+
}
|
|
955
|
+
try {
|
|
956
|
+
const libDir = join(dirname(binDir), "lib");
|
|
957
|
+
if (
|
|
958
|
+
readdirSync(libDir).some(
|
|
959
|
+
(file) => file.startsWith("io.appthreat.atom") && file.endsWith(".jar"),
|
|
960
|
+
)
|
|
961
|
+
) {
|
|
962
|
+
return "jar";
|
|
963
|
+
}
|
|
964
|
+
} catch {
|
|
965
|
+
// no lib directory: not a jar layout
|
|
966
|
+
}
|
|
967
|
+
return platformKind;
|
|
619
968
|
}
|
|
620
969
|
|
|
621
970
|
/**
|
|
@@ -623,8 +972,8 @@ export function atomProviderKind() {
|
|
|
623
972
|
*
|
|
624
973
|
* atom 3's dispatcher unconditionally sets `PHP_PARSER_BIN=<ATOM_HOME>/bin/php-parse`,
|
|
625
974
|
* which for a native sub-package does not exist and also clobbers a caller-set
|
|
626
|
-
* value. cdxgen therefore resolves the real location and forwards it through
|
|
627
|
-
*
|
|
975
|
+
* value. cdxgen therefore resolves the real location and forwards it through the
|
|
976
|
+
* child env (see `buildAtomCommandEnv`); `executeAtom` then spawns the
|
|
628
977
|
* native binary directly for PHP so the dispatcher cannot clobber it (see
|
|
629
978
|
* `resolveDirectAtomBinaryPath`). Resolution order:
|
|
630
979
|
* 1. explicit `PHP_PARSER_BIN` env var (operators / container images)
|
|
@@ -641,6 +990,44 @@ export function resolvePhpParseBin() {
|
|
|
641
990
|
if (readEnvironmentVariable("PHP_PARSER_BIN")) {
|
|
642
991
|
return readEnvironmentVariable("PHP_PARSER_BIN");
|
|
643
992
|
}
|
|
993
|
+
return parsetoolsFile("plugins", "bin", "php-parse");
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
/**
|
|
997
|
+
* Locate the `phpastgen` generator that lets the PHP frontend parse whole
|
|
998
|
+
* directories in one batch.
|
|
999
|
+
*
|
|
1000
|
+
* `php-parse` (forwarded as `PHP_PARSER_BIN`) is the per-file parser: atom
|
|
1001
|
+
* starts a php interpreter for every `.php` file, which on windows turns a
|
|
1002
|
+
* vendored PHP project (thousands of files under `vendor/`) into an hours-long
|
|
1003
|
+
* run that no timeout can rescue. `phpastgen` answers the `--parser-info`
|
|
1004
|
+
* capability probe, so atom's batch path engages, and atom-parsetools 1.9 and
|
|
1005
|
+
* later parse the whole tree, vendor included, with a few interpreters that
|
|
1006
|
+
* each take a chunk of files. It is a Node script, so the frontend launches it
|
|
1007
|
+
* with `node` rather than `php`; an atom that does not know `PHP_ASTGEN_BIN`
|
|
1008
|
+
* simply ignores it and keeps today's per-file behaviour. Resolution order:
|
|
1009
|
+
* 1. explicit `PHP_ASTGEN_BIN` env var (operators / container images)
|
|
1010
|
+
* 2. `@appthreat/atom-parsetools/phpastgen.js` under cdxgen's own
|
|
1011
|
+
* node_modules, then under `GLOBAL_NODE_MODULES_PATH` for global installs
|
|
1012
|
+
*
|
|
1013
|
+
* @returns {string|undefined}
|
|
1014
|
+
*/
|
|
1015
|
+
export function resolvePhpAstgenBin() {
|
|
1016
|
+
if (readEnvironmentVariable("PHP_ASTGEN_BIN")) {
|
|
1017
|
+
return readEnvironmentVariable("PHP_ASTGEN_BIN");
|
|
1018
|
+
}
|
|
1019
|
+
return parsetoolsFile("phpastgen.js");
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
/**
|
|
1023
|
+
* Resolve a file inside the installed `@appthreat/atom-parsetools` package,
|
|
1024
|
+
* searching cdxgen's own node_modules and `GLOBAL_NODE_MODULES_PATH` for global
|
|
1025
|
+
* installs.
|
|
1026
|
+
*
|
|
1027
|
+
* @param {...string} segments Path segments below the package root
|
|
1028
|
+
* @returns {string|undefined}
|
|
1029
|
+
*/
|
|
1030
|
+
export function parsetoolsFile(...segments) {
|
|
644
1031
|
const roots = [dirNameStr];
|
|
645
1032
|
if (readEnvironmentVariable("GLOBAL_NODE_MODULES_PATH")) {
|
|
646
1033
|
roots.push(dirname(readEnvironmentVariable("GLOBAL_NODE_MODULES_PATH")));
|
|
@@ -651,9 +1038,7 @@ export function resolvePhpParseBin() {
|
|
|
651
1038
|
"node_modules",
|
|
652
1039
|
"@appthreat",
|
|
653
1040
|
"atom-parsetools",
|
|
654
|
-
|
|
655
|
-
"bin",
|
|
656
|
-
"php-parse",
|
|
1041
|
+
...segments,
|
|
657
1042
|
);
|
|
658
1043
|
if (safeExistsSync(candidate)) {
|
|
659
1044
|
return candidate;
|
|
@@ -683,6 +1068,25 @@ export function resolveDirectAtomBinaryPath() {
|
|
|
683
1068
|
}
|
|
684
1069
|
const folder = preferredPkg.split("/")[1];
|
|
685
1070
|
const exeName = isWin ? "atom.exe" : "atom";
|
|
1071
|
+
for (const dir of atomSubPackageCandidates(folder)) {
|
|
1072
|
+
const candidate = join(dir, "bin", exeName);
|
|
1073
|
+
if (safeExistsSync(candidate)) {
|
|
1074
|
+
return candidate;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
return undefined;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
/**
|
|
1081
|
+
* Directories where an `@appthreat/atom-*` sub-package may be installed next to
|
|
1082
|
+
* the dispatcher cdxgen uses, for npm, pnpm and global layouts: a sibling of
|
|
1083
|
+
* `@appthreat/atom`, nested under it, in a pnpm virtual store, or a sibling of
|
|
1084
|
+
* the dispatcher's real (symlink-resolved) directory.
|
|
1085
|
+
*
|
|
1086
|
+
* @param {string} folder Sub-package folder name, e.g. `atom-jar`
|
|
1087
|
+
* @returns {string[]} Candidate package directories
|
|
1088
|
+
*/
|
|
1089
|
+
function atomSubPackageCandidates(folder) {
|
|
686
1090
|
const version = readAtomVersion();
|
|
687
1091
|
const roots = [join(dirNameStr, "node_modules")];
|
|
688
1092
|
if (readEnvironmentVariable("GLOBAL_NODE_MODULES_PATH")) {
|
|
@@ -690,18 +1094,9 @@ export function resolveDirectAtomBinaryPath() {
|
|
|
690
1094
|
}
|
|
691
1095
|
const candidates = [];
|
|
692
1096
|
for (const root of roots) {
|
|
693
|
-
candidates.push(join(root, "@appthreat", folder
|
|
1097
|
+
candidates.push(join(root, "@appthreat", folder));
|
|
694
1098
|
candidates.push(
|
|
695
|
-
join(
|
|
696
|
-
root,
|
|
697
|
-
"@appthreat",
|
|
698
|
-
"atom",
|
|
699
|
-
"node_modules",
|
|
700
|
-
"@appthreat",
|
|
701
|
-
folder,
|
|
702
|
-
"bin",
|
|
703
|
-
exeName,
|
|
704
|
-
),
|
|
1099
|
+
join(root, "@appthreat", "atom", "node_modules", "@appthreat", folder),
|
|
705
1100
|
);
|
|
706
1101
|
if (version) {
|
|
707
1102
|
candidates.push(
|
|
@@ -712,18 +1107,19 @@ export function resolveDirectAtomBinaryPath() {
|
|
|
712
1107
|
"node_modules",
|
|
713
1108
|
"@appthreat",
|
|
714
1109
|
folder,
|
|
715
|
-
"bin",
|
|
716
|
-
exeName,
|
|
717
1110
|
),
|
|
718
1111
|
);
|
|
719
1112
|
}
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
1113
|
+
// pnpm links @appthreat/atom to its store entry, where the sub-packages
|
|
1114
|
+
// are its siblings.
|
|
1115
|
+
try {
|
|
1116
|
+
const atomDir = realpathSync(join(root, "@appthreat", "atom"));
|
|
1117
|
+
candidates.push(join(dirname(atomDir), folder));
|
|
1118
|
+
} catch {
|
|
1119
|
+
// not installed under this root
|
|
724
1120
|
}
|
|
725
1121
|
}
|
|
726
|
-
return
|
|
1122
|
+
return [...new Set(candidates)];
|
|
727
1123
|
}
|
|
728
1124
|
|
|
729
1125
|
function readAtomVersion() {
|
|
@@ -874,6 +1270,117 @@ function applyAtomHeapLimit(args, env) {
|
|
|
874
1270
|
}
|
|
875
1271
|
}
|
|
876
1272
|
|
|
1273
|
+
// How long cdxgen waits past atom's own time limit for the dispatcher to stop
|
|
1274
|
+
// the runtime and exit. It covers the dispatcher's SIGTERM-to-SIGKILL grace (10
|
|
1275
|
+
// seconds) and the runtime's exit. A short limit (a test, a smoke run) gets a
|
|
1276
|
+
// quarter of itself instead.
|
|
1277
|
+
const ATOM_STOP_GRACE_MS = 30_000;
|
|
1278
|
+
|
|
1279
|
+
/**
|
|
1280
|
+
* Compute how long atom may run, and how long cdxgen waits for it.
|
|
1281
|
+
*
|
|
1282
|
+
* cdxgen's spawn timeout kills only the process cdxgen started, and for atom
|
|
1283
|
+
* that is the npm dispatcher. The runtime under it (the native binary or the
|
|
1284
|
+
* JVM) used to survive that kill, re-parented and still holding its heap. atom
|
|
1285
|
+
* therefore gets its own limit, `ATOM_TIMEOUT`, which the dispatcher enforces on
|
|
1286
|
+
* the runtime, and cdxgen's spawn timeout sits a grace period beyond it as the
|
|
1287
|
+
* last resort.
|
|
1288
|
+
*
|
|
1289
|
+
* An explicit `ATOM_TIMEOUT` (milliseconds) sets atom's limit independently of
|
|
1290
|
+
* `CDXGEN_TIMEOUT_MS`, which keeps bounding every other tool. Otherwise atom's
|
|
1291
|
+
* limit is `CDXGEN_TIMEOUT_MS` less the grace period.
|
|
1292
|
+
*
|
|
1293
|
+
* @returns {{atomTimeoutMs: number, spawnTimeoutMs: number}} atom's own limit and cdxgen's spawn timeout for it
|
|
1294
|
+
*/
|
|
1295
|
+
export function atomTimeouts() {
|
|
1296
|
+
const graceFor = (ms) => Math.min(ATOM_STOP_GRACE_MS, Math.floor(ms / 4));
|
|
1297
|
+
const configured = Number.parseInt(
|
|
1298
|
+
readEnvironmentVariable("ATOM_TIMEOUT"),
|
|
1299
|
+
10,
|
|
1300
|
+
);
|
|
1301
|
+
if (configured > 0) {
|
|
1302
|
+
return {
|
|
1303
|
+
atomTimeoutMs: configured,
|
|
1304
|
+
spawnTimeoutMs: configured + graceFor(configured),
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1307
|
+
return {
|
|
1308
|
+
atomTimeoutMs: TIMEOUT_MS - graceFor(TIMEOUT_MS),
|
|
1309
|
+
spawnTimeoutMs: TIMEOUT_MS,
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
// The status the atom dispatcher exits with when ATOM_TIMEOUT stops atom.
|
|
1314
|
+
const ATOM_TIMEOUT_EXIT_CODE = 124;
|
|
1315
|
+
|
|
1316
|
+
/**
|
|
1317
|
+
* Reap everything a timed-out atom run left behind on Windows.
|
|
1318
|
+
*
|
|
1319
|
+
* cdxgen starts atom through a shell there (`shell: isWin`), so the spawn
|
|
1320
|
+
* timeout terminates only that `cmd.exe`, and the runtime and every parser
|
|
1321
|
+
* worker under it (one `php.exe` per file for the per-file PHP frontend) keep
|
|
1322
|
+
* running against the next slice of the same run. See
|
|
1323
|
+
* {@link reapProcessTree} for how they are found. A chain broken by an
|
|
1324
|
+
* intermediate that already exited (the npm dispatcher stopping its runtime
|
|
1325
|
+
* itself) cannot be followed, and atom 4 stops its helpers once
|
|
1326
|
+
* `ATOM_PARENT_PID` is gone.
|
|
1327
|
+
*
|
|
1328
|
+
* @param {number|undefined} rootPid pid of the process cdxgen spawned
|
|
1329
|
+
* @param {number} startedAt Epoch milliseconds at which the run started
|
|
1330
|
+
* @param {number} [endedAt] Epoch milliseconds at which the spawned process ended
|
|
1331
|
+
* @returns {number[]} The pids that were stopped
|
|
1332
|
+
*/
|
|
1333
|
+
export function reapAtomProcessTree(rootPid, startedAt, endedAt = Date.now()) {
|
|
1334
|
+
return reapProcessTree(rootPid, startedAt, endedAt, "atom");
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
/**
|
|
1338
|
+
* Whether an atom run ended because it ran out of time.
|
|
1339
|
+
*
|
|
1340
|
+
* The dispatcher reports its own limit with status 124. cdxgen's spawn timeout
|
|
1341
|
+
* shows up as `ETIMEDOUT`. A dispatcher older than the status code stops atom
|
|
1342
|
+
* with SIGTERM and passes on how the runtime ended: 1, or the runtime's own
|
|
1343
|
+
* exit on SIGTERM (143) or SIGKILL (137), which is what atom 3.1 reports. Those
|
|
1344
|
+
* statuses at or past the limit count as well, unless atom's own output shows
|
|
1345
|
+
* it failed for another reason first (an exhausted heap, a crash), which
|
|
1346
|
+
* deserves its own diagnosis. Missing one leaves the half-written atom behind,
|
|
1347
|
+
* and the next slice of the run fails to load it.
|
|
1348
|
+
*
|
|
1349
|
+
* @param {Object} result spawnSync result
|
|
1350
|
+
* @param {number} elapsedMs How long the run took
|
|
1351
|
+
* @param {number} atomTimeoutMs atom's time limit
|
|
1352
|
+
* @returns {boolean} true when the run hit a time limit
|
|
1353
|
+
*/
|
|
1354
|
+
export function atomRunTimedOut(result, elapsedMs, atomTimeoutMs) {
|
|
1355
|
+
if (result?.status === ATOM_TIMEOUT_EXIT_CODE) {
|
|
1356
|
+
return true;
|
|
1357
|
+
}
|
|
1358
|
+
if (result?.error?.code === "ETIMEDOUT") {
|
|
1359
|
+
return true;
|
|
1360
|
+
}
|
|
1361
|
+
if (
|
|
1362
|
+
!ATOM_STOPPED_STATUSES.includes(result?.status) ||
|
|
1363
|
+
elapsedMs < atomTimeoutMs
|
|
1364
|
+
) {
|
|
1365
|
+
return false;
|
|
1366
|
+
}
|
|
1367
|
+
const output = `${result.stdout || ""}\n${result.stderr || ""}`;
|
|
1368
|
+
return !ATOM_FAILURE_MARKERS.some((marker) => output.includes(marker));
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
// How a runtime stopped by an older dispatcher at ATOM_TIMEOUT exits: the
|
|
1372
|
+
// dispatcher's own 1, or the runtime's exit on SIGTERM (128 + 15) or SIGKILL
|
|
1373
|
+
// (128 + 9) passed through.
|
|
1374
|
+
const ATOM_STOPPED_STATUSES = [1, 137, 143];
|
|
1375
|
+
|
|
1376
|
+
// Output that means atom failed by itself, whatever the clock says.
|
|
1377
|
+
const ATOM_FAILURE_MARKERS = [
|
|
1378
|
+
"OutOfMemoryError",
|
|
1379
|
+
"A fatal error has been detected by the Java Runtime Environment",
|
|
1380
|
+
"The crash happened outside the Java Virtual Machine in native code",
|
|
1381
|
+
"Failure: ",
|
|
1382
|
+
];
|
|
1383
|
+
|
|
877
1384
|
/**
|
|
878
1385
|
* Execute the atom tool against a source directory or file with the given arguments.
|
|
879
1386
|
*
|
|
@@ -914,14 +1421,12 @@ export function executeAtom(src, args, extra_env = {}) {
|
|
|
914
1421
|
...extra_env,
|
|
915
1422
|
};
|
|
916
1423
|
// Bound the heap while argv still starts at atom's own arguments: the
|
|
917
|
-
//
|
|
1424
|
+
// split below prepends the launcher script, which has to stay first.
|
|
918
1425
|
applyAtomHeapLimit(args, env);
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
args.unshift(tmpA[1]);
|
|
924
|
-
}
|
|
1426
|
+
const [resolvedBin, leadingArg] = splitAtomCommand(ATOM_BIN);
|
|
1427
|
+
if (leadingArg !== undefined) {
|
|
1428
|
+
ATOM_BIN = resolvedBin;
|
|
1429
|
+
args.unshift(leadingArg);
|
|
925
1430
|
}
|
|
926
1431
|
if (DEBUG_MODE) {
|
|
927
1432
|
console.log("Executing", ATOM_BIN);
|
|
@@ -952,12 +1457,35 @@ export function executeAtom(src, args, extra_env = {}) {
|
|
|
952
1457
|
".bin",
|
|
953
1458
|
)}`;
|
|
954
1459
|
}
|
|
1460
|
+
const { atomTimeoutMs, spawnTimeoutMs } = atomTimeouts();
|
|
1461
|
+
env.ATOM_TIMEOUT = String(atomTimeoutMs);
|
|
1462
|
+
// Name cdxgen as the supervisor: the dispatcher, and atom under it, stop when
|
|
1463
|
+
// cdxgen is gone (killed by a caller's own timeout, say) instead of analysing
|
|
1464
|
+
// on for nobody.
|
|
1465
|
+
env.ATOM_PARENT_PID = String(process.pid);
|
|
1466
|
+
const startedAt = Date.now();
|
|
955
1467
|
const result = safeSpawnSync(ATOM_BIN, args, {
|
|
956
1468
|
cwd,
|
|
957
1469
|
shell: isWin,
|
|
958
1470
|
killSignal: "SIGKILL",
|
|
959
1471
|
env,
|
|
1472
|
+
timeout: spawnTimeoutMs,
|
|
960
1473
|
});
|
|
1474
|
+
const endedAt = Date.now();
|
|
1475
|
+
if (atomRunTimedOut(result, endedAt - startedAt, atomTimeoutMs)) {
|
|
1476
|
+
console.warn(
|
|
1477
|
+
`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).`,
|
|
1478
|
+
);
|
|
1479
|
+
reapAtomProcessTree(result.pid, startedAt, endedAt);
|
|
1480
|
+
// A stopped atom leaves its graph half written, and the next slice of the
|
|
1481
|
+
// same run would try to reuse it and fail to load it.
|
|
1482
|
+
const atomFileIndex = args.indexOf("-o");
|
|
1483
|
+
const atomFile = atomFileIndex >= 0 ? args[atomFileIndex + 1] : undefined;
|
|
1484
|
+
if (atomFile && safeExistsSync(atomFile)) {
|
|
1485
|
+
safeRmSync(atomFile, { recursive: true, force: true });
|
|
1486
|
+
}
|
|
1487
|
+
return false;
|
|
1488
|
+
}
|
|
961
1489
|
const isJarKind = atomProviderKind() === "jar";
|
|
962
1490
|
if (result.stderr) {
|
|
963
1491
|
if (
|
|
@@ -1036,6 +1564,130 @@ export function executeAtom(src, args, extra_env = {}) {
|
|
|
1036
1564
|
return isSupported && !result.error && result.status === 0;
|
|
1037
1565
|
}
|
|
1038
1566
|
|
|
1567
|
+
const CHUNK_MTIME_SLACK_MS = 2000;
|
|
1568
|
+
|
|
1569
|
+
/**
|
|
1570
|
+
* The numbered chunks atom wrote beside a reachables slices file.
|
|
1571
|
+
*
|
|
1572
|
+
* atom writes reachables at most 1000 flows per file: `<base>.json`, then
|
|
1573
|
+
* `<base>_1.json`, `<base>_2.json` and so on. Chunks are taken until the first
|
|
1574
|
+
* gap. A chunk older than the base file was left by an earlier run with more
|
|
1575
|
+
* flows (atom before 4.0.0 did not remove those), so it ends the sequence
|
|
1576
|
+
* instead of being merged.
|
|
1577
|
+
*
|
|
1578
|
+
* @param {string} slicesFile Path of the base reachables slices file
|
|
1579
|
+
* @returns {string[]} Chunk file paths, in order
|
|
1580
|
+
*/
|
|
1581
|
+
export function reachablesChunkFiles(slicesFile) {
|
|
1582
|
+
const baseMtimeMs = mtimeMsOf(slicesFile);
|
|
1583
|
+
if (!slicesFile?.endsWith(".json") || baseMtimeMs === undefined) {
|
|
1584
|
+
return [];
|
|
1585
|
+
}
|
|
1586
|
+
const stem = slicesFile.slice(0, -".json".length);
|
|
1587
|
+
// atom writes the base file first, so its chunks are never older. The slack
|
|
1588
|
+
// covers filesystems that store modification times in whole seconds or two;
|
|
1589
|
+
// it assumes no earlier run wrote chunks to the same path within that window.
|
|
1590
|
+
// atom 4 and cdxgen both remove old chunks before atom writes new ones, so
|
|
1591
|
+
// the mtime test only matters for slices files produced elsewhere.
|
|
1592
|
+
const oldestChunkMtimeMs = baseMtimeMs - CHUNK_MTIME_SLACK_MS;
|
|
1593
|
+
const chunks = [];
|
|
1594
|
+
for (let index = 1; ; index++) {
|
|
1595
|
+
const chunk = `${stem}_${index}.json`;
|
|
1596
|
+
const chunkMtimeMs = mtimeMsOf(chunk);
|
|
1597
|
+
if (chunkMtimeMs === undefined || chunkMtimeMs < oldestChunkMtimeMs) {
|
|
1598
|
+
break;
|
|
1599
|
+
}
|
|
1600
|
+
chunks.push(chunk);
|
|
1601
|
+
}
|
|
1602
|
+
return chunks;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
// A file that disappears between listing and stat ends the sequence, not the run.
|
|
1606
|
+
function mtimeMsOf(file) {
|
|
1607
|
+
if (!file || !safeExistsSync(file)) {
|
|
1608
|
+
return undefined;
|
|
1609
|
+
}
|
|
1610
|
+
try {
|
|
1611
|
+
return statSync(file).mtimeMs;
|
|
1612
|
+
} catch {
|
|
1613
|
+
return undefined;
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
/**
|
|
1618
|
+
* Remove the numbered chunks beside a reachables slices file, before atom
|
|
1619
|
+
* writes a new set there, so no chunk of an earlier run survives next to it.
|
|
1620
|
+
*
|
|
1621
|
+
* @param {string} slicesFile Path of the base reachables slices file
|
|
1622
|
+
*/
|
|
1623
|
+
export function removeReachablesChunkFiles(slicesFile) {
|
|
1624
|
+
if (!slicesFile?.endsWith(".json")) {
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
const stem = slicesFile.slice(0, -".json".length);
|
|
1628
|
+
for (let index = 1; safeExistsSync(`${stem}_${index}.json`); index++) {
|
|
1629
|
+
safeRmSync(`${stem}_${index}.json`, { force: true });
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
/**
|
|
1634
|
+
* Read an atom slices file, or warn and return `undefined` when it cannot be
|
|
1635
|
+
* parsed. A run stopped while writing (a timeout, a kill) can leave a
|
|
1636
|
+
* truncated file, and the evidence from the other slices is still worth
|
|
1637
|
+
* keeping.
|
|
1638
|
+
*
|
|
1639
|
+
* @param {string} slicesFile Path of the slices file
|
|
1640
|
+
* @param {string} sliceType Slice type, for the warning
|
|
1641
|
+
* @returns {*} The parsed slices, or `undefined`
|
|
1642
|
+
*/
|
|
1643
|
+
export function readSlicesFile(slicesFile, sliceType) {
|
|
1644
|
+
if (!slicesFile || !safeExistsSync(slicesFile)) {
|
|
1645
|
+
return undefined;
|
|
1646
|
+
}
|
|
1647
|
+
try {
|
|
1648
|
+
return readJsonFile(slicesFile);
|
|
1649
|
+
} catch (e) {
|
|
1650
|
+
console.warn(
|
|
1651
|
+
`WARN: Ignoring the ${sliceType} slices file ${slicesFile}, which is incomplete or not valid JSON (${e.message}).`,
|
|
1652
|
+
);
|
|
1653
|
+
return undefined;
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
/**
|
|
1658
|
+
* Read a reachables slices file together with its chunks.
|
|
1659
|
+
*
|
|
1660
|
+
* Reading only the base file silently dropped every flow past the first
|
|
1661
|
+
* thousand. A chunk that cannot be parsed ends the sequence, keeping the flows
|
|
1662
|
+
* read before it.
|
|
1663
|
+
*
|
|
1664
|
+
* @param {string} slicesFile Path of the base reachables slices file
|
|
1665
|
+
* @returns {Object[]|Object|undefined} Every flow, in the base file's shape (an array, or an object with `reachables`)
|
|
1666
|
+
*/
|
|
1667
|
+
export function readReachablesSlices(slicesFile) {
|
|
1668
|
+
const data = readSlicesFile(slicesFile, "reachables");
|
|
1669
|
+
if (data === undefined) {
|
|
1670
|
+
return undefined;
|
|
1671
|
+
}
|
|
1672
|
+
const chunks = reachablesChunkFiles(slicesFile);
|
|
1673
|
+
if (!chunks.length) {
|
|
1674
|
+
return data;
|
|
1675
|
+
}
|
|
1676
|
+
const flowsOf = (slices) =>
|
|
1677
|
+
Array.isArray(slices) ? slices : slices?.reachables || [];
|
|
1678
|
+
const flows = [...flowsOf(data)];
|
|
1679
|
+
for (const chunk of chunks) {
|
|
1680
|
+
const chunkData = readSlicesFile(chunk, "reachables");
|
|
1681
|
+
if (chunkData === undefined) {
|
|
1682
|
+
break;
|
|
1683
|
+
}
|
|
1684
|
+
for (const flow of flowsOf(chunkData)) {
|
|
1685
|
+
flows.push(flow);
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
return Array.isArray(data) || !data ? flows : { ...data, reachables: flows };
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1039
1691
|
/**
|
|
1040
1692
|
* Find the imported modules in the application with atom parsedeps command
|
|
1041
1693
|
*
|
|
@@ -1068,6 +1720,14 @@ export function findAppModules(
|
|
|
1068
1720
|
"--slice-outfile",
|
|
1069
1721
|
resolve(slicesFile),
|
|
1070
1722
|
];
|
|
1723
|
+
// The header languages parse without function bodies. Their AST cache, kept
|
|
1724
|
+
// in <src>/.chen, would be replayed by a later full C/C++ run over the same
|
|
1725
|
+
// directory (evinse, or `-t c --deep`) on atom releases that do not key the
|
|
1726
|
+
// cache on that difference, leaving that run with no method bodies.
|
|
1727
|
+
if (ATOM_HEADER_LANGUAGES.has(`${language}`.toLowerCase())) {
|
|
1728
|
+
args.push("--no-ast-cache");
|
|
1729
|
+
}
|
|
1730
|
+
args.push(...atomCompileCommandsArgs(src, language, options));
|
|
1071
1731
|
args.push(resolve(src));
|
|
1072
1732
|
executeAtom(src, args, buildAtomCommandEnv(options, language));
|
|
1073
1733
|
if (safeExistsSync(slicesFile)) {
|