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.
Files changed (86) hide show
  1. package/LICENSE-3rdparty.csv +1 -2
  2. package/ci/init.js +8 -0
  3. package/ext/exporters.d.ts +2 -1
  4. package/ext/exporters.js +2 -1
  5. package/package.json +8 -9
  6. package/packages/datadog-instrumentations/orchestrion.yml +52 -0
  7. package/packages/datadog-instrumentations/src/cucumber.js +2 -1
  8. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  9. package/packages/datadog-instrumentations/src/helpers/register.js +41 -1
  10. package/packages/datadog-instrumentations/src/jest.js +11 -2
  11. package/packages/datadog-instrumentations/src/langchain.js +49 -53
  12. package/packages/datadog-instrumentations/src/mariadb.js +19 -0
  13. package/packages/datadog-instrumentations/src/mocha/main.js +1 -1
  14. package/packages/datadog-instrumentations/src/mocha/utils.js +11 -3
  15. package/packages/datadog-instrumentations/src/orchestrion-config/index.js +5 -0
  16. package/packages/datadog-instrumentations/src/playwright.js +333 -46
  17. package/packages/datadog-instrumentations/src/router.js +1 -7
  18. package/packages/datadog-instrumentations/src/vitest.js +11 -3
  19. package/packages/datadog-plugin-cucumber/src/index.js +11 -4
  20. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +17 -5
  21. package/packages/datadog-plugin-jest/src/index.js +11 -4
  22. package/packages/datadog-plugin-langchain/src/index.js +18 -12
  23. package/packages/datadog-plugin-langchain/src/tracing.js +66 -6
  24. package/packages/datadog-plugin-mocha/src/index.js +17 -5
  25. package/packages/datadog-plugin-mongodb-core/src/index.js +24 -0
  26. package/packages/datadog-plugin-playwright/src/index.js +124 -10
  27. package/packages/datadog-plugin-vitest/src/index.js +13 -8
  28. package/packages/datadog-shimmer/src/shimmer.js +3 -42
  29. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +39 -15
  30. package/packages/dd-trace/src/appsec/iast/taint-tracking/filter.js +3 -3
  31. package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +0 -3
  32. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +25 -12
  33. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-telemetry.js +3 -32
  34. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +99 -57
  35. package/packages/dd-trace/src/appsec/rasp/command_injection.js +1 -1
  36. package/packages/dd-trace/src/appsec/rasp/index.js +4 -2
  37. package/packages/dd-trace/src/appsec/rasp/lfi.js +1 -1
  38. package/packages/dd-trace/src/appsec/rasp/sql_injection.js +1 -1
  39. package/packages/dd-trace/src/appsec/rasp/ssrf.js +1 -1
  40. package/packages/dd-trace/src/appsec/rasp/utils.js +12 -7
  41. package/packages/dd-trace/src/appsec/recommended.json +256 -84
  42. package/packages/dd-trace/src/appsec/reporter.js +6 -4
  43. package/packages/dd-trace/src/appsec/telemetry/index.js +27 -3
  44. package/packages/dd-trace/src/appsec/telemetry/rasp.js +70 -6
  45. package/packages/dd-trace/src/appsec/telemetry/waf.js +0 -30
  46. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +4 -0
  47. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +8 -3
  48. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +6 -4
  49. package/packages/dd-trace/src/config.js +9 -0
  50. package/packages/dd-trace/src/constants.js +1 -0
  51. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +102 -22
  52. package/packages/dd-trace/src/debugger/devtools_client/condition.js +263 -0
  53. package/packages/dd-trace/src/debugger/devtools_client/index.js +69 -36
  54. package/packages/dd-trace/src/debugger/devtools_client/lock.js +8 -0
  55. package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +1 -7
  56. package/packages/dd-trace/src/debugger/devtools_client/send.js +2 -2
  57. package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +15 -10
  58. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +3 -3
  59. package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +69 -62
  60. package/packages/dd-trace/src/debugger/devtools_client/state.js +3 -2
  61. package/packages/dd-trace/src/debugger/index.js +3 -0
  62. package/packages/dd-trace/src/encode/0.4.js +24 -17
  63. package/packages/dd-trace/src/exporter.js +1 -0
  64. package/packages/dd-trace/src/exporters/common/docker.js +37 -7
  65. package/packages/dd-trace/src/exporters/common/request.js +1 -4
  66. package/packages/dd-trace/src/format.js +58 -60
  67. package/packages/dd-trace/src/llmobs/plugins/base.js +2 -2
  68. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +62 -3
  69. package/packages/dd-trace/src/llmobs/plugins/openai.js +1 -0
  70. package/packages/dd-trace/src/llmobs/plugins/vertexai.js +2 -1
  71. package/packages/dd-trace/src/llmobs/writers/spans/base.js +3 -3
  72. package/packages/dd-trace/src/log/index.js +2 -0
  73. package/packages/dd-trace/src/log/writer.js +19 -2
  74. package/packages/dd-trace/src/opentelemetry/span.js +4 -4
  75. package/packages/dd-trace/src/opentracing/propagation/text_map.js +17 -3
  76. package/packages/dd-trace/src/opentracing/span.js +10 -0
  77. package/packages/dd-trace/src/plugin_manager.js +2 -0
  78. package/packages/dd-trace/src/plugins/util/test.js +11 -0
  79. package/packages/dd-trace/src/profiler.js +1 -1
  80. package/packages/dd-trace/src/profiling/config.js +6 -0
  81. package/packages/dd-trace/src/profiling/exporters/agent.js +1 -5
  82. package/packages/dd-trace/src/profiling/profiler.js +4 -3
  83. package/packages/dd-trace/src/profiling/profilers/wall.js +12 -8
  84. package/packages/dd-trace/src/proxy.js +5 -1
  85. package/packages/dd-trace/src/tagger.js +38 -26
  86. 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
- flush () {
69
- this._writer.flush()
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, probes, breakpoints } = require('./state')
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
- log.debug(
46
- '[debugger:devtools_client] Adding breakpoint at %s:%d:%d (probe: %s, version: %d)',
47
- url, lineNumber, columnNumber, probe.id, probe.version
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 { breakpointId } = await session.post('Debugger.setBreakpoint', {
51
- location: {
52
- scriptId,
53
- lineNumber: lineNumber - 1, // Beware! lineNumber is zero-indexed
54
- columnNumber
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
- probes.set(probe.id, breakpointId)
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 (!probes.has(id)) {
92
+ if (!probeToLocation.has(id)) {
68
93
  throw Error(`Unknown probe id: ${id}`)
69
94
  }
70
95
 
71
- const breakpointId = probes.get(id)
72
- await session.post('Debugger.removeBreakpoint', { breakpointId })
73
- probes.delete(id)
74
- breakpoints.delete(breakpointId)
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
- if (breakpoints.size === 0) return stop() // return instead of await to reduce number of promises created
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') // return instead of await to reduce number of promises created
149
+ return session.post('Debugger.enable')
82
150
  }
83
151
 
84
152
  function stop () {
85
153
  sessionStarted = false
86
- return session.post('Debugger.disable') // return instead of await to reduce number of promises created
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
+ }