@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
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure reader for JSON compilation databases (`compile_commands.json`).
|
|
3
|
+
*
|
|
4
|
+
* Each entry names a translation unit (`file`), the directory its command ran
|
|
5
|
+
* in (`directory`) and the command, either as an argument list (`arguments`)
|
|
6
|
+
* or as one command line (`command`) split with the quoting rules of the
|
|
7
|
+
* shell it was written for. From the entries this module derives the
|
|
8
|
+
* compilers a build uses, the include directories each unit is compiled
|
|
9
|
+
* with, and the security-hardening options in effect.
|
|
10
|
+
*
|
|
11
|
+
* Layer 1: data in, data out. Locating and reading the file, and running a
|
|
12
|
+
* compiler to learn its version, belong to the callers.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { basename, isAbsolute, join, normalize } from "node:path";
|
|
16
|
+
|
|
17
|
+
/** Launchers that run the compiler named by the next argument. */
|
|
18
|
+
const COMPILER_LAUNCHERS = new Set([
|
|
19
|
+
"ccache",
|
|
20
|
+
"sccache",
|
|
21
|
+
"distcc",
|
|
22
|
+
"icecc",
|
|
23
|
+
"buildcache",
|
|
24
|
+
"env",
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Split a POSIX shell command line into words: single quotes keep everything
|
|
29
|
+
* literally, double quotes keep everything except `\"`, `\\`, `\$` and
|
|
30
|
+
* `` \` ``, and a backslash outside quotes escapes the next character.
|
|
31
|
+
*
|
|
32
|
+
* @param {string} command Command line
|
|
33
|
+
* @returns {string[]} Words
|
|
34
|
+
*/
|
|
35
|
+
export function splitPosixCommand(command) {
|
|
36
|
+
const words = [];
|
|
37
|
+
let current = "";
|
|
38
|
+
let inWord = false;
|
|
39
|
+
let quote = "";
|
|
40
|
+
for (let i = 0; i < command.length; i++) {
|
|
41
|
+
const c = command[i];
|
|
42
|
+
if (quote === "'") {
|
|
43
|
+
if (c === "'") {
|
|
44
|
+
quote = "";
|
|
45
|
+
} else {
|
|
46
|
+
current += c;
|
|
47
|
+
}
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (quote === '"') {
|
|
51
|
+
if (c === '"') {
|
|
52
|
+
quote = "";
|
|
53
|
+
} else if (
|
|
54
|
+
c === "\\" &&
|
|
55
|
+
i + 1 < command.length &&
|
|
56
|
+
'"\\$`'.includes(command[i + 1])
|
|
57
|
+
) {
|
|
58
|
+
current += command[++i];
|
|
59
|
+
} else {
|
|
60
|
+
current += c;
|
|
61
|
+
}
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (c === "'" || c === '"') {
|
|
65
|
+
quote = c;
|
|
66
|
+
inWord = true;
|
|
67
|
+
} else if (c === "\\" && i + 1 < command.length) {
|
|
68
|
+
current += command[++i];
|
|
69
|
+
inWord = true;
|
|
70
|
+
} else if (c === " " || c === "\t" || c === "\n" || c === "\r") {
|
|
71
|
+
if (inWord) {
|
|
72
|
+
words.push(current);
|
|
73
|
+
current = "";
|
|
74
|
+
inWord = false;
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
current += c;
|
|
78
|
+
inWord = true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (inWord) {
|
|
82
|
+
words.push(current);
|
|
83
|
+
}
|
|
84
|
+
return words;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Split a Windows command line into words as the Microsoft C runtime does:
|
|
89
|
+
* double quotes group, `\"` is a literal quote, and backslashes are literal
|
|
90
|
+
* unless they precede a quote.
|
|
91
|
+
*
|
|
92
|
+
* @param {string} command Command line
|
|
93
|
+
* @returns {string[]} Words
|
|
94
|
+
*/
|
|
95
|
+
export function splitWindowsCommand(command) {
|
|
96
|
+
const words = [];
|
|
97
|
+
let current = "";
|
|
98
|
+
let inWord = false;
|
|
99
|
+
let inQuotes = false;
|
|
100
|
+
for (let i = 0; i < command.length; i++) {
|
|
101
|
+
const c = command[i];
|
|
102
|
+
if (c === "\\") {
|
|
103
|
+
let slashes = 0;
|
|
104
|
+
while (i < command.length && command[i] === "\\") {
|
|
105
|
+
slashes++;
|
|
106
|
+
i++;
|
|
107
|
+
}
|
|
108
|
+
if (i < command.length && command[i] === '"') {
|
|
109
|
+
current += "\\".repeat(Math.floor(slashes / 2));
|
|
110
|
+
if (slashes % 2 === 1) {
|
|
111
|
+
current += '"';
|
|
112
|
+
} else {
|
|
113
|
+
inQuotes = !inQuotes;
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
current += "\\".repeat(slashes);
|
|
117
|
+
i--;
|
|
118
|
+
}
|
|
119
|
+
inWord = true;
|
|
120
|
+
} else if (c === '"') {
|
|
121
|
+
inQuotes = !inQuotes;
|
|
122
|
+
inWord = true;
|
|
123
|
+
} else if ((c === " " || c === "\t") && !inQuotes) {
|
|
124
|
+
if (inWord) {
|
|
125
|
+
words.push(current);
|
|
126
|
+
current = "";
|
|
127
|
+
inWord = false;
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
current += c;
|
|
131
|
+
inWord = true;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (inWord) {
|
|
135
|
+
words.push(current);
|
|
136
|
+
}
|
|
137
|
+
return words;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** The file name of a driver path, lower case, without `.exe`. */
|
|
141
|
+
function driverName(driver) {
|
|
142
|
+
const name = basename(`${driver}`.replaceAll("\\", "/")).toLowerCase();
|
|
143
|
+
return name.endsWith(".exe") ? name.slice(0, -4) : name;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* The compiler family a driver's name implies.
|
|
148
|
+
*
|
|
149
|
+
* @param {string} driver Driver path or name
|
|
150
|
+
* @returns {string} `msvc`, `clang-cl`, `clang`, `gcc`, `nvcc`, `icx`,
|
|
151
|
+
* `icc`, `nvhpc`, `edg` or `unknown`
|
|
152
|
+
*/
|
|
153
|
+
export function compilerFamilyOfName(driver) {
|
|
154
|
+
const name = driverName(driver);
|
|
155
|
+
if (name === "cl") {
|
|
156
|
+
return "msvc";
|
|
157
|
+
}
|
|
158
|
+
if (name === "clang-cl" || name.endsWith("-clang-cl")) {
|
|
159
|
+
return "clang-cl";
|
|
160
|
+
}
|
|
161
|
+
if (name === "nvcc") {
|
|
162
|
+
return "nvcc";
|
|
163
|
+
}
|
|
164
|
+
if (["icx", "icpx", "icx-cl", "dpcpp"].includes(name)) {
|
|
165
|
+
return "icx";
|
|
166
|
+
}
|
|
167
|
+
if (["icc", "icpc", "icl"].includes(name)) {
|
|
168
|
+
return "icc";
|
|
169
|
+
}
|
|
170
|
+
if (["nvc", "nvc++", "pgcc", "pgc++"].includes(name)) {
|
|
171
|
+
return "nvhpc";
|
|
172
|
+
}
|
|
173
|
+
if (["cpfe", "eccp", "edgcpfe"].includes(name)) {
|
|
174
|
+
return "edg";
|
|
175
|
+
}
|
|
176
|
+
if (name.includes("clang")) {
|
|
177
|
+
return "clang";
|
|
178
|
+
}
|
|
179
|
+
if (
|
|
180
|
+
name.includes("gcc") ||
|
|
181
|
+
name.includes("g++") ||
|
|
182
|
+
name === "cc" ||
|
|
183
|
+
name === "c++" ||
|
|
184
|
+
name.endsWith("-cc") ||
|
|
185
|
+
name.endsWith("-c++")
|
|
186
|
+
) {
|
|
187
|
+
return "gcc";
|
|
188
|
+
}
|
|
189
|
+
return "unknown";
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Families whose drivers take MSVC-style options. */
|
|
193
|
+
const MSVC_STYLE = new Set(["msvc", "clang-cl"]);
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* The argument list of a database entry, with any compiler launcher
|
|
197
|
+
* (`ccache`, `sccache`, ...) removed so the first argument is the compiler.
|
|
198
|
+
*
|
|
199
|
+
* @param {Object} entry Database entry
|
|
200
|
+
* @returns {string[]} Arguments
|
|
201
|
+
*/
|
|
202
|
+
export function entryArguments(entry) {
|
|
203
|
+
let args;
|
|
204
|
+
if (Array.isArray(entry?.arguments)) {
|
|
205
|
+
args = entry.arguments.filter((a) => typeof a === "string");
|
|
206
|
+
} else if (typeof entry?.command === "string") {
|
|
207
|
+
const firstWord = entry.command.trimStart().split(/\s/, 1)[0] || "";
|
|
208
|
+
const family = compilerFamilyOfName(firstWord.replaceAll('"', ""));
|
|
209
|
+
args = MSVC_STYLE.has(family)
|
|
210
|
+
? splitWindowsCommand(entry.command)
|
|
211
|
+
: splitPosixCommand(entry.command);
|
|
212
|
+
} else {
|
|
213
|
+
return [];
|
|
214
|
+
}
|
|
215
|
+
let start = 0;
|
|
216
|
+
while (
|
|
217
|
+
start < args.length - 1 &&
|
|
218
|
+
COMPILER_LAUNCHERS.has(driverName(args[start]))
|
|
219
|
+
) {
|
|
220
|
+
start++;
|
|
221
|
+
// `env NAME=value cc ...`
|
|
222
|
+
while (
|
|
223
|
+
start < args.length - 1 &&
|
|
224
|
+
/^[A-Za-z_][A-Za-z0-9_]*=/.test(args[start])
|
|
225
|
+
) {
|
|
226
|
+
start++;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return args.slice(start);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* The language a unit is compiled as: from `-x`/`/TP`/`/TC`, the driver
|
|
234
|
+
* name, then the file extension.
|
|
235
|
+
*
|
|
236
|
+
* @param {string[]} args Arguments, compiler first
|
|
237
|
+
* @param {string} file Translation unit
|
|
238
|
+
* @returns {string} `c`, `c++`, `cuda` or `unknown`
|
|
239
|
+
*/
|
|
240
|
+
export function unitLanguage(args, file) {
|
|
241
|
+
for (let i = 1; i < args.length; i++) {
|
|
242
|
+
const a = args[i];
|
|
243
|
+
const value =
|
|
244
|
+
a === "-x" ? args[i + 1] : a.startsWith("-x") ? a.slice(2) : undefined;
|
|
245
|
+
if (value) {
|
|
246
|
+
if (value.startsWith("c++")) {
|
|
247
|
+
return "c++";
|
|
248
|
+
}
|
|
249
|
+
if (value === "c" || value.startsWith("c-")) {
|
|
250
|
+
return "c";
|
|
251
|
+
}
|
|
252
|
+
if (value === "cuda") {
|
|
253
|
+
return "cuda";
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (a === "/TP" || a === "-TP") {
|
|
257
|
+
return "c++";
|
|
258
|
+
}
|
|
259
|
+
if (a === "/TC" || a === "-TC") {
|
|
260
|
+
return "c";
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
const name = driverName(args[0] || "");
|
|
264
|
+
if (name === "nvcc") {
|
|
265
|
+
return "cuda";
|
|
266
|
+
}
|
|
267
|
+
if (name.includes("++") || name.endsWith("cpp") || name === "icpc") {
|
|
268
|
+
return "c++";
|
|
269
|
+
}
|
|
270
|
+
const lower = `${file || ""}`.toLowerCase();
|
|
271
|
+
const dot = lower.lastIndexOf(".");
|
|
272
|
+
const ext = dot >= 0 ? lower.slice(dot) : "";
|
|
273
|
+
if (ext === ".c") {
|
|
274
|
+
return `${file}`.endsWith(".C") ? "c++" : "c";
|
|
275
|
+
}
|
|
276
|
+
if (ext === ".cu") {
|
|
277
|
+
return "cuda";
|
|
278
|
+
}
|
|
279
|
+
if ([".cc", ".cpp", ".cxx", ".c++", ".cppm", ".ixx", ".mm"].includes(ext)) {
|
|
280
|
+
return "c++";
|
|
281
|
+
}
|
|
282
|
+
return "unknown";
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* The include directories a unit is compiled with, as absolute paths.
|
|
287
|
+
*
|
|
288
|
+
* @param {string[]} args Arguments, compiler first
|
|
289
|
+
* @param {string} directory The entry's working directory
|
|
290
|
+
* @returns {string[]} Directories, in command order
|
|
291
|
+
*/
|
|
292
|
+
export function includeDirectories(args, directory) {
|
|
293
|
+
const msvc = MSVC_STYLE.has(compilerFamilyOfName(args[0] || ""));
|
|
294
|
+
const dirs = [];
|
|
295
|
+
const add = (d) => {
|
|
296
|
+
if (!d) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const unquoted = d.replaceAll('"', "");
|
|
300
|
+
dirs.push(
|
|
301
|
+
normalize(
|
|
302
|
+
isAbsolute(unquoted) || !directory
|
|
303
|
+
? unquoted
|
|
304
|
+
: join(directory, unquoted),
|
|
305
|
+
),
|
|
306
|
+
);
|
|
307
|
+
};
|
|
308
|
+
for (let i = 1; i < args.length; i++) {
|
|
309
|
+
const a = args[i];
|
|
310
|
+
for (const option of ["-iquote", "-isystem", "-idirafter", "-I"]) {
|
|
311
|
+
if (a === option) {
|
|
312
|
+
add(args[++i]);
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
if (a.startsWith(option)) {
|
|
316
|
+
add(a.slice(option.length).replace(/^=/, ""));
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (msvc && (a.startsWith("/I") || a.startsWith("/external:I"))) {
|
|
321
|
+
const option = a.startsWith("/I") ? "/I" : "/external:I";
|
|
322
|
+
if (a === option) {
|
|
323
|
+
add(args[++i]);
|
|
324
|
+
} else {
|
|
325
|
+
add(a.slice(option.length));
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return dirs;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* The security-hardening settings an argument list turns on or off. Later
|
|
334
|
+
* options override earlier ones, as they do for the compiler.
|
|
335
|
+
*
|
|
336
|
+
* @param {string[]} args Compiler or linker arguments
|
|
337
|
+
* @returns {Map<string, string>} Setting name to value:
|
|
338
|
+
* `fortifySource` (level), `stackProtector` (`on`, `strong`, `all`,
|
|
339
|
+
* `off`), `pie` (`on`/`off`), `relro` (`partial`/`full`, from `-z relro`
|
|
340
|
+
* and `-z now`), `cfProtection` (`full`, `branch`, `return`, `none`),
|
|
341
|
+
* `sanitizers` (comma-separated), `glibcxxAssertions` (`on`),
|
|
342
|
+
* `stackClashProtection` (`on`/`off`), `msvcBufferSecurityCheck` (`/GS`
|
|
343
|
+
* `on`/`off`), `msvcControlFlowGuard` (`on`/`off`)
|
|
344
|
+
*/
|
|
345
|
+
export function hardeningSettings(args) {
|
|
346
|
+
const settings = new Map();
|
|
347
|
+
const sanitizers = new Set();
|
|
348
|
+
let relro = false;
|
|
349
|
+
let now = false;
|
|
350
|
+
const linkerWords = [];
|
|
351
|
+
for (let i = 0; i < args.length; i++) {
|
|
352
|
+
const a = args[i];
|
|
353
|
+
if (a.startsWith("-Wl,")) {
|
|
354
|
+
linkerWords.push(...a.slice(4).split(","));
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
if (a === "-z" && i + 1 < args.length) {
|
|
358
|
+
linkerWords.push("-z", args[++i]);
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
const define =
|
|
362
|
+
a === "-D" || a === "/D"
|
|
363
|
+
? args[i + 1]
|
|
364
|
+
: /^[-/]D/.test(a)
|
|
365
|
+
? a.slice(2)
|
|
366
|
+
: undefined;
|
|
367
|
+
if (define !== undefined) {
|
|
368
|
+
if (a === "-D" || a === "/D") {
|
|
369
|
+
i++;
|
|
370
|
+
}
|
|
371
|
+
const [name, value] = define.split("=", 2);
|
|
372
|
+
if (name === "_FORTIFY_SOURCE") {
|
|
373
|
+
settings.set("fortifySource", value === undefined ? "1" : value);
|
|
374
|
+
} else if (name === "_GLIBCXX_ASSERTIONS") {
|
|
375
|
+
settings.set("glibcxxAssertions", "on");
|
|
376
|
+
}
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
if (/^[-/]U_FORTIFY_SOURCE$/.test(a)) {
|
|
380
|
+
settings.delete("fortifySource");
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
if (a === "-fstack-protector") {
|
|
384
|
+
settings.set("stackProtector", "on");
|
|
385
|
+
} else if (a === "-fstack-protector-strong") {
|
|
386
|
+
settings.set("stackProtector", "strong");
|
|
387
|
+
} else if (a === "-fstack-protector-all") {
|
|
388
|
+
settings.set("stackProtector", "all");
|
|
389
|
+
} else if (a === "-fno-stack-protector") {
|
|
390
|
+
settings.set("stackProtector", "off");
|
|
391
|
+
} else if (a === "-fPIE" || a === "-fpie" || a === "-pie") {
|
|
392
|
+
settings.set("pie", "on");
|
|
393
|
+
} else if (a === "-fno-pie" || a === "-fno-PIE" || a === "-no-pie") {
|
|
394
|
+
settings.set("pie", "off");
|
|
395
|
+
} else if (a === "-fcf-protection") {
|
|
396
|
+
settings.set("cfProtection", "full");
|
|
397
|
+
} else if (a.startsWith("-fcf-protection=")) {
|
|
398
|
+
settings.set("cfProtection", a.slice("-fcf-protection=".length));
|
|
399
|
+
} else if (a.startsWith("-fsanitize=")) {
|
|
400
|
+
for (const s of a.slice("-fsanitize=".length).split(",")) {
|
|
401
|
+
if (s) {
|
|
402
|
+
sanitizers.add(s);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
} else if (a.startsWith("-fno-sanitize=")) {
|
|
406
|
+
for (const s of a.slice("-fno-sanitize=".length).split(",")) {
|
|
407
|
+
sanitizers.delete(s);
|
|
408
|
+
}
|
|
409
|
+
} else if (a === "-fstack-clash-protection") {
|
|
410
|
+
settings.set("stackClashProtection", "on");
|
|
411
|
+
} else if (a === "-fno-stack-clash-protection") {
|
|
412
|
+
settings.set("stackClashProtection", "off");
|
|
413
|
+
} else if (a === "/GS" || a === "-GS") {
|
|
414
|
+
settings.set("msvcBufferSecurityCheck", "on");
|
|
415
|
+
} else if (a === "/GS-" || a === "-GS-") {
|
|
416
|
+
settings.set("msvcBufferSecurityCheck", "off");
|
|
417
|
+
} else if (a === "/guard:cf" || a === "-guard:cf") {
|
|
418
|
+
settings.set("msvcControlFlowGuard", "on");
|
|
419
|
+
} else if (a === "/guard:cf-" || a === "-guard:cf-") {
|
|
420
|
+
settings.set("msvcControlFlowGuard", "off");
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
for (let i = 0; i < linkerWords.length; i++) {
|
|
424
|
+
const w = linkerWords[i];
|
|
425
|
+
const z =
|
|
426
|
+
w === "-z"
|
|
427
|
+
? linkerWords[++i]
|
|
428
|
+
: w.startsWith("-z")
|
|
429
|
+
? w.slice(2)
|
|
430
|
+
: undefined;
|
|
431
|
+
if (z === "relro") {
|
|
432
|
+
relro = true;
|
|
433
|
+
} else if (z === "norelro") {
|
|
434
|
+
relro = false;
|
|
435
|
+
now = false;
|
|
436
|
+
} else if (z === "now") {
|
|
437
|
+
now = true;
|
|
438
|
+
} else if (w === "-pie" || w === "--pie") {
|
|
439
|
+
settings.set("pie", "on");
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
if (relro) {
|
|
443
|
+
settings.set("relro", now ? "full" : "partial");
|
|
444
|
+
}
|
|
445
|
+
if (sanitizers.size) {
|
|
446
|
+
settings.set("sanitizers", [...sanitizers].sort().join(","));
|
|
447
|
+
}
|
|
448
|
+
return settings;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Summarise a compilation database.
|
|
453
|
+
*
|
|
454
|
+
* @param {Object[]} entries Parsed database (a JSON array)
|
|
455
|
+
* @returns {{
|
|
456
|
+
* units: number,
|
|
457
|
+
* compilers: Map<string, {driver: string, family: string, units: number, languages: Set<string>}>,
|
|
458
|
+
* includeDirectories: Set<string>,
|
|
459
|
+
* hardening: Map<string, Map<string, number>>,
|
|
460
|
+
* }} `compilers` is keyed by the driver as written; `hardening` maps each
|
|
461
|
+
* setting to the number of units per value
|
|
462
|
+
*/
|
|
463
|
+
export function summarizeCompileDatabase(entries) {
|
|
464
|
+
const compilers = new Map();
|
|
465
|
+
const dirs = new Set();
|
|
466
|
+
const hardening = new Map();
|
|
467
|
+
let units = 0;
|
|
468
|
+
for (const entry of Array.isArray(entries) ? entries : []) {
|
|
469
|
+
if (!entry || typeof entry !== "object") {
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
const args = entryArguments(entry);
|
|
473
|
+
if (!args.length) {
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
units++;
|
|
477
|
+
const driver = args[0];
|
|
478
|
+
const known = compilers.get(driver) || {
|
|
479
|
+
driver,
|
|
480
|
+
family: compilerFamilyOfName(driver),
|
|
481
|
+
units: 0,
|
|
482
|
+
languages: new Set(),
|
|
483
|
+
};
|
|
484
|
+
known.units++;
|
|
485
|
+
const language = unitLanguage(args, entry.file);
|
|
486
|
+
if (language !== "unknown") {
|
|
487
|
+
known.languages.add(language);
|
|
488
|
+
}
|
|
489
|
+
compilers.set(driver, known);
|
|
490
|
+
const directory =
|
|
491
|
+
typeof entry.directory === "string" ? entry.directory : "";
|
|
492
|
+
for (const d of includeDirectories(args, directory)) {
|
|
493
|
+
dirs.add(d);
|
|
494
|
+
}
|
|
495
|
+
for (const [setting, value] of hardeningSettings(args.slice(1))) {
|
|
496
|
+
const counts = hardening.get(setting) || new Map();
|
|
497
|
+
counts.set(value, (counts.get(value) || 0) + 1);
|
|
498
|
+
hardening.set(setting, counts);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return { units, compilers, includeDirectories: dirs, hardening };
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Read a compiler's family and version from what it prints for `--version`
|
|
506
|
+
* (or, for MSVC, on start-up).
|
|
507
|
+
*
|
|
508
|
+
* @param {string} banner Output of the version query
|
|
509
|
+
* @param {string} [nameFamily] Family implied by the driver's name
|
|
510
|
+
* @returns {{family: string, version: string|undefined, edgFrontEnd: boolean}}
|
|
511
|
+
*/
|
|
512
|
+
export function classifyCompilerBanner(banner, nameFamily = "unknown") {
|
|
513
|
+
const text = `${banner || ""}`;
|
|
514
|
+
const firstLine = text.split(/\r?\n/).find((l) => l.trim().length) || "";
|
|
515
|
+
const edgFrontEnd = text.includes("Edison Design Group");
|
|
516
|
+
let family = nameFamily;
|
|
517
|
+
if (text.includes("Microsoft (R) C/C++")) {
|
|
518
|
+
family = "msvc";
|
|
519
|
+
} else if (firstLine.startsWith("Apple clang")) {
|
|
520
|
+
family = "apple-clang";
|
|
521
|
+
} else if (firstLine.includes("clang version")) {
|
|
522
|
+
// clang-cl prints clang's banner
|
|
523
|
+
family = nameFamily === "clang-cl" ? "clang-cl" : "clang";
|
|
524
|
+
} else if (text.includes("Cuda compilation tools")) {
|
|
525
|
+
family = "nvcc";
|
|
526
|
+
} else if (text.includes("Intel(R) oneAPI")) {
|
|
527
|
+
family = "icx";
|
|
528
|
+
} else if (/^ic(c|pc) \(ICC\)/.test(firstLine)) {
|
|
529
|
+
family = "icc";
|
|
530
|
+
} else if (/^nvc\+?\+? /.test(firstLine) || firstLine.startsWith("pgc")) {
|
|
531
|
+
family = "nvhpc";
|
|
532
|
+
} else if (/\(GCC\)|^gcc |^g\+\+ |Free Software Foundation/.test(text)) {
|
|
533
|
+
family = "gcc";
|
|
534
|
+
} else if (edgFrontEnd) {
|
|
535
|
+
family = "edg";
|
|
536
|
+
}
|
|
537
|
+
return { family, version: versionOf(text, family), edgFrontEnd };
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/** The first dotted version in the banner line that names the compiler. */
|
|
541
|
+
function versionOf(text, family) {
|
|
542
|
+
const lines = text.split(/\r?\n/);
|
|
543
|
+
const pick =
|
|
544
|
+
family === "nvcc"
|
|
545
|
+
? lines.find((l) => l.includes("release")) || ""
|
|
546
|
+
: family === "msvc"
|
|
547
|
+
? lines.find((l) => l.includes("Version")) || ""
|
|
548
|
+
: lines.find((l) => l.trim().length) || "";
|
|
549
|
+
const words = pick.replaceAll(",", " ").split(/\s+/);
|
|
550
|
+
for (const w of family === "gcc" ? [...words].reverse() : words) {
|
|
551
|
+
const v = w.replace(/^V/, "");
|
|
552
|
+
if (/^\d+(\.\d+)+/.test(v)) {
|
|
553
|
+
return v.match(/^\d+(\.\d+)+/)[0];
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
return undefined;
|
|
557
|
+
}
|