datadog 2.7.0 → 2.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +43 -1
  3. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +47 -17
  4. data/ext/datadog_profiling_native_extension/extconf.rb +3 -8
  5. data/ext/datadog_profiling_native_extension/heap_recorder.c +11 -89
  6. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +3 -9
  7. data/ext/datadog_profiling_native_extension/profiling.c +6 -0
  8. data/ext/datadog_profiling_native_extension/ruby_helpers.c +14 -4
  9. data/ext/datadog_profiling_native_extension/ruby_helpers.h +4 -0
  10. data/ext/datadog_profiling_native_extension/stack_recorder.c +0 -34
  11. data/ext/libdatadog_extconf_helpers.rb +1 -1
  12. data/lib/datadog/appsec/component.rb +1 -8
  13. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +73 -0
  14. data/lib/datadog/appsec/contrib/active_record/integration.rb +41 -0
  15. data/lib/datadog/appsec/contrib/active_record/patcher.rb +53 -0
  16. data/lib/datadog/appsec/event.rb +1 -1
  17. data/lib/datadog/appsec/processor/context.rb +2 -2
  18. data/lib/datadog/appsec/remote.rb +1 -3
  19. data/lib/datadog/appsec/response.rb +7 -11
  20. data/lib/datadog/appsec.rb +3 -2
  21. data/lib/datadog/core/configuration/components.rb +17 -1
  22. data/lib/datadog/core/configuration/settings.rb +10 -0
  23. data/lib/datadog/core/configuration.rb +9 -1
  24. data/lib/datadog/core/remote/client/capabilities.rb +6 -0
  25. data/lib/datadog/core/remote/client.rb +65 -59
  26. data/lib/datadog/core/telemetry/component.rb +9 -3
  27. data/lib/datadog/core/telemetry/ext.rb +1 -0
  28. data/lib/datadog/di/code_tracker.rb +5 -4
  29. data/lib/datadog/di/component.rb +5 -1
  30. data/lib/datadog/di/contrib/active_record.rb +1 -0
  31. data/lib/datadog/di/init.rb +20 -0
  32. data/lib/datadog/di/instrumenter.rb +81 -11
  33. data/lib/datadog/di/probe.rb +11 -1
  34. data/lib/datadog/di/probe_builder.rb +1 -0
  35. data/lib/datadog/di/probe_manager.rb +4 -1
  36. data/lib/datadog/di/probe_notification_builder.rb +13 -7
  37. data/lib/datadog/di/remote.rb +124 -0
  38. data/lib/datadog/di/serializer.rb +14 -7
  39. data/lib/datadog/di/transport.rb +2 -1
  40. data/lib/datadog/di/utils.rb +7 -0
  41. data/lib/datadog/di.rb +84 -20
  42. data/lib/datadog/profiling/component.rb +4 -16
  43. data/lib/datadog/tracing/configuration/settings.rb +4 -8
  44. data/lib/datadog/tracing/contrib/active_support/cache/redis.rb +16 -4
  45. data/lib/datadog/tracing/contrib/elasticsearch/configuration/settings.rb +4 -0
  46. data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +6 -1
  47. data/lib/datadog/tracing/tracer.rb +1 -1
  48. data/lib/datadog/version.rb +1 -1
  49. data/lib/datadog.rb +3 -0
  50. metadata +17 -13
  51. data/lib/datadog/appsec/processor/actions.rb +0 -49
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c42d47b95292bfbb455a015e67700b3b8e0be052d8cfbe6f0927b6bc6602899b
4
- data.tar.gz: ebcdd9d8d306a0edb62276a4598b4c1c80c6eeaf813ccc9d52bfea464896ef74
3
+ metadata.gz: 6f4b811f0c5014e6f325ac55406d225454a2101cc7576df15657ebc74cf47542
4
+ data.tar.gz: 20c6095b149238c31501bd0be8eb4363a9b22011f750d5f2d68e3f8b730bb970
5
5
  SHA512:
6
- metadata.gz: e071e16635f146c09550e0a835eefa1c61875339c9a64ce7283932fd646cc4401e7ea1acc0e8d0c7f7b60a20e519cce5f90eb23efdb8671b5372d02367f7bc95
7
- data.tar.gz: 696bf465fe94fbfe76539a513b8d143373a42100ffd236b96317c5f3ffef1251d9ba3969048ad64cb87dc267dc1d7360bb1d3126a0ce9157fd3e53c5283d2130
6
+ metadata.gz: af600463b83509c10417cc90fa808b4148baaa0961bde7aa2d1cdea98c6537a68fdde456af2d700a1e8d795a49fbc9aef3f61d68cc81949132abd8d9165ed19e
7
+ data.tar.gz: 28977b792b9f957e57bf8386b759a59257d0a74c161038ae7f2f4a1b0f8016b8c3828cf71092920c6fd14fd6640e5ab4dbf987dec257fc2ff54061f7342c20bf
data/CHANGELOG.md CHANGED
@@ -2,6 +2,35 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [2.8.0] - 2024-12-10
6
+
7
+ ### Added
8
+
9
+ * DI: Dynamic instrumentation is now available in Ruby as a Preview
10
+ * AppSec: Add SQL injection detection for ActiveRecord for following adapters: `mysql2`, `postgresql`, and `sqlite3` ([#4167][])
11
+ * Telemetry: Add environment variable to disable logs ([#4153][])
12
+ * Integrations: Add configuration option `on_error` to Elasticsearch tracing ([#4066][])
13
+
14
+ ### Changed
15
+
16
+ * Upgrade libdatadog dependency to 14.3.1 ([#4196][])
17
+ * Profiling: Require Ruby 3.1+ for heap profiling ([#4178][])
18
+ * AppSec: Update libddwaf to 1.18.0.0.0 ([#4164][])
19
+ * Single-step: Lower SSI GLIBC requirements down to 2.17 ([#4137][])
20
+
21
+ ### Fixed
22
+
23
+ * Integrations: Avoid loading `ActiveSupport::Cache::RedisCacheStore`, which tries to load `redis >= 4.0.1` regardless of the version of Redis the host application has installed ([#4197][])
24
+ * Profiling: Fix unsafe initialization when using profiler with otel tracing ([#4195][])
25
+ * Single-step: Add safe NOOP injection script for very old rubies ([#4140][])
26
+
27
+ ## [2.7.1] - 2024-11-28
28
+
29
+ ### Fixed
30
+
31
+ * Tracing: Fix missing version tag ([#4075][])
32
+ * Profiling: Fix profiling not loading in certain situations on Ruby 2.5 and 3.3 ([#4161][])
33
+
5
34
  ## [2.7.0] - 2024-11-13
6
35
 
7
36
  ### Added
@@ -3028,7 +3057,8 @@ Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.3.1
3028
3057
  Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
3029
3058
 
3030
3059
 
3031
- [Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.7.0...master
3060
+ [Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.8.0...master
3061
+ [2.8.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.7.1...v2.8.0
3032
3062
  [2.7.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.6.0...v2.7.0
3033
3063
  [2.6.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.5.0...v2.6.0
3034
3064
  [2.5.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.4.0...v2.5.0
@@ -4473,10 +4503,22 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
4473
4503
  [#4027]: https://github.com/DataDog/dd-trace-rb/issues/4027
4474
4504
  [#4033]: https://github.com/DataDog/dd-trace-rb/issues/4033
4475
4505
  [#4065]: https://github.com/DataDog/dd-trace-rb/issues/4065
4506
+ [#4066]: https://github.com/DataDog/dd-trace-rb/issues/4066
4507
+ [#4075]: https://github.com/DataDog/dd-trace-rb/issues/4075
4476
4508
  [#4078]: https://github.com/DataDog/dd-trace-rb/issues/4078
4477
4509
  [#4082]: https://github.com/DataDog/dd-trace-rb/issues/4082
4478
4510
  [#4083]: https://github.com/DataDog/dd-trace-rb/issues/4083
4479
4511
  [#4085]: https://github.com/DataDog/dd-trace-rb/issues/4085
4512
+ [#4137]: https://github.com/DataDog/dd-trace-rb/issues/4137
4513
+ [#4140]: https://github.com/DataDog/dd-trace-rb/issues/4140
4514
+ [#4153]: https://github.com/DataDog/dd-trace-rb/issues/4153
4515
+ [#4161]: https://github.com/DataDog/dd-trace-rb/issues/4161
4516
+ [#4164]: https://github.com/DataDog/dd-trace-rb/issues/4164
4517
+ [#4167]: https://github.com/DataDog/dd-trace-rb/issues/4167
4518
+ [#4178]: https://github.com/DataDog/dd-trace-rb/issues/4178
4519
+ [#4195]: https://github.com/DataDog/dd-trace-rb/issues/4195
4520
+ [#4196]: https://github.com/DataDog/dd-trace-rb/issues/4196
4521
+ [#4197]: https://github.com/DataDog/dd-trace-rb/issues/4197
4480
4522
  [@AdrianLC]: https://github.com/AdrianLC
4481
4523
  [@Azure7111]: https://github.com/Azure7111
4482
4524
  [@BabyGroot]: https://github.com/BabyGroot
@@ -226,7 +226,8 @@ static void trigger_sample_for_thread(
226
226
  long current_monotonic_wall_time_ns,
227
227
  ddog_CharSlice *ruby_vm_type,
228
228
  ddog_CharSlice *class_name,
229
- bool is_gvl_waiting_state
229
+ bool is_gvl_waiting_state,
230
+ bool is_safe_to_allocate_objects
230
231
  );
231
232
  static VALUE _native_thread_list(VALUE self);
232
233
  static struct per_thread_context *get_or_create_context_for(VALUE thread, struct thread_context_collector_state *state);
@@ -246,7 +247,12 @@ static long cpu_time_now_ns(struct per_thread_context *thread_context);
246
247
  static long thread_id_for(VALUE thread);
247
248
  static VALUE _native_stats(VALUE self, VALUE collector_instance);
248
249
  static VALUE _native_gc_tracking(VALUE self, VALUE collector_instance);
249
- static void trace_identifiers_for(struct thread_context_collector_state *state, VALUE thread, struct trace_identifiers *trace_identifiers_result);
250
+ static void trace_identifiers_for(
251
+ struct thread_context_collector_state *state,
252
+ VALUE thread,
253
+ struct trace_identifiers *trace_identifiers_result,
254
+ bool is_safe_to_allocate_objects
255
+ );
250
256
  static bool should_collect_resource(VALUE root_span);
251
257
  static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
252
258
  static VALUE thread_list(struct thread_context_collector_state *state);
@@ -259,7 +265,8 @@ static void ddtrace_otel_trace_identifiers_for(
259
265
  VALUE *root_span,
260
266
  VALUE *numeric_span_id,
261
267
  VALUE active_span,
262
- VALUE otel_values
268
+ VALUE otel_values,
269
+ bool is_safe_to_allocate_objects
263
270
  );
264
271
  static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE skipped_samples);
265
272
  static bool handle_gvl_waiting(
@@ -278,7 +285,8 @@ static VALUE _native_apply_delta_to_cpu_time_at_previous_sample_ns(DDTRACE_UNUSE
278
285
  static void otel_without_ddtrace_trace_identifiers_for(
279
286
  struct thread_context_collector_state *state,
280
287
  VALUE thread,
281
- struct trace_identifiers *trace_identifiers_result
288
+ struct trace_identifiers *trace_identifiers_result,
289
+ bool is_safe_to_allocate_objects
282
290
  );
283
291
  static struct otel_span otel_span_from(VALUE otel_context, VALUE otel_current_span_key);
284
292
  static uint64_t otel_span_id_to_uint(VALUE otel_span_id);
@@ -647,7 +655,8 @@ static void update_metrics_and_sample(
647
655
  current_monotonic_wall_time_ns,
648
656
  NULL,
649
657
  NULL,
650
- is_gvl_waiting_state
658
+ is_gvl_waiting_state,
659
+ /* is_safe_to_allocate_objects: */ true // We called from a context that's safe to run any regular code, including allocations
651
660
  );
652
661
  }
653
662
 
@@ -833,7 +842,10 @@ static void trigger_sample_for_thread(
833
842
  // These two labels are only used for allocation profiling; @ivoanjo: may want to refactor this at some point?
834
843
  ddog_CharSlice *ruby_vm_type,
835
844
  ddog_CharSlice *class_name,
836
- bool is_gvl_waiting_state
845
+ bool is_gvl_waiting_state,
846
+ // If the Ruby VM is at a state that can allocate objects safely, or not. Added for allocation profiling: we're not
847
+ // allowed to allocate objects (or raise exceptions) when inside the NEWOBJ tracepoint.
848
+ bool is_safe_to_allocate_objects
837
849
  ) {
838
850
  int max_label_count =
839
851
  1 + // thread id
@@ -872,11 +884,11 @@ static void trigger_sample_for_thread(
872
884
  }
873
885
 
874
886
  struct trace_identifiers trace_identifiers_result = {.valid = false, .trace_endpoint = Qnil};
875
- trace_identifiers_for(state, thread, &trace_identifiers_result);
887
+ trace_identifiers_for(state, thread, &trace_identifiers_result, is_safe_to_allocate_objects);
876
888
 
877
889
  if (!trace_identifiers_result.valid && state->otel_context_enabled != OTEL_CONTEXT_ENABLED_FALSE) {
878
890
  // If we couldn't get something with ddtrace, let's see if we can get some trace identifiers from opentelemetry directly
879
- otel_without_ddtrace_trace_identifiers_for(state, thread, &trace_identifiers_result);
891
+ otel_without_ddtrace_trace_identifiers_for(state, thread, &trace_identifiers_result, is_safe_to_allocate_objects);
880
892
  }
881
893
 
882
894
  if (trace_identifiers_result.valid) {
@@ -1289,7 +1301,12 @@ static VALUE _native_gc_tracking(DDTRACE_UNUSED VALUE _self, VALUE collector_ins
1289
1301
  }
1290
1302
 
1291
1303
  // Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
1292
- static void trace_identifiers_for(struct thread_context_collector_state *state, VALUE thread, struct trace_identifiers *trace_identifiers_result) {
1304
+ static void trace_identifiers_for(
1305
+ struct thread_context_collector_state *state,
1306
+ VALUE thread,
1307
+ struct trace_identifiers *trace_identifiers_result,
1308
+ bool is_safe_to_allocate_objects
1309
+ ) {
1293
1310
  if (state->otel_context_enabled == OTEL_CONTEXT_ENABLED_ONLY) return;
1294
1311
  if (state->tracer_context_key == MISSING_TRACER_CONTEXT_KEY) return;
1295
1312
 
@@ -1308,7 +1325,9 @@ static void trace_identifiers_for(struct thread_context_collector_state *state,
1308
1325
 
1309
1326
  VALUE numeric_span_id = Qnil;
1310
1327
 
1311
- if (otel_values != Qnil) ddtrace_otel_trace_identifiers_for(state, &active_trace, &root_span, &numeric_span_id, active_span, otel_values);
1328
+ if (otel_values != Qnil) {
1329
+ ddtrace_otel_trace_identifiers_for(state, &active_trace, &root_span, &numeric_span_id, active_span, otel_values, is_safe_to_allocate_objects);
1330
+ }
1312
1331
 
1313
1332
  if (root_span == Qnil || (active_span == Qnil && numeric_span_id == Qnil)) return;
1314
1333
 
@@ -1474,7 +1493,8 @@ void thread_context_collector_sample_allocation(VALUE self_instance, unsigned in
1474
1493
  INVALID_TIME, // For now we're not collecting timestamps for allocation events, as per profiling team internal discussions
1475
1494
  &ruby_vm_type,
1476
1495
  optional_class_name,
1477
- false
1496
+ /* is_gvl_waiting_state: */ false,
1497
+ /* is_safe_to_allocate_objects: */ false // Not safe to allocate further inside the NEWOBJ tracepoint
1478
1498
  );
1479
1499
  }
1480
1500
 
@@ -1529,11 +1549,18 @@ static VALUE read_otel_current_span_key_const(DDTRACE_UNUSED VALUE _unused) {
1529
1549
  return rb_const_get(trace_module, rb_intern("CURRENT_SPAN_KEY"));
1530
1550
  }
1531
1551
 
1532
- static VALUE get_otel_current_span_key(struct thread_context_collector_state *state) {
1552
+ static VALUE get_otel_current_span_key(struct thread_context_collector_state *state, bool is_safe_to_allocate_objects) {
1533
1553
  if (state->otel_current_span_key == Qtrue) { // Qtrue means we haven't tried to extract it yet
1554
+ if (!is_safe_to_allocate_objects) {
1555
+ // Calling read_otel_current_span_key_const below can trigger exceptions and arbitrary Ruby code running (e.g.
1556
+ // `const_missing`, etc). Not safe to call in this situation, so we just skip otel info for this sample.
1557
+ return Qnil;
1558
+ }
1559
+
1534
1560
  // If this fails, we want to fail gracefully, rather than raise an exception (e.g. if the opentelemetry gem
1535
1561
  // gets refactored, we should not fall on our face)
1536
1562
  VALUE span_key = rb_protect(read_otel_current_span_key_const, Qnil, NULL);
1563
+ rb_set_errinfo(Qnil); // **Clear any pending exception after ignoring it**
1537
1564
 
1538
1565
  // Note that this gets set to Qnil if we failed to extract the correct value, and thus we won't try to extract it again
1539
1566
  state->otel_current_span_key = span_key;
@@ -1550,7 +1577,8 @@ static void ddtrace_otel_trace_identifiers_for(
1550
1577
  VALUE *root_span,
1551
1578
  VALUE *numeric_span_id,
1552
1579
  VALUE active_span,
1553
- VALUE otel_values
1580
+ VALUE otel_values,
1581
+ bool is_safe_to_allocate_objects
1554
1582
  ) {
1555
1583
  VALUE resolved_numeric_span_id =
1556
1584
  active_span == Qnil ?
@@ -1561,7 +1589,7 @@ static void ddtrace_otel_trace_identifiers_for(
1561
1589
 
1562
1590
  if (resolved_numeric_span_id == Qnil) return;
1563
1591
 
1564
- VALUE otel_current_span_key = get_otel_current_span_key(state);
1592
+ VALUE otel_current_span_key = get_otel_current_span_key(state, is_safe_to_allocate_objects);
1565
1593
  if (otel_current_span_key == Qnil) return;
1566
1594
  VALUE current_trace = *active_trace;
1567
1595
 
@@ -1640,14 +1668,15 @@ static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self
1640
1668
  static void otel_without_ddtrace_trace_identifiers_for(
1641
1669
  struct thread_context_collector_state *state,
1642
1670
  VALUE thread,
1643
- struct trace_identifiers *trace_identifiers_result
1671
+ struct trace_identifiers *trace_identifiers_result,
1672
+ bool is_safe_to_allocate_objects
1644
1673
  ) {
1645
1674
  VALUE context_storage = rb_thread_local_aref(thread, otel_context_storage_id /* __opentelemetry_context_storage__ */);
1646
1675
 
1647
1676
  // If it exists, context_storage is expected to be an Array[OpenTelemetry::Context]
1648
1677
  if (context_storage == Qnil || !RB_TYPE_P(context_storage, T_ARRAY)) return;
1649
1678
 
1650
- VALUE otel_current_span_key = get_otel_current_span_key(state);
1679
+ VALUE otel_current_span_key = get_otel_current_span_key(state, is_safe_to_allocate_objects);
1651
1680
  if (otel_current_span_key == Qnil) return;
1652
1681
 
1653
1682
  int active_context_index = RARRAY_LEN(context_storage) - 1;
@@ -1939,7 +1968,8 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
1939
1968
  gvl_waiting_started_wall_time_ns,
1940
1969
  NULL,
1941
1970
  NULL,
1942
- false // This is the extra sample before the wait begun; only the next sample will be in the gvl waiting state
1971
+ /* is_gvl_waiting_state: */ false, // This is the extra sample before the wait begun; only the next sample will be in the gvl waiting state
1972
+ /* is_safe_to_allocate_objects: */ true // This is similar to a regular cpu/wall sample, so it's also safe
1943
1973
  );
1944
1974
  }
1945
1975
 
@@ -131,6 +131,9 @@ end
131
131
 
132
132
  have_func "malloc_stats"
133
133
 
134
+ # On Ruby 2.5 and 3.3, this symbol was not visible. It is on 2.6 to 3.2, as well as 3.4+
135
+ $defs << "-DNO_RB_OBJ_INFO" if RUBY_VERSION.start_with?("2.5", "3.3")
136
+
134
137
  # On older Rubies, rb_postponed_job_preregister/rb_postponed_job_trigger did not exist
135
138
  $defs << "-DNO_POSTPONED_TRIGGER" if RUBY_VERSION < "3.3"
136
139
 
@@ -167,11 +170,6 @@ $defs << "-DNO_THREAD_TID" if RUBY_VERSION < "3.1"
167
170
  # On older Rubies, there was no jit_return member on the rb_control_frame_t struct
168
171
  $defs << "-DNO_JIT_RETURN" if RUBY_VERSION < "3.1"
169
172
 
170
- # On older Rubies, rb_gc_force_recycle allowed to free objects in a way that
171
- # would be invisible to free tracepoints, finalizers and without cleaning
172
- # obj_to_id_tbl mappings.
173
- $defs << "-DHAVE_WORKING_RB_GC_FORCE_RECYCLE" if RUBY_VERSION < "3.1"
174
-
175
173
  # On older Rubies, there are no Ractors
176
174
  $defs << "-DNO_RACTORS" if RUBY_VERSION < "3"
177
175
 
@@ -181,9 +179,6 @@ $defs << "-DNO_IMEMO_NAME" if RUBY_VERSION < "3"
181
179
  # On older Rubies, objects would not move
182
180
  $defs << "-DNO_T_MOVED" if RUBY_VERSION < "2.7"
183
181
 
184
- # On older Rubies, there was no RUBY_SEEN_OBJ_ID flag
185
- $defs << "-DNO_SEEN_OBJ_ID_FLAG" if RUBY_VERSION < "2.7"
186
-
187
182
  # On older Rubies, rb_global_vm_lock_struct did not include the owner field
188
183
  $defs << "-DNO_GVL_OWNER" if RUBY_VERSION < "2.6"
189
184
 
@@ -7,10 +7,6 @@
7
7
  #include "libdatadog_helpers.h"
8
8
  #include "time_helpers.h"
9
9
 
10
- #if (defined(HAVE_WORKING_RB_GC_FORCE_RECYCLE) && ! defined(NO_SEEN_OBJ_ID_FLAG))
11
- #define CAN_APPLY_GC_FORCE_RECYCLE_BUG_WORKAROUND
12
- #endif
13
-
14
10
  // Minimum age (in GC generations) of heap objects we want to include in heap
15
11
  // recorder iterations. Object with age 0 represent objects that have yet to undergo
16
12
  // a GC and, thus, may just be noise/trash at instant of iteration and are usually not
@@ -123,9 +119,6 @@ typedef struct {
123
119
  // Pointer to the (potentially partial) object_record containing metadata about an ongoing recording.
124
120
  // When NULL, this symbolizes an unstarted/invalid recording.
125
121
  object_record *object_record;
126
- // A flag to track whether we had to force set the RUBY_FL_SEEN_OBJ_ID flag on this object
127
- // as part of our workaround around rb_gc_force_recycle issues.
128
- bool did_recycle_workaround;
129
122
  } recording;
130
123
 
131
124
  struct heap_recorder {
@@ -342,46 +335,12 @@ void start_heap_allocation_recording(heap_recorder *heap_recorder, VALUE new_obj
342
335
  rb_raise(rb_eRuntimeError, "Detected a bignum object id. These are not supported by heap profiling.");
343
336
  }
344
337
 
345
- bool did_recycle_workaround = false;
346
-
347
- #ifdef CAN_APPLY_GC_FORCE_RECYCLE_BUG_WORKAROUND
348
- // If we are in a ruby version that has a working rb_gc_force_recycle implementation,
349
- // its usage may lead to an object being re-used outside of the typical GC cycle.
350
- //
351
- // This re-use is in theory invisible to us unless we're lucky enough to sample both
352
- // the original object and the replacement that uses the recycled slot.
353
- //
354
- // In practice, we've observed (https://github.com/DataDog/dd-trace-rb/pull/3366)
355
- // that non-noop implementations of rb_gc_force_recycle have an implementation bug
356
- // which results in the object that re-used the recycled slot inheriting the same
357
- // object id without setting the FL_SEEN_OBJ_ID flag. We rely on this knowledge to
358
- // "observe" implicit frees when an object we are tracking is force-recycled.
359
- //
360
- // However, it may happen that we start tracking a new object and that object was
361
- // allocated on a recycled slot. Due to the bug, this object would be missing the
362
- // FL_SEEN_OBJ_ID flag even though it was not recycled itself. If we left it be,
363
- // when we're doing our liveness check, the absence of the flag would trigger our
364
- // implicit free workaround and the object would be inferred as recycled even though
365
- // it might still be alive.
366
- //
367
- // Thus, if we detect that this new allocation is already missing the flag at the start
368
- // of the heap allocation recording, we force-set it. This should be safe since we
369
- // just called rb_obj_id on it above and the expectation is that any flaggable object
370
- // that goes through it ends up with the flag set (as evidenced by the GC_ASSERT
371
- // lines in https://github.com/ruby/ruby/blob/4a8d7246d15b2054eacb20f8ab3d29d39a3e7856/gc.c#L4050C14-L4050C14).
372
- if (RB_FL_ABLE(new_obj) && !RB_FL_TEST(new_obj, RUBY_FL_SEEN_OBJ_ID)) {
373
- RB_FL_SET(new_obj, RUBY_FL_SEEN_OBJ_ID);
374
- did_recycle_workaround = true;
375
- }
376
- #endif
377
-
378
338
  heap_recorder->active_recording = (recording) {
379
339
  .object_record = object_record_new(FIX2LONG(ruby_obj_id), NULL, (live_object_data) {
380
340
  .weight = weight * heap_recorder->sample_rate,
381
341
  .class = alloc_class != NULL ? string_from_char_slice(*alloc_class) : NULL,
382
342
  .alloc_gen = rb_gc_count(),
383
- }),
384
- .did_recycle_workaround = did_recycle_workaround,
343
+ }),
385
344
  };
386
345
  }
387
346
 
@@ -685,41 +644,6 @@ static int st_object_record_update(st_data_t key, st_data_t value, st_data_t ext
685
644
 
686
645
  // If we got this far, then we found a valid live object for the tracked id.
687
646
 
688
- #ifdef CAN_APPLY_GC_FORCE_RECYCLE_BUG_WORKAROUND
689
- // If we are in a ruby version that has a working rb_gc_force_recycle implementation,
690
- // its usage may lead to an object being re-used outside of the typical GC cycle.
691
- //
692
- // This re-use is in theory invisible to us and would mean that the ref from which we
693
- // collected the object_record metadata may not be the same as the current ref and
694
- // thus any further reporting would be innacurately attributed to stale metadata.
695
- //
696
- // In practice, there is a way for us to notice that this happened because of a bug
697
- // in the implementation of rb_gc_force_recycle. Our heap profiler relies on object
698
- // ids and id2ref to detect whether objects are still alive. Turns out that when an
699
- // object with an id is re-used via rb_gc_force_recycle, it will "inherit" the ID
700
- // of the old object but it will NOT have the FL_SEEN_OBJ_ID as per the experiment
701
- // in https://github.com/DataDog/dd-trace-rb/pull/3360#discussion_r1442823517
702
- //
703
- // Thus, if we detect that the ref we just resolved above is missing this flag, we can
704
- // safely say re-use happened and thus treat it as an implicit free of the object
705
- // we were tracking (the original one which got recycled).
706
- if (RB_FL_ABLE(ref) && !RB_FL_TEST(ref, RUBY_FL_SEEN_OBJ_ID)) {
707
-
708
- // NOTE: We don't really need to set this flag for heap recorder to work correctly
709
- // but doing so partially mitigates a bug in runtimes with working rb_gc_force_recycle
710
- // which leads to broken invariants and leaking of entries in obj_to_id and id_to_obj
711
- // tables in objspace. We already do the same thing when we sample a recycled object,
712
- // here we apply it as well to objects that replace recycled objects that were being
713
- // tracked. More details in https://github.com/DataDog/dd-trace-rb/pull/3366
714
- RB_FL_SET(ref, RUBY_FL_SEEN_OBJ_ID);
715
-
716
- on_committed_object_record_cleanup(recorder, record);
717
- recorder->stats_last_update.objects_dead++;
718
- return ST_DELETE;
719
- }
720
-
721
- #endif
722
-
723
647
  if (
724
648
  recorder->size_enabled &&
725
649
  recorder->update_include_old && // We only update sizes when doing a full update
@@ -732,6 +656,10 @@ static int st_object_record_update(st_data_t key, st_data_t value, st_data_t ext
732
656
  record->object_data.is_frozen = RB_OBJ_FROZEN(ref);
733
657
  }
734
658
 
659
+ // Ensure that ref is kept on the stack so the Ruby garbage collector does not try to clean up the object before this
660
+ // point.
661
+ RB_GC_GUARD(ref);
662
+
735
663
  recorder->stats_last_update.objects_alive++;
736
664
  if (record->object_data.is_frozen) {
737
665
  recorder->stats_last_update.objects_frozen++;
@@ -803,18 +731,12 @@ static int update_object_record_entry(DDTRACE_UNUSED st_data_t *key, st_data_t *
803
731
  object_record *new_object_record = recording.object_record;
804
732
  if (existing) {
805
733
  object_record *existing_record = (object_record*) (*value);
806
- if (recording.did_recycle_workaround) {
807
- // In this case, it's possible for an object id to be re-used and we were lucky enough to have
808
- // sampled both the original object and the replacement so cleanup the old one and replace it with
809
- // the new object_record (i.e. treat this as a combined free+allocation).
810
- on_committed_object_record_cleanup(update_data->heap_recorder, existing_record);
811
- } else {
812
- // This is not supposed to happen, raising...
813
- VALUE existing_inspect = object_record_inspect(existing_record);
814
- VALUE new_inspect = object_record_inspect(new_object_record);
815
- rb_raise(rb_eRuntimeError, "Object ids are supposed to be unique. We got 2 allocation recordings with "
816
- "the same id. previous=%"PRIsVALUE" new=%"PRIsVALUE, existing_inspect, new_inspect);
817
- }
734
+
735
+ // This is not supposed to happen, raising...
736
+ VALUE existing_inspect = object_record_inspect(existing_record);
737
+ VALUE new_inspect = object_record_inspect(new_object_record);
738
+ rb_raise(rb_eRuntimeError, "Object ids are supposed to be unique. We got 2 allocation recordings with "
739
+ "the same id. previous=%"PRIsVALUE" new=%"PRIsVALUE, existing_inspect, new_inspect);
818
740
  }
819
741
  // Always carry on with the update, we want the new record to be there at the end
820
742
  (*value) = (st_data_t) new_object_record;
@@ -158,7 +158,7 @@ bool is_current_thread_holding_the_gvl(void) {
158
158
  //
159
159
  // Thus an incorrect `is_current_thread_holding_the_gvl` result may lead to issues inside `rb_postponed_job_register_one`.
160
160
  //
161
- // For this reason we currently do not enable the new Ruby profiler on Ruby 2.5 by default, and we print a
161
+ // For this reason we default to use the "no signals workaround" on Ruby 2.5 by default, and we print a
162
162
  // warning when customers force-enable it.
163
163
  bool gvl_acquired = vm->gvl.acquired != 0;
164
164
  rb_thread_t *current_owner = vm->running_thread;
@@ -587,16 +587,13 @@ int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *st
587
587
  // Taken from upstream vm_insnhelper.c at commit 5f10bd634fb6ae8f74a4ea730176233b0ca96954 (March 2022, Ruby 3.2 trunk)
588
588
  // Copyright (C) 2007 Koichi Sasada
589
589
  // to support our custom rb_profile_frames (see above)
590
- // Modifications: None
590
+ // Modifications:
591
+ // * Removed debug checks (they were ifdef'd out anyway)
591
592
  static rb_callable_method_entry_t *
592
593
  check_method_entry(VALUE obj, int can_be_svar)
593
594
  {
594
595
  if (obj == Qfalse) return NULL;
595
596
 
596
- #if VM_CHECK_MODE > 0
597
- if (!RB_TYPE_P(obj, T_IMEMO)) rb_bug("check_method_entry: unknown type: %s", rb_obj_info(obj));
598
- #endif
599
-
600
597
  switch (imemo_type(obj)) {
601
598
  case imemo_ment:
602
599
  return (rb_callable_method_entry_t *)obj;
@@ -608,9 +605,6 @@ check_method_entry(VALUE obj, int can_be_svar)
608
605
  }
609
606
  // fallthrough
610
607
  default:
611
- #if VM_CHECK_MODE > 0
612
- rb_bug("check_method_entry: svar should not be there:");
613
- #endif
614
608
  return NULL;
615
609
  }
616
610
  }
@@ -37,6 +37,7 @@ static VALUE _native_trigger_holding_the_gvl_signal_handler_on(DDTRACE_UNUSED VA
37
37
  static VALUE _native_enforce_success(DDTRACE_UNUSED VALUE _self, VALUE syserr_errno, VALUE with_gvl);
38
38
  static void *trigger_enforce_success(void *trigger_args);
39
39
  static VALUE _native_malloc_stats(DDTRACE_UNUSED VALUE _self);
40
+ static VALUE _native_safe_object_info(DDTRACE_UNUSED VALUE _self, VALUE obj);
40
41
 
41
42
  void DDTRACE_EXPORT Init_datadog_profiling_native_extension(void) {
42
43
  VALUE datadog_module = rb_define_module("Datadog");
@@ -72,6 +73,7 @@ void DDTRACE_EXPORT Init_datadog_profiling_native_extension(void) {
72
73
  rb_define_singleton_method(testing_module, "_native_trigger_holding_the_gvl_signal_handler_on", _native_trigger_holding_the_gvl_signal_handler_on, 1);
73
74
  rb_define_singleton_method(testing_module, "_native_enforce_success", _native_enforce_success, 2);
74
75
  rb_define_singleton_method(testing_module, "_native_malloc_stats", _native_malloc_stats, 0);
76
+ rb_define_singleton_method(testing_module, "_native_safe_object_info", _native_safe_object_info, 1);
75
77
  }
76
78
 
77
79
  static VALUE native_working_p(DDTRACE_UNUSED VALUE _self) {
@@ -265,3 +267,7 @@ static VALUE _native_malloc_stats(DDTRACE_UNUSED VALUE _self) {
265
267
  return Qfalse;
266
268
  #endif
267
269
  }
270
+
271
+ static VALUE _native_safe_object_info(DDTRACE_UNUSED VALUE _self, VALUE obj) {
272
+ return rb_str_new_cstr(safe_object_info(obj));
273
+ }
@@ -3,6 +3,7 @@
3
3
 
4
4
  #include "ruby_helpers.h"
5
5
  #include "private_vm_api_access.h"
6
+ #include "extconf.h"
6
7
 
7
8
  // The following global variables are initialized at startup to save expensive lookups later.
8
9
  // They are not expected to be mutated outside of init.
@@ -219,17 +220,26 @@ static bool ruby_is_obj_with_class(VALUE obj) {
219
220
  return false;
220
221
  }
221
222
 
222
- // These two functions are not present in the VM headers, but are public symbols that can be invoked.
223
+ // This function is not present in the VM headers, but is a public symbol that can be invoked.
223
224
  int rb_objspace_internal_object_p(VALUE obj);
224
- const char *rb_obj_info(VALUE obj);
225
+
226
+ #ifdef NO_RB_OBJ_INFO
227
+ const char* safe_object_info(DDTRACE_UNUSED VALUE obj) { return "(No rb_obj_info for current Ruby)"; }
228
+ #else
229
+ // This function is a public symbol, but not on all Rubies; `safe_object_info` below abstracts this, and
230
+ // should be used instead.
231
+ const char *rb_obj_info(VALUE obj);
232
+
233
+ const char* safe_object_info(VALUE obj) { return rb_obj_info(obj); }
234
+ #endif
225
235
 
226
236
  VALUE ruby_safe_inspect(VALUE obj) {
227
237
  if (!ruby_is_obj_with_class(obj)) return rb_str_new_cstr("(Not an object)");
228
- if (rb_objspace_internal_object_p(obj)) return rb_sprintf("(VM Internal, %s)", rb_obj_info(obj));
238
+ if (rb_objspace_internal_object_p(obj)) return rb_sprintf("(VM Internal, %s)", safe_object_info(obj));
229
239
  // @ivoanjo: I saw crashes on Ruby 3.1.4 when trying to #inspect matchdata objects. I'm not entirely sure why this
230
240
  // is needed, but since we only use this method for debug purposes I put in this alternative and decided not to
231
241
  // dig deeper.
232
- if (rb_type(obj) == RUBY_T_MATCH) return rb_sprintf("(MatchData, %s)", rb_obj_info(obj));
242
+ if (rb_type(obj) == RUBY_T_MATCH) return rb_sprintf("(MatchData, %s)", safe_object_info(obj));
233
243
  if (rb_respond_to(obj, inspect_id)) return rb_sprintf("%+"PRIsVALUE, obj);
234
244
  if (rb_respond_to(obj, to_s_id)) return rb_sprintf("%"PRIsVALUE, obj);
235
245
 
@@ -90,3 +90,7 @@ size_t ruby_obj_memsize_of(VALUE obj);
90
90
  // return a string with the result of that call. Elsif the object responds to
91
91
  // 'to_s', return a string with the result of that call. Otherwise, return Qnil.
92
92
  VALUE ruby_safe_inspect(VALUE obj);
93
+
94
+ // You probably want ruby_safe_inspect instead; this is a lower-level dependency
95
+ // of it, that's being exposed here just to facilitate testing.
96
+ const char* safe_object_info(VALUE obj);
@@ -258,8 +258,6 @@ static VALUE _native_check_heap_hashes(DDTRACE_UNUSED VALUE _self, VALUE locatio
258
258
  static VALUE _native_start_fake_slow_heap_serialization(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
259
259
  static VALUE _native_end_fake_slow_heap_serialization(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
260
260
  static VALUE _native_debug_heap_recorder(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
261
- static VALUE _native_gc_force_recycle(DDTRACE_UNUSED VALUE _self, VALUE obj);
262
- static VALUE _native_has_seen_id_flag(DDTRACE_UNUSED VALUE _self, VALUE obj);
263
261
  static VALUE _native_stats(DDTRACE_UNUSED VALUE self, VALUE instance);
264
262
  static VALUE build_profile_stats(profile_slot *slot, long serialization_time_ns, long heap_iteration_prep_time_ns, long heap_profile_build_time_ns);
265
263
  static VALUE _native_is_object_recorded(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE object_id);
@@ -297,10 +295,6 @@ void stack_recorder_init(VALUE profiling_module) {
297
295
  _native_end_fake_slow_heap_serialization, 1);
298
296
  rb_define_singleton_method(testing_module, "_native_debug_heap_recorder",
299
297
  _native_debug_heap_recorder, 1);
300
- rb_define_singleton_method(testing_module, "_native_gc_force_recycle",
301
- _native_gc_force_recycle, 1);
302
- rb_define_singleton_method(testing_module, "_native_has_seen_id_flag",
303
- _native_has_seen_id_flag, 1);
304
298
  rb_define_singleton_method(testing_module, "_native_is_object_recorded?", _native_is_object_recorded, 2);
305
299
  rb_define_singleton_method(testing_module, "_native_heap_recorder_reset_last_update", _native_heap_recorder_reset_last_update, 1);
306
300
  rb_define_singleton_method(testing_module, "_native_recorder_after_gc_step", _native_recorder_after_gc_step, 1);
@@ -1006,34 +1000,6 @@ static VALUE _native_debug_heap_recorder(DDTRACE_UNUSED VALUE _self, VALUE recor
1006
1000
  return heap_recorder_testonly_debug(state->heap_recorder);
1007
1001
  }
1008
1002
 
1009
- #pragma GCC diagnostic push
1010
- // rb_gc_force_recycle was deprecated in latest versions of Ruby and is a noop.
1011
- #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1012
- #pragma GCC diagnostic ignored "-Wunused-parameter"
1013
- // This method exists only to enable testing Datadog::Profiling::StackRecorder behavior using RSpec.
1014
- // It SHOULD NOT be used for other purposes.
1015
- static VALUE _native_gc_force_recycle(DDTRACE_UNUSED VALUE _self, VALUE obj) {
1016
- #ifdef HAVE_WORKING_RB_GC_FORCE_RECYCLE
1017
- rb_gc_force_recycle(obj);
1018
- #endif
1019
- return Qnil;
1020
- }
1021
- #pragma GCC diagnostic pop
1022
-
1023
- // This method exists only to enable testing Datadog::Profiling::StackRecorder behavior using RSpec.
1024
- // It SHOULD NOT be used for other purposes.
1025
- static VALUE _native_has_seen_id_flag(DDTRACE_UNUSED VALUE _self, VALUE obj) {
1026
- #ifndef NO_SEEN_OBJ_ID_FLAG
1027
- if (RB_FL_TEST(obj, RUBY_FL_SEEN_OBJ_ID)) {
1028
- return Qtrue;
1029
- } else {
1030
- return Qfalse;
1031
- }
1032
- #else
1033
- return Qfalse;
1034
- #endif
1035
- }
1036
-
1037
1003
  static VALUE _native_stats(DDTRACE_UNUSED VALUE self, VALUE recorder_instance) {
1038
1004
  struct stack_recorder_state *state;
1039
1005
  TypedData_Get_Struct(recorder_instance, struct stack_recorder_state, &stack_recorder_typed_data, state);
@@ -8,7 +8,7 @@ module Datadog
8
8
  module LibdatadogExtconfHelpers
9
9
  # Used to make sure the correct gem version gets loaded, as extconf.rb does not get run with "bundle exec" and thus
10
10
  # may see multiple libdatadog versions. See https://github.com/DataDog/dd-trace-rb/pull/2531 for the horror story.
11
- LIBDATADOG_VERSION = '~> 14.1.0.1.0'
11
+ LIBDATADOG_VERSION = '~> 14.3.1.1.0'
12
12
 
13
13
  # Used as an workaround for a limitation with how dynamic linking works in environments where the datadog gem and
14
14
  # libdatadog are moved after the extension gets compiled.
@@ -3,7 +3,6 @@
3
3
  require_relative 'processor'
4
4
  require_relative 'processor/rule_merger'
5
5
  require_relative 'processor/rule_loader'
6
- require_relative 'processor/actions'
7
6
 
8
7
  module Datadog
9
8
  module AppSec
@@ -52,10 +51,6 @@ module Datadog
52
51
  )
53
52
  return nil unless rules
54
53
 
55
- actions = rules['actions']
56
-
57
- AppSec::Processor::Actions.merge(actions) if actions
58
-
59
54
  data = AppSec::Processor::RuleLoader.load_data(
60
55
  ip_denylist: settings.appsec.ip_denylist,
61
56
  user_id_denylist: settings.appsec.user_id_denylist,
@@ -84,10 +79,8 @@ module Datadog
84
79
  @mutex = Mutex.new
85
80
  end
86
81
 
87
- def reconfigure(ruleset:, actions:, telemetry:)
82
+ def reconfigure(ruleset:, telemetry:)
88
83
  @mutex.synchronize do
89
- AppSec::Processor::Actions.merge(actions)
90
-
91
84
  new = Processor.new(ruleset: ruleset, telemetry: telemetry)
92
85
 
93
86
  if new && new.ready?