dd-trace 5.45.0 → 5.47.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/LICENSE-3rdparty.csv +1 -2
- package/ci/init.js +8 -0
- package/ext/exporters.d.ts +2 -1
- package/ext/exporters.js +2 -1
- package/package.json +8 -9
- package/packages/datadog-instrumentations/orchestrion.yml +52 -0
- package/packages/datadog-instrumentations/src/cucumber.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +41 -1
- package/packages/datadog-instrumentations/src/jest.js +11 -2
- package/packages/datadog-instrumentations/src/langchain.js +49 -53
- package/packages/datadog-instrumentations/src/mariadb.js +19 -0
- package/packages/datadog-instrumentations/src/mocha/main.js +1 -1
- package/packages/datadog-instrumentations/src/mocha/utils.js +11 -3
- package/packages/datadog-instrumentations/src/orchestrion-config/index.js +5 -0
- package/packages/datadog-instrumentations/src/playwright.js +333 -46
- package/packages/datadog-instrumentations/src/router.js +1 -7
- package/packages/datadog-instrumentations/src/vitest.js +11 -3
- package/packages/datadog-plugin-cucumber/src/index.js +11 -4
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +17 -5
- package/packages/datadog-plugin-jest/src/index.js +11 -4
- package/packages/datadog-plugin-langchain/src/index.js +18 -12
- package/packages/datadog-plugin-langchain/src/tracing.js +66 -6
- package/packages/datadog-plugin-mocha/src/index.js +17 -5
- package/packages/datadog-plugin-mongodb-core/src/index.js +24 -0
- package/packages/datadog-plugin-playwright/src/index.js +124 -10
- package/packages/datadog-plugin-vitest/src/index.js +13 -8
- package/packages/datadog-shimmer/src/shimmer.js +3 -42
- package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +39 -15
- package/packages/dd-trace/src/appsec/iast/taint-tracking/filter.js +3 -3
- package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +0 -3
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +25 -12
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-telemetry.js +3 -32
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +99 -57
- package/packages/dd-trace/src/appsec/rasp/command_injection.js +1 -1
- package/packages/dd-trace/src/appsec/rasp/index.js +4 -2
- package/packages/dd-trace/src/appsec/rasp/lfi.js +1 -1
- package/packages/dd-trace/src/appsec/rasp/sql_injection.js +1 -1
- package/packages/dd-trace/src/appsec/rasp/ssrf.js +1 -1
- package/packages/dd-trace/src/appsec/rasp/utils.js +12 -7
- package/packages/dd-trace/src/appsec/recommended.json +256 -84
- package/packages/dd-trace/src/appsec/reporter.js +6 -4
- package/packages/dd-trace/src/appsec/telemetry/index.js +27 -3
- package/packages/dd-trace/src/appsec/telemetry/rasp.js +70 -6
- package/packages/dd-trace/src/appsec/telemetry/waf.js +0 -30
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +4 -0
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +8 -3
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +6 -4
- package/packages/dd-trace/src/config.js +9 -0
- package/packages/dd-trace/src/constants.js +1 -0
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +102 -22
- package/packages/dd-trace/src/debugger/devtools_client/condition.js +263 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +69 -36
- package/packages/dd-trace/src/debugger/devtools_client/lock.js +8 -0
- package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +1 -7
- package/packages/dd-trace/src/debugger/devtools_client/send.js +2 -2
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +15 -10
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +3 -3
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +69 -62
- package/packages/dd-trace/src/debugger/devtools_client/state.js +3 -2
- package/packages/dd-trace/src/debugger/index.js +3 -0
- package/packages/dd-trace/src/encode/0.4.js +24 -17
- package/packages/dd-trace/src/exporter.js +1 -0
- package/packages/dd-trace/src/exporters/common/docker.js +37 -7
- package/packages/dd-trace/src/exporters/common/request.js +1 -4
- package/packages/dd-trace/src/format.js +58 -60
- package/packages/dd-trace/src/llmobs/plugins/base.js +2 -2
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +62 -3
- package/packages/dd-trace/src/llmobs/plugins/openai.js +1 -0
- package/packages/dd-trace/src/llmobs/plugins/vertexai.js +2 -1
- package/packages/dd-trace/src/llmobs/writers/spans/base.js +3 -3
- package/packages/dd-trace/src/log/index.js +2 -0
- package/packages/dd-trace/src/log/writer.js +19 -2
- package/packages/dd-trace/src/opentelemetry/span.js +4 -4
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +17 -3
- package/packages/dd-trace/src/opentracing/span.js +10 -0
- package/packages/dd-trace/src/plugin_manager.js +2 -0
- package/packages/dd-trace/src/plugins/util/test.js +11 -0
- package/packages/dd-trace/src/profiler.js +1 -1
- package/packages/dd-trace/src/profiling/config.js +6 -0
- package/packages/dd-trace/src/profiling/exporters/agent.js +1 -5
- package/packages/dd-trace/src/profiling/profiler.js +4 -3
- package/packages/dd-trace/src/profiling/profilers/wall.js +12 -8
- package/packages/dd-trace/src/proxy.js +5 -1
- package/packages/dd-trace/src/tagger.js +38 -26
- package/packages/dd-trace/src/util.js +1 -7
|
@@ -33,21 +33,9 @@ function addWafRequestMetrics (store, { duration, durationExt, wafTimeout, error
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
function trackWafDurations ({ duration, durationExt }, versionsTags) {
|
|
37
|
-
if (duration) {
|
|
38
|
-
appsecMetrics.distribution('waf.duration', versionsTags).track(duration)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (durationExt) {
|
|
42
|
-
appsecMetrics.distribution('waf.duration_ext', versionsTags).track(durationExt)
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
36
|
function trackWafMetrics (store, metrics) {
|
|
47
37
|
const versionsTags = getVersionsTags(metrics.wafVersion, metrics.rulesVersion)
|
|
48
38
|
|
|
49
|
-
trackWafDurations(metrics, versionsTags)
|
|
50
|
-
|
|
51
39
|
const metricTags = getOrCreateMetricTags(store, versionsTags)
|
|
52
40
|
|
|
53
41
|
if (metrics.blockFailed) {
|
|
@@ -134,24 +122,6 @@ function incrementWafRequests (store) {
|
|
|
134
122
|
function incrementTruncatedMetrics (metrics, truncationReason) {
|
|
135
123
|
const truncationTags = { truncation_reason: truncationReason }
|
|
136
124
|
appsecMetrics.count('waf.input_truncated', truncationTags).inc(1)
|
|
137
|
-
|
|
138
|
-
if (metrics?.maxTruncatedString) {
|
|
139
|
-
appsecMetrics.distribution('waf.truncated_value_size', {
|
|
140
|
-
truncation_reason: TRUNCATION_FLAGS.STRING
|
|
141
|
-
}).track(metrics.maxTruncatedString)
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (metrics?.maxTruncatedContainerSize) {
|
|
145
|
-
appsecMetrics.distribution('waf.truncated_value_size', {
|
|
146
|
-
truncation_reason: TRUNCATION_FLAGS.CONTAINER_SIZE
|
|
147
|
-
}).track(metrics.maxTruncatedContainerSize)
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (metrics?.maxTruncatedContainerDepth) {
|
|
151
|
-
appsecMetrics.distribution('waf.truncated_value_size', {
|
|
152
|
-
truncation_reason: TRUNCATION_FLAGS.CONTAINER_DEPTH
|
|
153
|
-
}).track(metrics.maxTruncatedContainerDepth)
|
|
154
|
-
}
|
|
155
125
|
}
|
|
156
126
|
|
|
157
127
|
function getTruncationReason ({ maxTruncatedString, maxTruncatedContainerSize, maxTruncatedContainerDepth }) {
|
|
@@ -25,6 +25,10 @@ class WAFContextWrapper {
|
|
|
25
25
|
run ({ persistent, ephemeral }, raspRule) {
|
|
26
26
|
if (this.ddwafContext.disposed) {
|
|
27
27
|
log.warn('[ASM] Calling run on a disposed context')
|
|
28
|
+
if (raspRule) {
|
|
29
|
+
Reporter.reportRaspRuleSkipped(raspRule, 'after-request')
|
|
30
|
+
}
|
|
31
|
+
|
|
28
32
|
return
|
|
29
33
|
}
|
|
30
34
|
|
|
@@ -6,7 +6,8 @@ const {
|
|
|
6
6
|
JEST_WORKER_TRACE_PAYLOAD_CODE,
|
|
7
7
|
CUCUMBER_WORKER_TRACE_PAYLOAD_CODE,
|
|
8
8
|
MOCHA_WORKER_TRACE_PAYLOAD_CODE,
|
|
9
|
-
JEST_WORKER_LOGS_PAYLOAD_CODE
|
|
9
|
+
JEST_WORKER_LOGS_PAYLOAD_CODE,
|
|
10
|
+
PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE
|
|
10
11
|
} = require('../../../plugins/util/test')
|
|
11
12
|
|
|
12
13
|
function getInterprocessTraceCode () {
|
|
@@ -19,6 +20,9 @@ function getInterprocessTraceCode () {
|
|
|
19
20
|
if (process.env.MOCHA_WORKER_ID) {
|
|
20
21
|
return MOCHA_WORKER_TRACE_PAYLOAD_CODE
|
|
21
22
|
}
|
|
23
|
+
if (process.env.DD_PLAYWRIGHT_WORKER) {
|
|
24
|
+
return PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE
|
|
25
|
+
}
|
|
22
26
|
return null
|
|
23
27
|
}
|
|
24
28
|
|
|
@@ -65,8 +69,9 @@ class TestWorkerCiVisibilityExporter {
|
|
|
65
69
|
this._logsWriter.append({ testConfiguration, logMessage })
|
|
66
70
|
}
|
|
67
71
|
|
|
68
|
-
|
|
69
|
-
|
|
72
|
+
// TODO: add to other writers
|
|
73
|
+
flush (onDone) {
|
|
74
|
+
this._writer.flush(onDone)
|
|
70
75
|
this._coverageWriter.flush()
|
|
71
76
|
this._logsWriter.flush()
|
|
72
77
|
}
|
|
@@ -8,13 +8,13 @@ class Writer {
|
|
|
8
8
|
this._interprocessCode = interprocessCode
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
flush () {
|
|
11
|
+
flush (onDone) {
|
|
12
12
|
const count = this._encoder.count()
|
|
13
13
|
|
|
14
14
|
if (count > 0) {
|
|
15
15
|
const payload = this._encoder.makePayload()
|
|
16
16
|
|
|
17
|
-
this._sendPayload(payload)
|
|
17
|
+
this._sendPayload(payload, onDone)
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -22,7 +22,7 @@ class Writer {
|
|
|
22
22
|
this._encoder.encode(payload)
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
_sendPayload (data) {
|
|
25
|
+
_sendPayload (data, onDone = () => {}) {
|
|
26
26
|
// ## Jest
|
|
27
27
|
// Only available when `child_process` is used for the jest worker.
|
|
28
28
|
// eslint-disable-next-line
|
|
@@ -36,7 +36,9 @@ class Writer {
|
|
|
36
36
|
// eslint-disable-next-line
|
|
37
37
|
// https://github.com/cucumber/cucumber-js/blob/5ce371870b677fe3d1a14915dc535688946f734c/src/runtime/parallel/run_worker.ts#L13
|
|
38
38
|
if (process.send) { // it only works if process.send is available
|
|
39
|
-
process.send([this._interprocessCode, data])
|
|
39
|
+
process.send([this._interprocessCode, data], () => {
|
|
40
|
+
onDone()
|
|
41
|
+
})
|
|
40
42
|
}
|
|
41
43
|
}
|
|
42
44
|
}
|
|
@@ -63,6 +63,8 @@ const otelDdEnvMapping = {
|
|
|
63
63
|
|
|
64
64
|
const VALID_PROPAGATION_STYLES = new Set(['datadog', 'tracecontext', 'b3', 'b3 single header', 'none'])
|
|
65
65
|
|
|
66
|
+
const VALID_PROPAGATION_BEHAVIOR_EXTRACT = new Set(['continue', 'restart', 'ignore'])
|
|
67
|
+
|
|
66
68
|
const VALID_LOG_LEVELS = new Set(['debug', 'info', 'warn', 'error'])
|
|
67
69
|
|
|
68
70
|
function getFromOtelSamplerMap (otelTracesSampler, otelTracesSamplerArg) {
|
|
@@ -584,6 +586,7 @@ class Config {
|
|
|
584
586
|
this._setValue(defaults, 'traceId128BitGenerationEnabled', true)
|
|
585
587
|
this._setValue(defaults, 'traceId128BitLoggingEnabled', true)
|
|
586
588
|
this._setValue(defaults, 'tracePropagationExtractFirst', false)
|
|
589
|
+
this._setValue(defaults, 'tracePropagationBehaviorExtract', 'continue')
|
|
587
590
|
this._setValue(defaults, 'tracePropagationStyle.inject', ['datadog', 'tracecontext', 'baggage'])
|
|
588
591
|
this._setValue(defaults, 'tracePropagationStyle.extract', ['datadog', 'tracecontext', 'baggage'])
|
|
589
592
|
this._setValue(defaults, 'tracePropagationStyle.otelPropagators', false)
|
|
@@ -746,6 +749,7 @@ class Config {
|
|
|
746
749
|
DD_TRACE_PARTIAL_FLUSH_MIN_SPANS,
|
|
747
750
|
DD_TRACE_PEER_SERVICE_MAPPING,
|
|
748
751
|
DD_TRACE_PROPAGATION_EXTRACT_FIRST,
|
|
752
|
+
DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT,
|
|
749
753
|
DD_TRACE_PROPAGATION_STYLE,
|
|
750
754
|
DD_TRACE_PROPAGATION_STYLE_INJECT,
|
|
751
755
|
DD_TRACE_PROPAGATION_STYLE_EXTRACT,
|
|
@@ -967,6 +971,11 @@ class Config {
|
|
|
967
971
|
this._setBoolean(env, 'traceId128BitGenerationEnabled', DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED)
|
|
968
972
|
this._setBoolean(env, 'traceId128BitLoggingEnabled', DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED)
|
|
969
973
|
this._setBoolean(env, 'tracePropagationExtractFirst', DD_TRACE_PROPAGATION_EXTRACT_FIRST)
|
|
974
|
+
const stringPropagationBehaviorExtract = String(DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT)
|
|
975
|
+
this._setValue(env, 'tracePropagationBehaviorExtract',
|
|
976
|
+
VALID_PROPAGATION_BEHAVIOR_EXTRACT.has(stringPropagationBehaviorExtract)
|
|
977
|
+
? stringPropagationBehaviorExtract
|
|
978
|
+
: 'continue')
|
|
970
979
|
this._setBoolean(env, 'tracePropagationStyle.otelPropagators',
|
|
971
980
|
DD_TRACE_PROPAGATION_STYLE ||
|
|
972
981
|
DD_TRACE_PROPAGATION_STYLE_INJECT ||
|
|
@@ -26,6 +26,7 @@ module.exports = {
|
|
|
26
26
|
ERROR_TYPE: 'error.type',
|
|
27
27
|
ERROR_MESSAGE: 'error.message',
|
|
28
28
|
ERROR_STACK: 'error.stack',
|
|
29
|
+
IGNORE_OTEL_ERROR: Symbol('ignore.otel.error'),
|
|
29
30
|
COMPONENT: 'component',
|
|
30
31
|
CLIENT_PORT_KEY: 'network.destination.port',
|
|
31
32
|
PEER_SERVICE_KEY: 'peer.service',
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { getGeneratedPosition } = require('./source-maps')
|
|
4
|
+
const lock = require('./lock')()
|
|
4
5
|
const session = require('./session')
|
|
6
|
+
const compileCondition = require('./condition')
|
|
5
7
|
const { MAX_SNAPSHOTS_PER_SECOND_PER_PROBE, MAX_NON_SNAPSHOTS_PER_SECOND_PER_PROBE } = require('./defaults')
|
|
6
|
-
const { findScriptFromPartialPath,
|
|
8
|
+
const { findScriptFromPartialPath, locationToBreakpoint, breakpointToProbes, probeToLocation } = require('./state')
|
|
7
9
|
const log = require('../../log')
|
|
8
10
|
|
|
9
11
|
let sessionStarted = false
|
|
@@ -42,21 +44,44 @@ async function addBreakpoint (probe) {
|
|
|
42
44
|
({ line: lineNumber, column: columnNumber } = await getGeneratedPosition(url, source, lineNumber, sourceMapURL))
|
|
43
45
|
}
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
try {
|
|
48
|
+
probe.condition = probe.when?.json && compileCondition(probe.when.json)
|
|
49
|
+
} catch (err) {
|
|
50
|
+
throw new Error(`Cannot compile expression: ${probe.when.dsl}`, { cause: err })
|
|
51
|
+
}
|
|
49
52
|
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
const release = await lock()
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
log.debug(
|
|
57
|
+
'[debugger:devtools_client] Adding breakpoint at %s:%d:%d (probe: %s, version: %d)',
|
|
58
|
+
url, lineNumber, columnNumber, probe.id, probe.version
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
const locationKey = generateLocationKey(scriptId, lineNumber, columnNumber)
|
|
62
|
+
const breakpoint = locationToBreakpoint.get(locationKey)
|
|
63
|
+
|
|
64
|
+
if (breakpoint) {
|
|
65
|
+
// A breakpoint already exists at this location, so we need to add the probe to the existing breakpoint
|
|
66
|
+
await updateBreakpoint(breakpoint, probe)
|
|
67
|
+
} else {
|
|
68
|
+
// No breakpoint exists at this location, so we need to create a new one
|
|
69
|
+
const location = {
|
|
70
|
+
scriptId,
|
|
71
|
+
lineNumber: lineNumber - 1, // Beware! lineNumber is zero-indexed
|
|
72
|
+
columnNumber
|
|
73
|
+
}
|
|
74
|
+
const result = await session.post('Debugger.setBreakpoint', {
|
|
75
|
+
location,
|
|
76
|
+
condition: probe.condition
|
|
77
|
+
})
|
|
78
|
+
probeToLocation.set(probe.id, locationKey)
|
|
79
|
+
locationToBreakpoint.set(locationKey, { id: result.breakpointId, location, locationKey })
|
|
80
|
+
breakpointToProbes.set(result.breakpointId, new Map([[probe.id, probe]]))
|
|
55
81
|
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
breakpoints.set(breakpointId, probe)
|
|
82
|
+
} finally {
|
|
83
|
+
release()
|
|
84
|
+
}
|
|
60
85
|
}
|
|
61
86
|
|
|
62
87
|
async function removeBreakpoint ({ id }) {
|
|
@@ -64,24 +89,79 @@ async function removeBreakpoint ({ id }) {
|
|
|
64
89
|
// We should not get in this state, but abort if we do, so the code doesn't fail unexpected
|
|
65
90
|
throw Error(`Cannot remove probe ${id}: Debugger not started`)
|
|
66
91
|
}
|
|
67
|
-
if (!
|
|
92
|
+
if (!probeToLocation.has(id)) {
|
|
68
93
|
throw Error(`Unknown probe id: ${id}`)
|
|
69
94
|
}
|
|
70
95
|
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
96
|
+
const release = await lock()
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const locationKey = probeToLocation.get(id)
|
|
100
|
+
const breakpoint = locationToBreakpoint.get(locationKey)
|
|
101
|
+
const probesAtLocation = breakpointToProbes.get(breakpoint.id)
|
|
102
|
+
|
|
103
|
+
probesAtLocation.delete(id)
|
|
104
|
+
probeToLocation.delete(id)
|
|
105
|
+
|
|
106
|
+
if (probesAtLocation.size === 0) {
|
|
107
|
+
locationToBreakpoint.delete(locationKey)
|
|
108
|
+
breakpointToProbes.delete(breakpoint.id)
|
|
109
|
+
if (breakpointToProbes.size === 0) {
|
|
110
|
+
await stop() // TODO: Will this actually delete the breakpoint?
|
|
111
|
+
} else {
|
|
112
|
+
await session.post('Debugger.removeBreakpoint', { breakpointId: breakpoint.id })
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
await updateBreakpoint(breakpoint)
|
|
116
|
+
}
|
|
117
|
+
} finally {
|
|
118
|
+
release()
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function updateBreakpoint (breakpoint, probe) {
|
|
123
|
+
const probesAtLocation = breakpointToProbes.get(breakpoint.id)
|
|
124
|
+
const conditionBeforeNewProbe = compileCompoundCondition(Array.from(probesAtLocation.values()))
|
|
75
125
|
|
|
76
|
-
|
|
126
|
+
// If a probe is provided, add it to the breakpoint. If not, it's because we're removing a probe, but potentially
|
|
127
|
+
// need to update the condtion of the breakpoint.
|
|
128
|
+
if (probe) {
|
|
129
|
+
probesAtLocation.set(probe.id, probe)
|
|
130
|
+
probeToLocation.set(probe.id, breakpoint.locationKey)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const condition = compileCompoundCondition(Array.from(probesAtLocation.values()))
|
|
134
|
+
|
|
135
|
+
if (condition || conditionBeforeNewProbe !== condition) {
|
|
136
|
+
await session.post('Debugger.removeBreakpoint', { breakpointId: breakpoint.id })
|
|
137
|
+
breakpointToProbes.delete(breakpoint.id)
|
|
138
|
+
const result = await session.post('Debugger.setBreakpoint', {
|
|
139
|
+
location: breakpoint.location,
|
|
140
|
+
condition
|
|
141
|
+
})
|
|
142
|
+
breakpoint.id = result.breakpointId
|
|
143
|
+
breakpointToProbes.set(result.breakpointId, probesAtLocation)
|
|
144
|
+
}
|
|
77
145
|
}
|
|
78
146
|
|
|
79
147
|
function start () {
|
|
80
148
|
sessionStarted = true
|
|
81
|
-
return session.post('Debugger.enable')
|
|
149
|
+
return session.post('Debugger.enable')
|
|
82
150
|
}
|
|
83
151
|
|
|
84
152
|
function stop () {
|
|
85
153
|
sessionStarted = false
|
|
86
|
-
return session.post('Debugger.disable')
|
|
154
|
+
return session.post('Debugger.disable')
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Only if all probes have a condition can we use a compound condition.
|
|
158
|
+
// Otherwise, we need to evaluate each probe individually once the breakpoint is hit.
|
|
159
|
+
function compileCompoundCondition (probes) {
|
|
160
|
+
return probes.every(p => p.condition)
|
|
161
|
+
? probes.map(p => p.condition).filter(Boolean).join(' || ')
|
|
162
|
+
: undefined
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function generateLocationKey (scriptId, lineNumber, columnNumber) {
|
|
166
|
+
return `${scriptId}:${lineNumber}:${columnNumber}`
|
|
87
167
|
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
module.exports = compile
|
|
4
|
+
|
|
5
|
+
const identifierRegex = /^[@a-zA-Z_$][\w$]*$/
|
|
6
|
+
|
|
7
|
+
// The following identifiers have purposefully not been included in this list:
|
|
8
|
+
// - The reserved words `this` and `super` as they can have valid use cases as `ref` values
|
|
9
|
+
// - The literals `undefined` and `Infinity` as they can be useful as `ref` values, especially to check if a
|
|
10
|
+
// variable is `undefined`.
|
|
11
|
+
// - The following future reserved words in older standards, as they can now be used safely:
|
|
12
|
+
// `abstract`, `boolean`, `byte`, `char`, `double`, `final`, `float`, `goto`, `int`, `long`, `native`, `short`,
|
|
13
|
+
// `synchronized`, `throws`, `transient`, `volatile`.
|
|
14
|
+
const reservedWords = new Set([
|
|
15
|
+
// Reserved words
|
|
16
|
+
'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', 'default', 'delete', 'do', 'else', 'export',
|
|
17
|
+
'extends', 'false', 'finally', 'for', 'function', 'if', 'import', 'in', 'instanceof', 'new', 'null', 'return',
|
|
18
|
+
'switch', 'throw', 'true', 'try', 'typeof', 'var', 'void', 'while', 'with',
|
|
19
|
+
|
|
20
|
+
// Reserved in strict mode
|
|
21
|
+
'let', 'static', 'yield',
|
|
22
|
+
|
|
23
|
+
// Reserved in module code or async function bodies:
|
|
24
|
+
'await',
|
|
25
|
+
|
|
26
|
+
// Future reserved words
|
|
27
|
+
'enum',
|
|
28
|
+
|
|
29
|
+
// Future reserved words in strict mode
|
|
30
|
+
'implements', 'interface', 'package', 'private', 'protected', 'public',
|
|
31
|
+
|
|
32
|
+
// Litterals
|
|
33
|
+
'NaN'
|
|
34
|
+
])
|
|
35
|
+
|
|
36
|
+
const PRIMITIVE_TYPES = new Set(['string', 'number', 'bigint', 'boolean', 'undefined', 'symbol', 'null'])
|
|
37
|
+
|
|
38
|
+
// TODO: Consider storing some of these functions on `process` so they can be reused across probes
|
|
39
|
+
function compile (node) {
|
|
40
|
+
if (node === null || typeof node === 'number' || typeof node === 'boolean') {
|
|
41
|
+
return node
|
|
42
|
+
} else if (typeof node === 'string') {
|
|
43
|
+
return JSON.stringify(node)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const [type, value] = Object.entries(node)[0]
|
|
47
|
+
|
|
48
|
+
if (type === 'not') {
|
|
49
|
+
return `!(${compile(value)})`
|
|
50
|
+
} else if (type === 'len' || type === 'count') {
|
|
51
|
+
return getSize(compile(value))
|
|
52
|
+
} else if (type === 'isEmpty') {
|
|
53
|
+
return `${getSize(compile(value))} === 0`
|
|
54
|
+
} else if (type === 'isDefined') {
|
|
55
|
+
return `(() => {
|
|
56
|
+
try {
|
|
57
|
+
${compile(value)}
|
|
58
|
+
return true
|
|
59
|
+
} catch {
|
|
60
|
+
return false
|
|
61
|
+
}
|
|
62
|
+
})()`
|
|
63
|
+
} else if (type === 'instanceof') {
|
|
64
|
+
if (isPrimitiveType(value[1])) {
|
|
65
|
+
return `(typeof ${compile(value[0])} === '${value[1]}')` // TODO: Is parenthesizing necessary?
|
|
66
|
+
} else {
|
|
67
|
+
return `Function.prototype[Symbol.hasInstance].call(${assertIdentifier(value[1])}, ${compile(value[0])})`
|
|
68
|
+
}
|
|
69
|
+
} else if (type === 'ref') {
|
|
70
|
+
if (value === '@it') {
|
|
71
|
+
return '$dd_it'
|
|
72
|
+
} else if (value === '@key') {
|
|
73
|
+
return '$dd_key'
|
|
74
|
+
} else if (value === '@value') {
|
|
75
|
+
return '$dd_value'
|
|
76
|
+
} else {
|
|
77
|
+
return assertIdentifier(value)
|
|
78
|
+
}
|
|
79
|
+
} else if (Array.isArray(value)) {
|
|
80
|
+
const args = value.map(compile)
|
|
81
|
+
switch (type) {
|
|
82
|
+
case 'eq': return `(${args[0]}) === (${args[1]})`
|
|
83
|
+
case 'ne': return `(${args[0]}) !== (${args[1]})`
|
|
84
|
+
case 'gt': return `${guardAgainstCoercionSideEffects(args[0])} > ${guardAgainstCoercionSideEffects(args[1])}`
|
|
85
|
+
case 'ge': return `${guardAgainstCoercionSideEffects(args[0])} >= ${guardAgainstCoercionSideEffects(args[1])}`
|
|
86
|
+
case 'lt': return `${guardAgainstCoercionSideEffects(args[0])} < ${guardAgainstCoercionSideEffects(args[1])}`
|
|
87
|
+
case 'le': return `${guardAgainstCoercionSideEffects(args[0])} <= ${guardAgainstCoercionSideEffects(args[1])}`
|
|
88
|
+
case 'any': return iterateOn('some', ...args)
|
|
89
|
+
case 'all': return iterateOn('every', ...args)
|
|
90
|
+
case 'and': return `(${args.join(') && (')})`
|
|
91
|
+
case 'or': return `(${args.join(') || (')})`
|
|
92
|
+
case 'startsWith': return `String.prototype.startsWith.call(${assertString(args[0])}, ${assertString(args[1])})`
|
|
93
|
+
case 'endsWith': return `String.prototype.endsWith.call(${assertString(args[0])}, ${assertString(args[1])})`
|
|
94
|
+
case 'contains': return `((obj, elm) => {
|
|
95
|
+
if (${isString('obj')}) {
|
|
96
|
+
return String.prototype.includes.call(obj, elm)
|
|
97
|
+
} else if (Array.isArray(obj)) {
|
|
98
|
+
return Array.prototype.includes.call(obj, elm)
|
|
99
|
+
} else if (${isTypedArray('obj')}) {
|
|
100
|
+
return Object.getPrototypeOf(Int8Array.prototype).includes.call(obj, elm)
|
|
101
|
+
} else if (${isInstanceOfCoreType('Set', 'obj')}) {
|
|
102
|
+
return Set.prototype.has.call(obj, elm)
|
|
103
|
+
} else if (${isInstanceOfCoreType('WeakSet', 'obj')}) {
|
|
104
|
+
return WeakSet.prototype.has.call(obj, elm)
|
|
105
|
+
} else if (${isInstanceOfCoreType('Map', 'obj')}) {
|
|
106
|
+
return Map.prototype.has.call(obj, elm)
|
|
107
|
+
} else if (${isInstanceOfCoreType('WeakMap', 'obj')}) {
|
|
108
|
+
return WeakMap.prototype.has.call(obj, elm)
|
|
109
|
+
} else {
|
|
110
|
+
throw new TypeError('Variable does not support contains')
|
|
111
|
+
}
|
|
112
|
+
})(${args[0]}, ${args[1]})`
|
|
113
|
+
case 'matches': return `((str, regex) => {
|
|
114
|
+
if (${isString('str')}) {
|
|
115
|
+
const regexIsString = ${isString('regex')}
|
|
116
|
+
if (regexIsString || Object.getPrototypeOf(regex) === RegExp.prototype) {
|
|
117
|
+
return RegExp.prototype.test.call(regexIsString ? new RegExp(regex) : regex, str)
|
|
118
|
+
} else {
|
|
119
|
+
throw new TypeError('Regular expression must be either a string or an instance of RegExp')
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
throw new TypeError('Variable is not a string')
|
|
123
|
+
}
|
|
124
|
+
})(${args[0]}, ${args[1]})`
|
|
125
|
+
case 'filter': return `(($dd_var) => {
|
|
126
|
+
return ${isIterableCollection('$dd_var')}
|
|
127
|
+
? Array.from($dd_var).filter(($dd_it) => ${args[1]})
|
|
128
|
+
: Object.entries($dd_var).reduce((acc, [$dd_key, $dd_value]) => {
|
|
129
|
+
if (${args[1]}) acc[$dd_key] = $dd_value
|
|
130
|
+
return acc
|
|
131
|
+
}, {})
|
|
132
|
+
})(${args[0]})`
|
|
133
|
+
case 'substring': return `((str) => {
|
|
134
|
+
if (${isString('str')}) {
|
|
135
|
+
return String.prototype.substring.call(str, ${args[1]}, ${args[2]})
|
|
136
|
+
} else {
|
|
137
|
+
throw new TypeError('Variable is not a string')
|
|
138
|
+
}
|
|
139
|
+
})(${args[0]})`
|
|
140
|
+
case 'getmember': return accessProperty(args[0], args[1], false)
|
|
141
|
+
case 'index': return accessProperty(args[0], args[1], true)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
throw new TypeError(`Unknown AST node type: ${type}`)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function iterateOn (fnName, variable, callbackCode) {
|
|
149
|
+
return `(($dd_val) => {
|
|
150
|
+
return ${isIterableCollection('$dd_val')}
|
|
151
|
+
? Array.from($dd_val).${fnName}(($dd_it) => ${callbackCode})
|
|
152
|
+
: Object.entries($dd_val).${fnName}(([$dd_key, $dd_value]) => ${callbackCode})
|
|
153
|
+
})(${variable})`
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function isString (variable) {
|
|
157
|
+
return `(typeof ${variable} === 'string' || ${variable} instanceof String)`
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function isPrimitiveType (type) {
|
|
161
|
+
return PRIMITIVE_TYPES.has(type)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function isIterableCollection (variable) {
|
|
165
|
+
return `(${isArrayOrTypedArray(variable)} || ${isInstanceOfCoreType('Set', variable)} || ` +
|
|
166
|
+
`${isInstanceOfCoreType('WeakSet', variable)})`
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function isArrayOrTypedArray (variable) {
|
|
170
|
+
return `(Array.isArray(${variable}) || ${isTypedArray(variable)})`
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function isTypedArray (variable) {
|
|
174
|
+
return isInstanceOfCoreType('TypedArray', variable, `${variable} instanceof Object.getPrototypeOf(Int8Array)`)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function isInstanceOfCoreType (type, variable, fallback = `${variable} instanceof ${type}`) {
|
|
178
|
+
return `(process[Symbol.for('datadog:node:util:types')]?.is${type}?.(${variable}) ?? ${fallback})`
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function getSize (variable) {
|
|
182
|
+
return `((val) => {
|
|
183
|
+
if (${isString('val')} || ${isArrayOrTypedArray('val')}) {
|
|
184
|
+
return ${guardAgainstPropertyAccessSideEffects('val', '"length"')}
|
|
185
|
+
} else if (${isInstanceOfCoreType('Set', 'val')} || ${isInstanceOfCoreType('Map', 'val')}) {
|
|
186
|
+
return ${guardAgainstPropertyAccessSideEffects('val', '"size"')}
|
|
187
|
+
} else if (${isInstanceOfCoreType('WeakSet', 'val')} || ${isInstanceOfCoreType('WeakMap', 'val')}) {
|
|
188
|
+
throw new TypeError('Cannot get size of WeakSet or WeakMap')
|
|
189
|
+
} else if (typeof val === 'object' && val !== null) {
|
|
190
|
+
return Object.keys(val).length
|
|
191
|
+
} else {
|
|
192
|
+
throw new TypeError('Cannot get length of variable')
|
|
193
|
+
}
|
|
194
|
+
})(${variable})`
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function accessProperty (variable, keyOrIndex, allowMapAccess) {
|
|
198
|
+
return `((val, key) => {
|
|
199
|
+
if (${isInstanceOfCoreType('Map', 'val')}) {
|
|
200
|
+
${allowMapAccess
|
|
201
|
+
? 'return Map.prototype.get.call(val, key)'
|
|
202
|
+
: 'throw new Error(\'Accessing a Map is not allowed\')'}
|
|
203
|
+
} else if (${isInstanceOfCoreType('WeakMap', 'val')}) {
|
|
204
|
+
${allowMapAccess
|
|
205
|
+
? 'return WeakMap.prototype.get.call(val, key)'
|
|
206
|
+
: 'throw new Error(\'Accessing a WeakMap is not allowed\')'}
|
|
207
|
+
} else if (${isInstanceOfCoreType('Set', 'val')} || ${isInstanceOfCoreType('WeakSet', 'val')}) {
|
|
208
|
+
throw new Error('Accessing a Set or WeakSet is not allowed')
|
|
209
|
+
} else {
|
|
210
|
+
return ${guardAgainstPropertyAccessSideEffects('val', 'key')}
|
|
211
|
+
}
|
|
212
|
+
})(${variable}, ${keyOrIndex})`
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function guardAgainstPropertyAccessSideEffects (variable, propertyName) {
|
|
216
|
+
return `((val, key) => {
|
|
217
|
+
if (
|
|
218
|
+
${isInstanceOfCoreType('Proxy', 'val', 'true')} ||
|
|
219
|
+
Object.getOwnPropertyDescriptor(val, key)?.get !== undefined
|
|
220
|
+
) {
|
|
221
|
+
throw new Error('Possibility of side effect')
|
|
222
|
+
} else {
|
|
223
|
+
return val[key]
|
|
224
|
+
}
|
|
225
|
+
})(${variable}, ${propertyName})`
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function guardAgainstCoercionSideEffects (variable) {
|
|
229
|
+
// shortcut if we're comparing number literals
|
|
230
|
+
if (typeof variable === 'number') return variable
|
|
231
|
+
|
|
232
|
+
return `((val) => {
|
|
233
|
+
if (
|
|
234
|
+
typeof val === 'object' && val !== null && (
|
|
235
|
+
${isInstanceOfCoreType('Proxy', 'val', 'true')} ||
|
|
236
|
+
val[Symbol.toPrimitive] !== undefined ||
|
|
237
|
+
val.valueOf !== Object.prototype.valueOf ||
|
|
238
|
+
val.toString !== Object.prototype.toString
|
|
239
|
+
)
|
|
240
|
+
) {
|
|
241
|
+
throw new Error('Possibility of side effect due to coercion methods')
|
|
242
|
+
} else {
|
|
243
|
+
return val
|
|
244
|
+
}
|
|
245
|
+
})(${variable})`
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function assertString (variable) {
|
|
249
|
+
return `((val) => {
|
|
250
|
+
if (typeof val === 'string' || val instanceof String) {
|
|
251
|
+
return val
|
|
252
|
+
} else {
|
|
253
|
+
throw new TypeError('Variable is not a string')
|
|
254
|
+
}
|
|
255
|
+
})(${variable})`
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function assertIdentifier (value) {
|
|
259
|
+
if (!identifierRegex.test(value) || reservedWords.has(value)) {
|
|
260
|
+
throw new SyntaxError(`Illegal identifier: ${value}`)
|
|
261
|
+
}
|
|
262
|
+
return value
|
|
263
|
+
}
|