datadog 2.35.0 → 2.37.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +85 -1
- data/ext/datadog_profiling_native_extension/clock_id_from_mach.c +5 -12
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +71 -31
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -1
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +18 -4
- data/ext/datadog_profiling_native_extension/collectors_stack.c +37 -18
- data/ext/datadog_profiling_native_extension/collectors_stack.h +8 -2
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +596 -341
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +11 -7
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +7 -8
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +0 -12
- data/ext/datadog_profiling_native_extension/extconf.rb +13 -2
- data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +4 -43
- data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +15 -47
- data/ext/datadog_profiling_native_extension/heap_recorder.c +44 -26
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +14 -35
- data/ext/datadog_profiling_native_extension/profiling.c +41 -4
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +40 -34
- data/ext/datadog_profiling_native_extension/stack_recorder.c +24 -3
- data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -0
- data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +4 -2
- data/ext/libdatadog_api/datadog_ruby_common.c +7 -8
- data/ext/libdatadog_api/datadog_ruby_common.h +0 -12
- data/ext/libdatadog_api/di.c +76 -0
- data/ext/libdatadog_api/extconf.rb +6 -0
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/ai_guard/api_client.rb +6 -4
- data/lib/datadog/appsec/api_security/route_extractor.rb +13 -4
- data/lib/datadog/appsec/component.rb +1 -1
- data/lib/datadog/appsec/configuration.rb +13 -0
- data/lib/datadog/appsec/context.rb +4 -2
- data/lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb +37 -4
- data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +59 -4
- data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +59 -5
- data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +64 -19
- data/lib/datadog/appsec/contrib/graphql/integration.rb +1 -0
- data/lib/datadog/appsec/contrib/rack/buffered_input.rb +83 -0
- data/lib/datadog/appsec/contrib/rack/gateway/request.rb +41 -3
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +24 -7
- data/lib/datadog/appsec/contrib/rack/input_peeker.rb +79 -0
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +21 -0
- data/lib/datadog/appsec/contrib/rails/gateway/request.rb +35 -1
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +17 -1
- data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +60 -7
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +24 -3
- data/lib/datadog/appsec/default_header_tags.rb +10 -6
- data/lib/datadog/appsec/ext.rb +1 -0
- data/lib/datadog/appsec/metrics/collector.rb +18 -1
- data/lib/datadog/appsec/metrics/exporter.rb +9 -3
- data/lib/datadog/appsec/remote.rb +9 -3
- data/lib/datadog/appsec/route_normalizer/rails_route_pattern.rb +176 -0
- data/lib/datadog/appsec/route_normalizer/route_pattern.rb +378 -0
- data/lib/datadog/appsec/route_normalizer/route_text.rb +57 -0
- data/lib/datadog/appsec/route_normalizer.rb +80 -0
- data/lib/datadog/appsec/utils/http/body_reader.rb +61 -0
- data/lib/datadog/core/configuration/components.rb +83 -2
- data/lib/datadog/core/configuration/components_state.rb +6 -1
- data/lib/datadog/core/configuration/settings.rb +1 -5
- data/lib/datadog/core/configuration/supported_configurations.rb +4 -0
- data/lib/datadog/core/evp.rb +11 -0
- data/lib/datadog/core/remote/client/capabilities.rb +34 -7
- data/lib/datadog/core/remote/component.rb +1 -1
- data/lib/datadog/core/telemetry/event/app_started.rb +8 -22
- data/lib/datadog/core/utils/at_fork_monkey_patch.rb +1 -1
- data/lib/datadog/core/utils/forking.rb +3 -1
- data/lib/datadog/core/utils/spawn_monkey_patch.rb +3 -1
- data/lib/datadog/core.rb +3 -0
- data/lib/datadog/di/base.rb +7 -2
- data/lib/datadog/di/code_tracker.rb +5 -2
- data/lib/datadog/di/component.rb +110 -36
- data/lib/datadog/di/error.rb +10 -0
- data/lib/datadog/di/fatal_exceptions.rb +26 -0
- data/lib/datadog/di/instrumenter.rb +381 -165
- data/lib/datadog/di/probe.rb +14 -0
- data/lib/datadog/di/probe_file_loader.rb +8 -3
- data/lib/datadog/di/probe_manager.rb +67 -9
- data/lib/datadog/di/probe_notification_builder.rb +4 -1
- data/lib/datadog/di/probe_notifier_worker.rb +52 -32
- data/lib/datadog/di/redactor.rb +16 -1
- data/lib/datadog/di/remote.rb +175 -14
- data/lib/datadog/di/serializer.rb +12 -5
- data/lib/datadog/di/transport/input.rb +8 -4
- data/lib/datadog/di.rb +63 -0
- data/lib/datadog/error_tracking/collector.rb +2 -1
- data/lib/datadog/error_tracking/component.rb +2 -2
- data/lib/datadog/kit/appsec/events/v2.rb +60 -2
- data/lib/datadog/kit/tracing/method_tracer.rb +4 -1
- data/lib/datadog/open_feature/component.rb +29 -6
- data/lib/datadog/open_feature/configuration.rb +8 -0
- data/lib/datadog/open_feature/ext.rb +2 -0
- data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +286 -0
- data/lib/datadog/open_feature/flag_evaluation/writer.rb +433 -0
- data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +101 -0
- data/lib/datadog/open_feature/hooks/{flag_eval_hook.rb → flag_eval_metrics_hook.rb} +1 -1
- data/lib/datadog/open_feature/native_evaluator.rb +22 -0
- data/lib/datadog/open_feature/provider.rb +59 -27
- data/lib/datadog/open_feature/transport.rb +55 -1
- data/lib/datadog/opentelemetry/sdk/logs_exporter.rb +5 -12
- data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +4 -11
- data/lib/datadog/opentelemetry/sdk/propagator.rb +9 -3
- data/lib/datadog/opentelemetry/sdk/span_processor.rb +4 -1
- data/lib/datadog/opentelemetry/sdk.rb +19 -0
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +1 -1
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +5 -3
- data/lib/datadog/profiling/collectors/thread_context.rb +1 -0
- data/lib/datadog/profiling/component.rb +13 -15
- data/lib/datadog/profiling/ext/dir_monkey_patches.rb +3 -3
- data/lib/datadog/ruby_version.rb +25 -0
- data/lib/datadog/symbol_database/component.rb +453 -128
- data/lib/datadog/symbol_database/configuration.rb +12 -4
- data/lib/datadog/symbol_database/extensions.rb +19 -0
- data/lib/datadog/symbol_database/extractor.rb +326 -150
- data/lib/datadog/symbol_database/file_hash.rb +3 -1
- data/lib/datadog/symbol_database/remote.rb +8 -3
- data/lib/datadog/symbol_database/scope_batcher.rb +7 -3
- data/lib/datadog/symbol_database/uploader.rb +9 -2
- data/lib/datadog/symbol_database.rb +22 -0
- data/lib/datadog/tracing/configuration/ext.rb +13 -0
- data/lib/datadog/tracing/configuration/settings.rb +17 -0
- data/lib/datadog/tracing/contrib/configuration/resolver.rb +7 -0
- data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +2 -0
- data/lib/datadog/tracing/contrib/grpc.rb +1 -0
- data/lib/datadog/tracing/contrib/http/distributed/propagation.rb +2 -0
- data/lib/datadog/tracing/contrib/http.rb +1 -0
- data/lib/datadog/tracing/contrib/karafka/distributed/propagation.rb +2 -0
- data/lib/datadog/tracing/contrib/karafka.rb +1 -0
- data/lib/datadog/tracing/contrib/rack/middlewares.rb +3 -1
- data/lib/datadog/tracing/contrib/rack/route_inference.rb +3 -1
- data/lib/datadog/tracing/contrib/sidekiq/distributed/propagation.rb +2 -0
- data/lib/datadog/tracing/contrib/sidekiq.rb +1 -0
- data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +2 -0
- data/lib/datadog/tracing/contrib/waterdrop.rb +1 -0
- data/lib/datadog/tracing/distributed/baggage.rb +2 -1
- data/lib/datadog/tracing/distributed/propagation.rb +33 -1
- data/lib/datadog/tracing/distributed/trace_context.rb +11 -2
- data/lib/datadog/tracing/ext.rb +9 -0
- data/lib/datadog/tracing/remote.rb +34 -3
- data/lib/datadog/tracing/trace_digest.rb +7 -0
- data/lib/datadog/tracing/trace_operation.rb +4 -1
- data/lib/datadog/tracing/tracer.rb +1 -0
- data/lib/datadog/version.rb +1 -1
- data/lib/datadog.rb +5 -1
- metadata +22 -6
|
@@ -4,6 +4,7 @@ require_relative 'scope'
|
|
|
4
4
|
require_relative 'symbol'
|
|
5
5
|
require_relative 'file_hash'
|
|
6
6
|
require_relative '../core/utils/enumerable_compat'
|
|
7
|
+
require_relative '../di/fatal_exceptions'
|
|
7
8
|
|
|
8
9
|
module Datadog
|
|
9
10
|
module SymbolDatabase
|
|
@@ -79,7 +80,7 @@ module Datadog
|
|
|
79
80
|
# Cached unbound Module#singleton_class? — dispatched explicitly so user classes
|
|
80
81
|
# that define their own `singleton_class?` (e.g. with required arguments) cannot
|
|
81
82
|
# intercept the predicate and cause the module to be silently dropped from
|
|
82
|
-
# extract_all. Cached at load time because
|
|
83
|
+
# extract_all. Cached at load time because build_per_file_index iterates
|
|
83
84
|
# ObjectSpace.each_object(Module) over tens of thousands of modules.
|
|
84
85
|
MODULE_SINGLETON_CLASS_PRED = Module.instance_method(:singleton_class?)
|
|
85
86
|
private_constant :MODULE_SINGLETON_CLASS_PRED
|
|
@@ -89,6 +90,26 @@ module Datadog
|
|
|
89
90
|
# so we bind the original Module#name to get the real module name safely.
|
|
90
91
|
MODULE_NAME = Module.instance_method(:name)
|
|
91
92
|
|
|
93
|
+
# Cached UnboundMethods for the remaining class/module/object introspection
|
|
94
|
+
# used during extraction. Like MODULE_NAME, these bind the original
|
|
95
|
+
# implementations and are dispatched explicitly so that application code
|
|
96
|
+
# which overrides any of them (per-class, on a subclass, or in a singleton
|
|
97
|
+
# class) can neither intercept extraction nor be executed as a side effect
|
|
98
|
+
# of it. KERNEL_CLASS recovers an object's real class without calling a
|
|
99
|
+
# possibly-overridden #class.
|
|
100
|
+
CLASS_SUPERCLASS = Class.instance_method(:superclass)
|
|
101
|
+
MODULE_INCLUDED_MODULES = Module.instance_method(:included_modules)
|
|
102
|
+
MODULE_ANCESTORS = Module.instance_method(:ancestors)
|
|
103
|
+
MODULE_CLASS_VARIABLES = Module.instance_method(:class_variables)
|
|
104
|
+
MODULE_CONSTANTS = Module.instance_method(:constants)
|
|
105
|
+
MODULE_AUTOLOAD_P = Module.instance_method(:autoload?)
|
|
106
|
+
MODULE_CONST_GET = Module.instance_method(:const_get)
|
|
107
|
+
MODULE_CONST_DEFINED = Module.instance_method(:const_defined?)
|
|
108
|
+
KERNEL_CLASS = ::Kernel.instance_method(:class)
|
|
109
|
+
private_constant :CLASS_SUPERCLASS, :MODULE_INCLUDED_MODULES, :MODULE_ANCESTORS,
|
|
110
|
+
:MODULE_CLASS_VARIABLES, :MODULE_CONSTANTS, :MODULE_AUTOLOAD_P, :MODULE_CONST_GET,
|
|
111
|
+
:MODULE_CONST_DEFINED, :KERNEL_CLASS
|
|
112
|
+
|
|
92
113
|
# @param logger [Logger] Logger instance (SymbolDatabase::Logger facade or compatible)
|
|
93
114
|
# @param settings [Configuration::Settings] Tracer settings
|
|
94
115
|
def initialize(logger:, settings:)
|
|
@@ -109,7 +130,7 @@ module Datadog
|
|
|
109
130
|
# @param mod [Module, Class] The module or class to extract from
|
|
110
131
|
# @return [Scope, nil] FILE scope wrapping extracted scope, or nil if filtered out
|
|
111
132
|
def extract(mod)
|
|
112
|
-
return nil unless mod
|
|
133
|
+
return nil unless Module === mod
|
|
113
134
|
mod_name = safe_mod_name(mod)
|
|
114
135
|
return nil unless mod_name
|
|
115
136
|
|
|
@@ -118,14 +139,15 @@ module Datadog
|
|
|
118
139
|
source_file = find_source_file(mod)
|
|
119
140
|
return nil unless source_file
|
|
120
141
|
|
|
121
|
-
inner_scope = if mod
|
|
142
|
+
inner_scope = if Class === mod
|
|
122
143
|
extract_class_scope(mod)
|
|
123
144
|
else
|
|
124
145
|
extract_module_scope(mod)
|
|
125
146
|
end
|
|
126
147
|
|
|
127
148
|
wrap_in_file_scope(source_file, [inner_scope])
|
|
128
|
-
rescue => e
|
|
149
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
150
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
129
151
|
@logger.debug { "symdb: failed to extract #{mod_name || '<unknown>'}: #{e.class}: #{e.message}" }
|
|
130
152
|
nil
|
|
131
153
|
end
|
|
@@ -134,21 +156,66 @@ module Datadog
|
|
|
134
156
|
# Returns an array of FILE scopes with proper FQN-based nesting.
|
|
135
157
|
#
|
|
136
158
|
# Two-pass algorithm:
|
|
137
|
-
# Pass 1:
|
|
138
|
-
#
|
|
159
|
+
# Pass 1 (`build_per_file_index`): iterate ObjectSpace once, building
|
|
160
|
+
# `{ file_path => [[mod_name, mod, [method_name_symbol, ...]], ...] }`.
|
|
161
|
+
# Stores Symbol method names + Module refs only; no UnboundMethod retention
|
|
162
|
+
# between passes.
|
|
163
|
+
# Pass 2 (`build_file_scope`): for each file in the index, resolve
|
|
164
|
+
# UnboundMethods just-in-time, build the nested MODULE/CLASS scope tree from
|
|
165
|
+
# FQN splitting, and produce one FILE Scope. The per-file working set is
|
|
166
|
+
# released as soon as the FILE scope is yielded (or accumulated into the
|
|
167
|
+
# returned Array, in legacy mode).
|
|
139
168
|
#
|
|
140
169
|
# This is the production path used by Component. Methods are split by source file,
|
|
141
170
|
# so a class reopened across two files produces two FILE scopes, each with only
|
|
142
171
|
# the methods defined in that file.
|
|
143
172
|
#
|
|
144
|
-
#
|
|
173
|
+
# Memory profile (with a block):
|
|
174
|
+
# - Pass 1 builds a per-file index containing only Symbol method names plus
|
|
175
|
+
# Module references. No UnboundMethod objects are retained between passes.
|
|
176
|
+
# - Pass 2 processes one file at a time. The peak per file is bounded by the
|
|
177
|
+
# number of methods that live in that one file across all its modules
|
|
178
|
+
# (typical Rails: tens of methods; pathological case: a single very large
|
|
179
|
+
# source file). Once a FILE scope is yielded and the caller stops referencing
|
|
180
|
+
# it, the entire per-file working set becomes garbage.
|
|
181
|
+
#
|
|
182
|
+
# This is O(largest_file + batch_buffer), not O(total_classes).
|
|
183
|
+
#
|
|
184
|
+
# Without a block, returns the full `Array<Scope>` (legacy form, used by specs).
|
|
185
|
+
# The Array itself still scales with the number of files, so block form is the
|
|
186
|
+
# one to use for production memory bounds.
|
|
187
|
+
#
|
|
188
|
+
# @yieldparam scope [Scope] FILE scope for one source file
|
|
189
|
+
# @return [Array<Scope>, nil] Array of FILE scopes when called without a block; nil when a block is given
|
|
145
190
|
def extract_all
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
191
|
+
index = build_per_file_index
|
|
192
|
+
|
|
193
|
+
if block_given?
|
|
194
|
+
# Drain the index destructively so each per-file entry becomes eligible for
|
|
195
|
+
# collection as soon as its FILE scope is yielded and consumed. Hash#shift
|
|
196
|
+
# returns [key, value] on a non-empty hash and nil when empty, so the
|
|
197
|
+
# `while (pair = ...)` form is the drain. Indexing pair[0]/pair[1] rather
|
|
198
|
+
# than destructuring avoids introducing names into method scope that would
|
|
199
|
+
# then shadow the else-branch's block parameters.
|
|
200
|
+
while (pair = index.shift)
|
|
201
|
+
scope = build_file_scope(pair[0], pair[1])
|
|
202
|
+
yield scope if scope
|
|
203
|
+
end
|
|
204
|
+
nil
|
|
205
|
+
else
|
|
206
|
+
# Legacy non-block form for specs. No memory bound — the full Array is
|
|
207
|
+
# materialized.
|
|
208
|
+
result = []
|
|
209
|
+
index.each do |path, file_entries|
|
|
210
|
+
scope = build_file_scope(path, file_entries)
|
|
211
|
+
result << scope if scope
|
|
212
|
+
end
|
|
213
|
+
result
|
|
214
|
+
end
|
|
215
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
216
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
150
217
|
@logger.debug { "symdb: error in extract_all: #{e.class}: #{e.message}" }
|
|
151
|
-
[]
|
|
218
|
+
block_given? ? nil : []
|
|
152
219
|
end
|
|
153
220
|
|
|
154
221
|
private
|
|
@@ -160,11 +227,54 @@ module Datadog
|
|
|
160
227
|
# @return [String, nil] Module name or nil
|
|
161
228
|
def safe_mod_name(mod)
|
|
162
229
|
MODULE_NAME.bind(mod).call
|
|
163
|
-
rescue => e
|
|
230
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
231
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
164
232
|
@logger.debug { "symdb: safe_mod_name failed: #{e.class}: #{e.message}" }
|
|
165
233
|
nil
|
|
166
234
|
end
|
|
167
235
|
|
|
236
|
+
# Verify that mod_name still resolves to mod through Ruby's constant
|
|
237
|
+
# table. Returns false when a Class/Module has been detached from its
|
|
238
|
+
# constant (via remove_const) but still carries the cached Module#name —
|
|
239
|
+
# see build_per_file_index for the failure mode this protects.
|
|
240
|
+
#
|
|
241
|
+
# Walks the namespace path segment-by-segment. For each segment:
|
|
242
|
+
# 1. Check for a pending autoload directly on the current namespace.
|
|
243
|
+
# If present, const_get would trigger it — loading customer code as
|
|
244
|
+
# a side effect of symbol extraction and raising LoadError if the
|
|
245
|
+
# target file is missing (LoadError is ScriptError, not StandardError,
|
|
246
|
+
# and would propagate past the outer rescue in
|
|
247
|
+
# build_per_file_index). Return false instead.
|
|
248
|
+
# 2. Otherwise, require the constant to be directly defined on this
|
|
249
|
+
# namespace (const_defined?(sym, false)) and descend via
|
|
250
|
+
# const_get(sym, false). The direct-only lookup means an ancestor's
|
|
251
|
+
# pending autoload at the same name does not affect the result: a
|
|
252
|
+
# subclass with its own binding resolves through the binding, an
|
|
253
|
+
# inherited autoload triggers nothing.
|
|
254
|
+
#
|
|
255
|
+
# Uses `current.autoload?(sym, false)` (the inherit=false form, added
|
|
256
|
+
# in Ruby 2.7) so the question is strictly "is there an autoload
|
|
257
|
+
# registered directly on this namespace?" and ancestors' pending
|
|
258
|
+
# autoloads at the same name do not affect the result.
|
|
259
|
+
# @param mod_name [String]
|
|
260
|
+
# @param mod [Module]
|
|
261
|
+
# @return [Boolean]
|
|
262
|
+
def resolves_to_same_module?(mod_name, mod)
|
|
263
|
+
current = Object
|
|
264
|
+
mod_name.split('::').each do |seg|
|
|
265
|
+
sym = seg.to_sym
|
|
266
|
+
return false if MODULE_AUTOLOAD_P.bind(current).call(sym, false)
|
|
267
|
+
return false unless MODULE_CONST_DEFINED.bind(current).call(sym, false)
|
|
268
|
+
current = MODULE_CONST_GET.bind(current).call(sym, false)
|
|
269
|
+
end
|
|
270
|
+
current.equal?(mod)
|
|
271
|
+
rescue NameError, ArgumentError, TypeError
|
|
272
|
+
# Expected "no" outcome for stale/detached classes — the whole point
|
|
273
|
+
# of this predicate. Per the rescue convention in this file's header
|
|
274
|
+
# comment: inner per-item rescues are expected failures, no logging.
|
|
275
|
+
false
|
|
276
|
+
end
|
|
277
|
+
|
|
168
278
|
# Check if module is from user code (not gems or stdlib)
|
|
169
279
|
# @param mod [Module] The module to check
|
|
170
280
|
# @return [Boolean] true if user code
|
|
@@ -228,15 +338,10 @@ module Datadog
|
|
|
228
338
|
# AR models get filtered out as gem code.
|
|
229
339
|
#
|
|
230
340
|
# For namespace-only modules (no instance or singleton methods), falls back to
|
|
231
|
-
# Module#const_source_location
|
|
341
|
+
# Module#const_source_location to locate the module via its constants.
|
|
232
342
|
# This handles patterns like `module ApplicationCable; class Channel...; end; end`
|
|
233
343
|
# where the namespace module itself has no methods but defines user-code classes.
|
|
234
344
|
#
|
|
235
|
-
# On Ruby 2.6 (where const_source_location is unavailable), namespace-only modules
|
|
236
|
-
# and classes whose only methods are generated (e.g., AR models with only associations)
|
|
237
|
-
# may not be found — the extraction silently omits them. This is a graceful degradation:
|
|
238
|
-
# fewer symbols uploaded, no errors.
|
|
239
|
-
#
|
|
240
345
|
# @param mod [Module] The module
|
|
241
346
|
# @return [String, nil] Source file path or nil
|
|
242
347
|
def find_source_file(mod)
|
|
@@ -266,18 +371,19 @@ module Datadog
|
|
|
266
371
|
fallback ||= path # steep:ignore
|
|
267
372
|
end
|
|
268
373
|
|
|
269
|
-
#
|
|
374
|
+
# Use const_source_location to find where this class/module is declared.
|
|
270
375
|
# This handles two cases:
|
|
271
376
|
# 1. Classes with no user-defined methods (e.g. AR models with only associations) whose
|
|
272
377
|
# generated methods point to gem code — we find the `class Foo` declaration instead.
|
|
273
378
|
# 2. Namespace-only modules (`module Foo; class Bar; end; end`) with no methods at all.
|
|
274
|
-
|
|
379
|
+
mod_name = safe_mod_name(mod)
|
|
380
|
+
if mod_name
|
|
275
381
|
# Look up the class/module by its last name component in its enclosing namespace.
|
|
276
|
-
parts =
|
|
382
|
+
parts = mod_name.split('::')
|
|
277
383
|
const_name = parts.last
|
|
278
384
|
namespace = if parts.length > 1
|
|
279
385
|
begin
|
|
280
|
-
Object.
|
|
386
|
+
MODULE_CONST_GET.bind(Object).call(parts[0..-2].join('::')) # steep:ignore
|
|
281
387
|
rescue NameError
|
|
282
388
|
nil
|
|
283
389
|
end
|
|
@@ -288,7 +394,8 @@ module Datadog
|
|
|
288
394
|
if namespace
|
|
289
395
|
location = begin
|
|
290
396
|
namespace.const_source_location(const_name)
|
|
291
|
-
rescue => e
|
|
397
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
398
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
292
399
|
@logger.debug { "symdb: const_source_location(#{const_name}) failed: #{e.class}: #{e.message}" }
|
|
293
400
|
nil
|
|
294
401
|
end
|
|
@@ -301,10 +408,11 @@ module Datadog
|
|
|
301
408
|
end
|
|
302
409
|
|
|
303
410
|
# Also scan constants defined by mod itself (namespace-only modules).
|
|
304
|
-
mod.
|
|
411
|
+
MODULE_CONSTANTS.bind(mod).call(false).each do |child_const_name|
|
|
305
412
|
location = begin
|
|
306
413
|
mod.const_source_location(child_const_name)
|
|
307
|
-
rescue => e
|
|
414
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
415
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
308
416
|
@logger.debug { "symdb: const_source_location(#{child_const_name}) failed: #{e.class}: #{e.message}" }
|
|
309
417
|
nil
|
|
310
418
|
end
|
|
@@ -320,7 +428,8 @@ module Datadog
|
|
|
320
428
|
end
|
|
321
429
|
|
|
322
430
|
fallback
|
|
323
|
-
rescue => e
|
|
431
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
432
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
324
433
|
@logger.debug { "symdb: error finding source file for #{safe_mod_name(mod) || '<unknown>'}: #{e.class}: #{e.message}" }
|
|
325
434
|
nil
|
|
326
435
|
end
|
|
@@ -357,7 +466,7 @@ module Datadog
|
|
|
357
466
|
|
|
358
467
|
Scope.new(
|
|
359
468
|
scope_type: 'MODULE',
|
|
360
|
-
name: mod
|
|
469
|
+
name: safe_mod_name(mod),
|
|
361
470
|
source_file: source_file,
|
|
362
471
|
start_line: UNKNOWN_MIN_LINE,
|
|
363
472
|
end_line: UNKNOWN_MAX_LINE,
|
|
@@ -375,7 +484,7 @@ module Datadog
|
|
|
375
484
|
|
|
376
485
|
Scope.new(
|
|
377
486
|
scope_type: 'CLASS',
|
|
378
|
-
name: klass
|
|
487
|
+
name: safe_mod_name(klass),
|
|
379
488
|
source_file: source_file,
|
|
380
489
|
start_line: start_line,
|
|
381
490
|
end_line: end_line,
|
|
@@ -406,8 +515,9 @@ module Datadog
|
|
|
406
515
|
return [UNKNOWN_MIN_LINE, UNKNOWN_MAX_LINE] if starts.empty?
|
|
407
516
|
|
|
408
517
|
[starts.min, ends.max]
|
|
409
|
-
rescue => e
|
|
410
|
-
|
|
518
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
519
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
520
|
+
@logger.debug { "symdb: error calculating line range for #{safe_mod_name(klass)}: #{e.class}: #{e.message}" }
|
|
411
521
|
[UNKNOWN_MIN_LINE, UNKNOWN_MAX_LINE]
|
|
412
522
|
end
|
|
413
523
|
|
|
@@ -421,8 +531,9 @@ module Datadog
|
|
|
421
531
|
# Emitted as an array named super_classes — consistent with Java, .NET, and Python.
|
|
422
532
|
# Array allows for multiple entries if future Ruby versions or mixins expand the chain.
|
|
423
533
|
# Anonymous superclasses (class Foo < Class.new { ... }) have nil name; compact to skip.
|
|
424
|
-
|
|
425
|
-
|
|
534
|
+
superclass = CLASS_SUPERCLASS.bind(klass).call
|
|
535
|
+
if superclass && !superclass.equal?(Object) && !superclass.equal?(BasicObject)
|
|
536
|
+
super_name = safe_mod_name(superclass)
|
|
426
537
|
specifics[:super_classes] = [super_name] if super_name
|
|
427
538
|
end
|
|
428
539
|
|
|
@@ -430,7 +541,7 @@ module Datadog
|
|
|
430
541
|
# included_modules returns the entire ancestor chain's mixins, not only directly
|
|
431
542
|
# included ones. This is intentional: the field reports "modules this class
|
|
432
543
|
# responds to," which is what the consumer (UI navigation, probe context) needs.
|
|
433
|
-
included = klass.
|
|
544
|
+
included = MODULE_INCLUDED_MODULES.bind(klass).call.map { |m| safe_mod_name(m) }.reject do |name|
|
|
434
545
|
name.nil? || EXCLUDED_COMMON_MODULES.any? { |prefix| name.start_with?(prefix) }
|
|
435
546
|
end
|
|
436
547
|
specifics[:included_modules] = included unless included.empty?
|
|
@@ -441,16 +552,17 @@ module Datadog
|
|
|
441
552
|
# Single-pass collection avoids the intermediate arrays from take_while.map.compact.
|
|
442
553
|
# Test coverage: spec/datadog/symbol_database/extractor_spec.rb tests prepend behavior.
|
|
443
554
|
prepended = []
|
|
444
|
-
klass.
|
|
445
|
-
break if a
|
|
446
|
-
name = a
|
|
555
|
+
MODULE_ANCESTORS.bind(klass).call.each do |a|
|
|
556
|
+
break if a.equal?(klass)
|
|
557
|
+
name = safe_mod_name(a)
|
|
447
558
|
prepended << name if name
|
|
448
559
|
end
|
|
449
560
|
specifics[:prepended_modules] = prepended unless prepended.empty?
|
|
450
561
|
|
|
451
562
|
specifics
|
|
452
|
-
rescue => e
|
|
453
|
-
|
|
563
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
564
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
565
|
+
@logger.debug { "symdb: error building language specifics for #{safe_mod_name(klass)}: #{e.class}: #{e.message}" }
|
|
454
566
|
{}
|
|
455
567
|
end
|
|
456
568
|
|
|
@@ -472,8 +584,9 @@ module Datadog
|
|
|
472
584
|
end
|
|
473
585
|
|
|
474
586
|
scopes
|
|
475
|
-
rescue => e
|
|
476
|
-
|
|
587
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
588
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
589
|
+
@logger.debug { "symdb: failed to extract methods from #{safe_mod_name(klass)}: #{e.class}: #{e.message}" }
|
|
477
590
|
[]
|
|
478
591
|
end
|
|
479
592
|
|
|
@@ -507,8 +620,9 @@ module Datadog
|
|
|
507
620
|
},
|
|
508
621
|
symbols: extract_method_parameters(method)
|
|
509
622
|
)
|
|
510
|
-
rescue => e
|
|
511
|
-
|
|
623
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
624
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
625
|
+
@logger.debug { "symdb: failed to extract method #{safe_mod_name(klass)}##{method_name}: #{e.class}: #{e.message}" }
|
|
512
626
|
nil
|
|
513
627
|
end
|
|
514
628
|
|
|
@@ -585,7 +699,8 @@ module Datadog
|
|
|
585
699
|
def extract_method_parameters(method)
|
|
586
700
|
method_name = begin
|
|
587
701
|
method.name.to_s
|
|
588
|
-
rescue => e
|
|
702
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
703
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
589
704
|
@logger.debug { "symdb: method.name failed: #{e.class}: #{e.message}" }
|
|
590
705
|
'unknown'
|
|
591
706
|
end
|
|
@@ -607,14 +722,15 @@ module Datadog
|
|
|
607
722
|
line: UNKNOWN_MIN_LINE, # Parameters available in entire method
|
|
608
723
|
)
|
|
609
724
|
end
|
|
610
|
-
rescue => e
|
|
725
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
726
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
611
727
|
@logger.debug { "symdb: failed to extract parameters from #{method_name}: #{e.class}: #{e.message}" }
|
|
612
728
|
[]
|
|
613
729
|
end
|
|
614
730
|
|
|
615
731
|
# ── extract_all helpers ──────────────────────────────────────────────
|
|
616
732
|
|
|
617
|
-
# Sleep between chunks of modules processed in
|
|
733
|
+
# Sleep between chunks of modules processed in build_per_file_index so
|
|
618
734
|
# request-handling threads have guaranteed CPU time while extraction is in
|
|
619
735
|
# flight. Unlike Thread.pass (which only offers the GVL among runnable
|
|
620
736
|
# threads and leaves the extractor immediately re-runnable), sleep removes
|
|
@@ -629,20 +745,25 @@ module Datadog
|
|
|
629
745
|
SLEEP_SECONDS = 0.001
|
|
630
746
|
private_constant :SLEEP_EVERY_N_MODULES, :SLEEP_SECONDS
|
|
631
747
|
|
|
632
|
-
# Pass 1
|
|
633
|
-
#
|
|
634
|
-
|
|
635
|
-
|
|
748
|
+
# Pass 1 (memory-bounded form): build a per-file index of
|
|
749
|
+
# `{ file_path => [[mod_name, mod, [method_name_symbol, ...]], ...] }`.
|
|
750
|
+
#
|
|
751
|
+
# Stores Symbol method names plus Module references only — no UnboundMethod
|
|
752
|
+
# objects retained between passes. UnboundMethods created here (to read
|
|
753
|
+
# `source_location`) become garbage as the inner loop ends.
|
|
754
|
+
#
|
|
755
|
+
# The Module references are pointer-sized and the modules are already kept
|
|
756
|
+
# alive in ObjectSpace, so adding them to the index costs no extra retention.
|
|
757
|
+
#
|
|
758
|
+
# @return [Hash{String=>Array<Array(String, Module, Array<Symbol>)>}]
|
|
759
|
+
def build_per_file_index
|
|
760
|
+
index = {}
|
|
636
761
|
seen = 0
|
|
637
762
|
|
|
638
763
|
ObjectSpace.each_object(Module) do |mod|
|
|
639
764
|
# Singleton classes (per-object metaclasses) are never user-code classes.
|
|
640
765
|
# They're not const-referenced, DI cannot instrument methods on a singular
|
|
641
|
-
# object instance,
|
|
642
|
-
# singleton classes with long ancestor chains (e.g. through monkey-patches
|
|
643
|
-
# prepended into Kernel, common in dd-trace-rb test processes) is O(ancestors)
|
|
644
|
-
# — measured ~20ms per call, which dominates extract_all on heavily-loaded
|
|
645
|
-
# processes. Ruby 2.7+ optimized this path; the skip is a no-op there.
|
|
766
|
+
# object instance, so skipping them is both correct and cheap.
|
|
646
767
|
next if MODULE_SINGLETON_CLASS_PRED.bind(mod).call
|
|
647
768
|
|
|
648
769
|
seen += 1
|
|
@@ -650,84 +771,124 @@ module Datadog
|
|
|
650
771
|
|
|
651
772
|
mod_name = safe_mod_name(mod)
|
|
652
773
|
next unless mod_name
|
|
774
|
+
next unless resolves_to_same_module?(mod_name, mod)
|
|
653
775
|
next unless user_code_module?(mod)
|
|
654
776
|
|
|
655
|
-
|
|
777
|
+
file_to_names = collect_method_names_by_file(mod)
|
|
656
778
|
|
|
657
|
-
#
|
|
658
|
-
#
|
|
659
|
-
if
|
|
779
|
+
# Namespace-only modules (no own methods) — use find_source_file as the
|
|
780
|
+
# canonical file so the FILE scope still gets a MODULE entry.
|
|
781
|
+
if file_to_names.empty?
|
|
660
782
|
source_file = find_source_file(mod)
|
|
661
|
-
|
|
783
|
+
file_to_names[source_file] = [] if source_file
|
|
662
784
|
end
|
|
663
785
|
|
|
664
|
-
next if
|
|
786
|
+
next if file_to_names.empty?
|
|
665
787
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
788
|
+
file_to_names.each do |file_path, method_names|
|
|
789
|
+
(index[file_path] ||= []) << [mod_name, mod, method_names]
|
|
790
|
+
end
|
|
791
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
792
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
793
|
+
@logger.debug { "symdb: error indexing #{mod_name || '<unknown>'}: #{e.class}: #{e.message}" }
|
|
669
794
|
end
|
|
670
795
|
|
|
671
|
-
|
|
796
|
+
index
|
|
672
797
|
end
|
|
673
798
|
|
|
674
|
-
#
|
|
675
|
-
#
|
|
676
|
-
#
|
|
677
|
-
|
|
799
|
+
# For a single module, return `{ file_path => [method_name_symbol, ...] }`.
|
|
800
|
+
# Stores only the method-name symbols and their file paths — UnboundMethod
|
|
801
|
+
# objects allocated to read `source_location` are not retained between
|
|
802
|
+
# passes, so they can be GC'd as soon as the inner loop ends.
|
|
803
|
+
def collect_method_names_by_file(mod)
|
|
678
804
|
result = Hash.new { |h, k| h[k] = [] } # steep:ignore
|
|
679
805
|
|
|
680
|
-
#
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
806
|
+
# Module#instance_methods(false) already returns both public and protected
|
|
807
|
+
# methods, so iterating it plus private_instance_methods covers all three
|
|
808
|
+
# visibilities without an intermediate merged array.
|
|
809
|
+
[mod.instance_methods(false), mod.private_instance_methods(false)].each do |method_names|
|
|
810
|
+
method_names.each do |method_name|
|
|
811
|
+
method = mod.instance_method(method_name)
|
|
812
|
+
loc = method.source_location
|
|
813
|
+
next unless loc
|
|
814
|
+
next unless user_code_path?(loc[0])
|
|
815
|
+
|
|
816
|
+
result[loc[0]] << method_name
|
|
817
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
818
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
819
|
+
@logger.debug { "symdb: error indexing method #{method_name}: #{e.class}: #{e.message}" }
|
|
820
|
+
end
|
|
695
821
|
end
|
|
696
822
|
|
|
697
823
|
result
|
|
698
|
-
rescue => e
|
|
699
|
-
|
|
824
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
825
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
826
|
+
@logger.debug { "symdb: error indexing methods: #{e.class}: #{e.message}" }
|
|
700
827
|
{}
|
|
701
828
|
end
|
|
702
829
|
|
|
703
|
-
# Pass 2:
|
|
704
|
-
#
|
|
705
|
-
#
|
|
706
|
-
#
|
|
830
|
+
# Pass 2: build the FILE scope for one source file by walking just the modules
|
|
831
|
+
# that contribute methods to it. Resolves UnboundMethods just-in-time per
|
|
832
|
+
# method; the per-method scratch is collected by GC as each module's loop body
|
|
833
|
+
# ends. The returned Scope is the only thing the caller needs to keep alive
|
|
834
|
+
# — once the caller drops it, the entire per-file working set is collectable.
|
|
707
835
|
#
|
|
708
|
-
# @param
|
|
709
|
-
# @
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
836
|
+
# @param file_path [String]
|
|
837
|
+
# @param entries [Array<Array(String, Module, Array<Symbol>)>] tuples produced by build_per_file_index
|
|
838
|
+
# @return [Scope, nil] FILE scope, or nil if nothing extractable
|
|
839
|
+
def build_file_scope(file_path, entries)
|
|
840
|
+
return nil if entries.empty?
|
|
841
|
+
|
|
842
|
+
root = {
|
|
843
|
+
name: file_path, type: 'FILE', children: {},
|
|
844
|
+
methods: [], mod: nil, source_file: file_path, fqn: nil,
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
# Sort by FQN depth so parent namespaces are placed before children.
|
|
848
|
+
sorted = entries.sort_by { |(mod_name, _, _)| mod_name.count(':') }
|
|
849
|
+
|
|
850
|
+
sorted.each do |mod_name, mod, method_names|
|
|
851
|
+
# Resolve UnboundMethods for this (mod, file) just-in-time. These objects
|
|
852
|
+
# live only as long as the tree node holds them; they are released when
|
|
853
|
+
# convert_tree_to_scope finishes building the file's Scope.
|
|
854
|
+
method_infos = Core::Utils::EnumerableCompat.filter_map(method_names) do |name|
|
|
855
|
+
method = mod.instance_method(name)
|
|
856
|
+
# Pass 1 (build_per_file_index) recorded this method under file_path.
|
|
857
|
+
# If the method has been redefined in another file between the two
|
|
858
|
+
# passes (e.g. a class reopened during a Rails reload while extract_all
|
|
859
|
+
# is iterating), the resolved UnboundMethod's source_location now
|
|
860
|
+
# points elsewhere. Drop the stale entry — the hot-load TracePoint
|
|
861
|
+
# enqueues the redefined class and the next debounce window extracts
|
|
862
|
+
# it under the new file_path.
|
|
863
|
+
loc = method.source_location
|
|
864
|
+
next nil unless loc && loc[0] == file_path
|
|
865
|
+
{name: name, method: method, type: :instance}
|
|
866
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
867
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
868
|
+
@logger.debug { "symdb: error resolving #{mod_name}##{name}: #{e.class}: #{e.message}" }
|
|
869
|
+
nil
|
|
725
870
|
end
|
|
726
|
-
|
|
727
|
-
|
|
871
|
+
|
|
872
|
+
# If Pass 1 recorded methods for this module but every one of them has
|
|
873
|
+
# moved out of file_path between the passes, drop the entry — otherwise
|
|
874
|
+
# the FILE scope would carry an empty CLASS/MODULE node at a location
|
|
875
|
+
# the module no longer lives in.
|
|
876
|
+
next if method_names.any? && method_infos.empty?
|
|
877
|
+
|
|
878
|
+
parts = mod_name.split('::')
|
|
879
|
+
place_in_tree(root, parts, mod, mod_name, method_infos, file_path)
|
|
880
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
881
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
882
|
+
@logger.debug { "symdb: error placing #{mod_name} in tree: #{e.class}: #{e.message}" }
|
|
728
883
|
end
|
|
729
884
|
|
|
730
|
-
|
|
885
|
+
# steep:ignore:start
|
|
886
|
+
# Steep widens root[:children] to the union of all value types declared in
|
|
887
|
+
# the literal (String | Hash | Array | nil), losing the Hash narrowing.
|
|
888
|
+
return nil if root[:children].empty?
|
|
889
|
+
# steep:ignore:end
|
|
890
|
+
|
|
891
|
+
convert_tree_to_scope(file_path, root)
|
|
731
892
|
end
|
|
732
893
|
|
|
733
894
|
# Place a module/class in the file tree at the correct nesting depth.
|
|
@@ -755,12 +916,12 @@ module Datadog
|
|
|
755
916
|
if leaf
|
|
756
917
|
# Node exists (was created as intermediate or from another entry).
|
|
757
918
|
# Update type and mod — the actual module object is authoritative.
|
|
758
|
-
leaf[:type] =
|
|
919
|
+
leaf[:type] = (Class === mod) ? 'CLASS' : 'MODULE'
|
|
759
920
|
leaf[:mod] = mod
|
|
760
921
|
else
|
|
761
922
|
leaf = {
|
|
762
923
|
name: mod_name,
|
|
763
|
-
type:
|
|
924
|
+
type: (Class === mod) ? 'CLASS' : 'MODULE',
|
|
764
925
|
children: {}, methods: [],
|
|
765
926
|
mod: mod, source_file: file_path,
|
|
766
927
|
fqn: mod_name
|
|
@@ -777,32 +938,43 @@ module Datadog
|
|
|
777
938
|
# @param fqn [String] Fully-qualified name (e.g. "Authentication::Strategies")
|
|
778
939
|
# @return [String] 'CLASS' or 'MODULE'
|
|
779
940
|
def resolve_scope_type(fqn)
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
941
|
+
current = Object
|
|
942
|
+
fqn.split('::').each do |seg|
|
|
943
|
+
sym = seg.to_sym
|
|
944
|
+
pending_autoload = if RubyVersion.is?('>= 2.7')
|
|
945
|
+
MODULE_AUTOLOAD_P.bind(current).call(sym, false)
|
|
946
|
+
else
|
|
947
|
+
MODULE_AUTOLOAD_P.bind(current).call(sym)
|
|
948
|
+
end
|
|
949
|
+
return 'MODULE' if pending_autoload
|
|
950
|
+
return 'MODULE' unless MODULE_CONST_DEFINED.bind(current).call(sym, false)
|
|
951
|
+
current = MODULE_CONST_GET.bind(current).call(sym, false)
|
|
952
|
+
end
|
|
953
|
+
(Class === current) ? 'CLASS' : 'MODULE'
|
|
954
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
955
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
783
956
|
@logger.debug { "symdb: resolve_scope_type(#{fqn}) failed: #{e.class}: #{e.message}, defaulting to MODULE" }
|
|
784
957
|
'MODULE'
|
|
785
958
|
end
|
|
786
959
|
|
|
787
|
-
# Convert
|
|
788
|
-
# @param
|
|
789
|
-
# @
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
end
|
|
960
|
+
# Convert a single file tree (built by build_file_scope) to a FILE Scope.
|
|
961
|
+
# @param file_path [String] Source file path
|
|
962
|
+
# @param root [Hash] Tree node from build_file_scope
|
|
963
|
+
# @return [Scope] FILE scope
|
|
964
|
+
def convert_tree_to_scope(file_path, root)
|
|
965
|
+
file_hash = FileHash.compute(file_path, logger: @logger)
|
|
966
|
+
lang = {}
|
|
967
|
+
lang[:file_hash] = file_hash if file_hash
|
|
968
|
+
|
|
969
|
+
Scope.new(
|
|
970
|
+
scope_type: 'FILE',
|
|
971
|
+
name: file_path,
|
|
972
|
+
source_file: file_path,
|
|
973
|
+
start_line: UNKNOWN_MIN_LINE,
|
|
974
|
+
end_line: UNKNOWN_MAX_LINE,
|
|
975
|
+
language_specifics: lang,
|
|
976
|
+
scopes: root[:children].values.map { |child| convert_node_to_scope(child) },
|
|
977
|
+
)
|
|
806
978
|
end
|
|
807
979
|
|
|
808
980
|
# Convert a single hash node to a Scope object (recursive).
|
|
@@ -875,7 +1047,8 @@ module Datadog
|
|
|
875
1047
|
},
|
|
876
1048
|
symbols: extract_method_parameters(method)
|
|
877
1049
|
)
|
|
878
|
-
rescue => e
|
|
1050
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
1051
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
879
1052
|
klass_name = klass ? (safe_mod_name(klass) || '<unknown>') : '<unknown>'
|
|
880
1053
|
@logger.debug { "symdb: failed to build method scope #{klass_name}##{method_name}: #{e.class}: #{e.message}" }
|
|
881
1054
|
nil
|
|
@@ -889,8 +1062,8 @@ module Datadog
|
|
|
889
1062
|
symbols = []
|
|
890
1063
|
|
|
891
1064
|
# Class variables (only for classes)
|
|
892
|
-
if mod
|
|
893
|
-
mod.
|
|
1065
|
+
if Class === mod
|
|
1066
|
+
MODULE_CLASS_VARIABLES.bind(mod).call(false).each do |var_name|
|
|
894
1067
|
symbols << Symbol.new(
|
|
895
1068
|
symbol_type: 'STATIC_FIELD',
|
|
896
1069
|
name: var_name.to_s,
|
|
@@ -901,30 +1074,33 @@ module Datadog
|
|
|
901
1074
|
|
|
902
1075
|
# Constants (excluding nested modules/classes).
|
|
903
1076
|
# Skip autoloaded constants to avoid triggering loading as a side effect.
|
|
904
|
-
mod.
|
|
905
|
-
next if mod.
|
|
906
|
-
const_value = mod.
|
|
907
|
-
next if const_value
|
|
1077
|
+
MODULE_CONSTANTS.bind(mod).call(false).each do |const_name|
|
|
1078
|
+
next if MODULE_AUTOLOAD_P.bind(mod).call(const_name)
|
|
1079
|
+
const_value = MODULE_CONST_GET.bind(mod).call(const_name)
|
|
1080
|
+
next if Module === const_value
|
|
908
1081
|
|
|
909
1082
|
symbols << Symbol.new(
|
|
910
1083
|
symbol_type: 'STATIC_FIELD',
|
|
911
1084
|
name: const_name.to_s,
|
|
912
1085
|
line: UNKNOWN_MIN_LINE,
|
|
913
|
-
type: const_value.
|
|
1086
|
+
type: safe_mod_name(KERNEL_CLASS.bind(const_value).call)
|
|
914
1087
|
)
|
|
915
|
-
rescue NameError, LoadError, NoMethodError => e # standard:disable Lint/ShadowedException
|
|
916
|
-
# Expected: constant removed/undefined, autoload failure,
|
|
917
|
-
#
|
|
918
|
-
#
|
|
919
|
-
#
|
|
920
|
-
#
|
|
1088
|
+
rescue NameError, LoadError, NoMethodError, TypeError => e # standard:disable Lint/ShadowedException
|
|
1089
|
+
# Expected: constant removed/undefined (NameError), autoload failure (LoadError),
|
|
1090
|
+
# or a value whose class cannot be read (NoMethodError/TypeError). Skipping one
|
|
1091
|
+
# constant here keeps the rest of the module's symbols. Logged separately from
|
|
1092
|
+
# unexpected errors so the latter stand out in triage. Lint/ShadowedException
|
|
1093
|
+
# disabled: these descend from StandardError, but Ruby's rescue-clause-order
|
|
1094
|
+
# semantics ensure the catch-all rescue below only catches exceptions not matched here.
|
|
921
1095
|
@logger.debug { "symdb: skipping module constant #{const_name}: #{e.class}: #{e.message}" }
|
|
922
|
-
rescue => e
|
|
1096
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
1097
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
923
1098
|
@logger.debug { "symdb: unexpected error reading module constant #{const_name}: #{e.class}: #{e.message}" }
|
|
924
1099
|
end
|
|
925
1100
|
|
|
926
1101
|
symbols
|
|
927
|
-
rescue => e
|
|
1102
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
1103
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
928
1104
|
mod_name = safe_mod_name(mod) || '<unknown>'
|
|
929
1105
|
@logger.debug { "symdb: failed to extract symbols from #{mod_name}: #{e.class}: #{e.message}" }
|
|
930
1106
|
[]
|