Repository: DataDog/dd-trace-php
Pull request #3235: style(prof): fix tons of clippy lints
Author: login: morrisonlevi
Labels: name: profiling
description: Relates to the Continuous Profiler
Created: 2025-05-06T23:48:10Z | Merged: 2025-05-08T06:37:15Z

### Description

Many clippy lints have accumulated. This fixes all of the lints on PHP 8.3/8.4 and on Rust 1.78. It also fixes many lints from Rust 1.86 and nightly. Here are some of the things fixed:

- Using Rust's CStr literal syntax `c""` and converting some engine members from `*const u8` to `*const c_char`.
- Using `core::ptr::eq` for certain pointer comparisons.
- Adjust use of mutable statics to use pointers, see [Disallow references to static mut](https://doc.rust-lang.org/nightly/edition-guide/rust-2024/static-mut-references.html#disallow-references-to-static-mut).

And some non-clippy items:
- Converts `/*` style comments to `//`, which is by far more popular.
- Converts `Safety:` comments to `SAFETY:`, which is mostly what we use everywhere else.
- Updates some of the historical comments to be more accurate in the present.

There were historical reasons for not running clippy in CI. Over time think these have all been fixed, helped by advances in Rust. We should enable clippy soon for profiling. On the versions listed, the only lints on current nightly are [unpredictable function pointer comparisons](https://doc.rust-lang.org/nightly/core/ptr/fn.fn_addr_eq.html). I'm not sure what to do here, other than maybe allow them. But since this is nightly, it may not land in its current state anyway, so I'm not going to sweat it.

### Reviewer checklist
- [ ] Test coverage seems ok.
- [ ] Appropriate labels assigned.



Diff:
diff --git a/Cargo.lock b/Cargo.lock
index 1122df442bd..ed2145101df 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1551,6 +1551,7 @@ dependencies = [
  "rand 0.8.5",
  "rand_distr",
  "rustc-hash 1.1.0",
+ "rustc_version",
  "serde_json",
  "thiserror 2.0.11",
  "tracing",
@@ -4842,6 +4843,15 @@ version = "2.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
 
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
 [[package]]
 name = "rustix"
 version = "0.38.43"
diff --git a/profiling/Cargo.toml b/profiling/Cargo.toml
index ecf9269f6bd..def14004c28 100644
--- a/profiling/Cargo.toml
+++ b/profiling/Cargo.toml
@@ -75,4 +75,7 @@ trigger_time_sample = []
 bindgen = { version = "0.69.4" }
 cc = { version = "1.0" }
 
+# We can remove this when we're using Rust 1.80+
+rustc_version = "0.4"
+
 # profiling release options in root Cargo.toml
diff --git a/profiling/build.rs b/profiling/build.rs
index 9609e5c5bdb..ba174188803 100644
--- a/profiling/build.rs
+++ b/profiling/build.rs
@@ -1,4 +1,5 @@
 use bindgen::callbacks::IntKind;
+use rustc_version::version_meta;
 use std::collections::HashSet;
 use std::path::Path;
 use std::path::PathBuf;
@@ -13,7 +14,7 @@ fn main() {
 
     if !php_config_includes_output.status.success() {
         match String::from_utf8(php_config_includes_output.stderr) {
-            Ok(stderr) => panic!("`php-config failed: {}", stderr),
+            Ok(stderr) => panic!("`php-config failed: {stderr}"),
             Err(err) => panic!(
                 "`php-config` failed, not utf8: {}",
                 String::from_utf8_lossy(err.as_bytes())
@@ -26,7 +27,7 @@ fn main() {
         .expect("Failed to read VERSION file")
         .trim()
         .to_string();
-    println!("cargo:rustc-env=PROFILER_VERSION={}", version);
+    println!("cargo:rustc-env=PROFILER_VERSION={version}");
     println!("cargo:rerun-if-changed=../VERSION");
 
     let php_config_includes = std::str::from_utf8(php_config_includes_output.stdout.as_slice())
@@ -65,7 +66,7 @@ fn php_config_vernum() -> u64 {
 
     if !output.status.success() {
         match String::from_utf8(output.stderr) {
-            Ok(stderr) => panic!("`php-config --vernum` failed: {}", stderr),
+            Ok(stderr) => panic!("`php-config --vernum` failed: {stderr}"),
             Err(err) => panic!(
                 "`php-config --vernum` failed, not utf8: {}",
                 String::from_utf8_lossy(err.as_bytes())
@@ -134,7 +135,7 @@ fn build_zend_php_ffis(
     ];
 
     for file in zai_c_files.iter().chain(ZAI_H_FILES.iter()) {
-        println!("cargo:rerun-if-changed={}", *file);
+        println!("cargo:rerun-if-changed={file}");
     }
 
     let output = Command::new("php-config")
@@ -143,7 +144,10 @@ fn build_zend_php_ffis(
         .expect("Unable to run `php-config`. Is it in your PATH?");
 
     let prefix = String::from_utf8(output.stdout).expect("only utf8 chars work");
-    println!("cargo:rustc-link-search=native={}/lib", prefix.trim());
+    println!(
+        "cargo:rustc-link-search=native={prefix}/lib",
+        prefix = prefix.trim()
+    );
 
     let files = ["src/php_ffi.c", "../ext/handlers_api.c"];
     let post_startup_cb = if post_startup_cb { "1" } else { "0" };
@@ -296,7 +300,18 @@ fn generate_bindings(php_config_includes: &str, fibers: bool, zend_error_observe
         .expect("bindings to be written successfully");
 }
 
+// See https://doc.rust-lang.org/nightly/rustc/check-cfg/cargo-specifics.html
+// We can remove this when we're on Rust 1.80+.
+fn has_check_cfg() -> bool {
+    let meta = version_meta().unwrap();
+    assert_eq!(1, meta.semver.major);
+    meta.semver.minor >= 80
+}
+
 fn cfg_post_startup_cb(vernum: u64) -> bool {
+    if has_check_cfg() {
+        println!("cargo::rustc-check-cfg=cfg(php_post_startup_cb)");
+    }
     if vernum >= 70300 {
         println!("cargo:rustc-cfg=php_post_startup_cb");
         true
@@ -306,6 +321,9 @@ fn cfg_post_startup_cb(vernum: u64) -> bool {
 }
 
 fn cfg_preload(vernum: u64) -> bool {
+    if has_check_cfg() {
+        println!("cargo::rustc-check-cfg=cfg(php_preload)");
+    }
     if vernum >= 70400 {
         println!("cargo:rustc-cfg=php_preload");
         true
@@ -315,6 +333,9 @@ fn cfg_preload(vernum: u64) -> bool {
 }
 
 fn cfg_run_time_cache(vernum: u64) -> bool {
+    if has_check_cfg() {
+        println!("cargo::rustc-check-cfg=cfg(php_run_time_cache)");
+    }
     if vernum >= 80000 {
         println!("cargo:rustc-cfg=php_run_time_cache");
         true
@@ -328,6 +349,9 @@ fn cfg_trigger_time_sample() -> bool {
 }
 
 fn cfg_zend_error_observer(vernum: u64) -> bool {
+    if has_check_cfg() {
+        println!("cargo::rustc-check-cfg=cfg(zend_error_observer, zend_error_observer_80)");
+    }
     if vernum >= 80000 {
         println!("cargo:rustc-cfg=zend_error_observer");
         if vernum < 80100 {
@@ -340,6 +364,10 @@ fn cfg_zend_error_observer(vernum: u64) -> bool {
 }
 
 fn cfg_php_major_version(vernum: u64) {
+    if has_check_cfg() {
+        println!("cargo::rustc-check-cfg=cfg(php7, php8)");
+    }
+
     let major_version = match vernum {
         70000..=79999 => 7,
         80000..=89999 => 8,
@@ -354,6 +382,9 @@ fn cfg_php_major_version(vernum: u64) {
 }
 
 fn cfg_fibers(vernum: u64) -> bool {
+    if has_check_cfg() {
+        println!("cargo::rustc-check-cfg=cfg(php_has_fibers)");
+    }
     if vernum >= 80100 {
         println!("cargo:rustc-cfg=php_has_fibers");
         true
@@ -363,6 +394,10 @@ fn cfg_fibers(vernum: u64) -> bool {
 }
 
 fn cfg_php_feature_flags(vernum: u64) {
+    if has_check_cfg() {
+        println!("cargo::rustc-check-cfg=cfg(php_gc_status, php_zend_compile_string_has_position, php_gc_status_extended, php_frameless, php_opcache_restart_hook, php_zend_mm_set_custom_handlers_ex)");
+    }
+
     if vernum >= 70300 {
         println!("cargo:rustc-cfg=php_gc_status");
     }
@@ -380,6 +415,10 @@ fn cfg_php_feature_flags(vernum: u64) {
 }
 
 fn cfg_zts() {
+    if has_check_cfg() {
+        println!("cargo::rustc-check-cfg=cfg(php_zts)");
+    }
+
     let output = Command::new("php")
         .arg("-n")
         .arg("-r")
@@ -389,8 +428,8 @@ fn cfg_zts() {
 
     if !output.status.success() {
         match String::from_utf8(output.stderr) {
-            Ok(stderr) => panic!("`php failed: {}", stderr),
-            Err(err) => panic!("`php` failed, not utf8: {}", err),
+            Ok(stderr) => panic!("`php failed: {stderr}"),
+            Err(err) => panic!("`php` failed, not utf8: {err}"),
         }
     }
 
diff --git a/profiling/src/allocation/allocation_ge84.rs b/profiling/src/allocation/allocation_ge84.rs
index 7f39a441c38..24244780d5e 100644
--- a/profiling/src/allocation/allocation_ge84.rs
+++ b/profiling/src/allocation/allocation_ge84.rs
@@ -420,7 +420,7 @@ unsafe extern "C" fn alloc_prof_realloc(prev_ptr: *mut c_void, len: size_t) -> *
 
     // during startup, minit, rinit, ... current_execute_data is null
     // we are only interested in allocations during userland operations
-    if zend::ddog_php_prof_get_current_execute_data().is_null() || ptr == prev_ptr {
+    if zend::ddog_php_prof_get_current_execute_data().is_null() || ptr::eq(ptr, prev_ptr) {
         return ptr;
     }
 
diff --git a/profiling/src/allocation/allocation_le83.rs b/profiling/src/allocation/allocation_le83.rs
index 3f84b48c29f..afb8da684c0 100644
--- a/profiling/src/allocation/allocation_le83.rs
+++ b/profiling/src/allocation/allocation_le83.rs
@@ -203,10 +203,10 @@ pub fn alloc_prof_rinit() {
 }
 
 pub fn alloc_prof_rshutdown() {
-    // If `is_zend_mm()` is true, the custom handlers have been reset to `None`
-    // already. This is unexpected, therefore we will not touch the ZendMM
+    // If `is_zend_mm()` is true, the custom handlers have already been reset
+    // to `None`. This is unexpected, therefore we will not touch the ZendMM
     // handlers anymore as resetting to prev handlers might result in segfaults
-    // and other undefined behaviour.
+    // and other undefined behavior.
     if is_zend_mm() {
         return;
     }
@@ -281,14 +281,17 @@ pub fn alloc_prof_startup() {
     }
 }
 
-/// Overrides the ZendMM heap's `use_custom_heap` flag with the default `ZEND_MM_CUSTOM_HEAP_NONE`
-/// (currently a `u32: 0`). This needs to be done, as the `zend_mm_gc()` and `zend_mm_shutdown()`
-/// functions alter behaviour in case custom handlers are installed.
+/// Overrides the ZendMM heap's `use_custom_heap` flag with the default
+/// `ZEND_MM_CUSTOM_HEAP_NONE` (currently a `u32: 0`). This needs to be done,
+/// as the `zend_mm_gc()` and `zend_mm_shutdown()` functions alter behavior
+/// in case custom handlers are installed.
+///
 /// - `zend_mm_gc()` will not do anything anymore.
-/// - `zend_mm_shutdown()` wont cleanup chunks anymore, leading to memory leaks
-/// The `_zend_mm_heap`-struct itself is private, but we are lucky, as the `use_custom_heap` flag
-/// is the first element and thus the first 4 bytes.
-/// Take care and call `restore_zend_heap()` afterwards!
+/// - `zend_mm_shutdown()` won't clean up chunks anymore (leaks memory)
+///
+/// The `_zend_mm_heap`-struct itself is private, but we are lucky, as the
+/// `use_custom_heap` flag is the first element and thus the first 4 bytes.
+/// Take care and call `restore_zend_heap()` afterward!
 unsafe fn prepare_zend_heap(heap: *mut zend::_zend_mm_heap) -> c_int {
     let custom_heap: c_int = ptr::read(heap as *const c_int);
     ptr::write(heap as *mut c_int, zend::ZEND_MM_CUSTOM_HEAP_NONE as c_int);
@@ -374,7 +377,7 @@ unsafe fn alloc_prof_orig_alloc(len: size_t) -> *mut c_void {
 
 /// This function exists because when calling `zend_mm_set_custom_handlers()`,
 /// you need to pass a pointer to a `free()` function as well, otherwise your
-/// custom handlers won't be installed. We can not just point to the original
+/// custom handlers won't be installed. We cannot just point to the original
 /// `zend::_zend_mm_free()` as the function definitions differ.
 unsafe extern "C" fn alloc_prof_free(ptr: *mut c_void) {
     tls_zend_mm_state_get!(free)(ptr);
@@ -401,7 +404,7 @@ unsafe extern "C" fn alloc_prof_realloc(prev_ptr: *mut c_void, len: size_t) -> *
 
     // during startup, minit, rinit, ... current_execute_data is null
     // we are only interested in allocations during userland operations
-    if zend::ddog_php_prof_get_current_execute_data().is_null() || ptr == prev_ptr {
+    if zend::ddog_php_prof_get_current_execute_data().is_null() || ptr::eq(ptr, prev_ptr) {
         return ptr;
     }
 
diff --git a/profiling/src/bindings/mod.rs b/profiling/src/bindings/mod.rs
index a86eafcab1d..a9e93ca5503 100644
--- a/profiling/src/bindings/mod.rs
+++ b/profiling/src/bindings/mod.rs
@@ -165,7 +165,7 @@ pub struct ModuleEntry {
     pub zts: c_uchar,
     pub ini_entry: *const _zend_ini_entry,
     pub deps: *const ModuleDep,
-    pub name: *const u8,
+    pub name: *const c_char,
     pub functions: *const _zend_function_entry,
     pub module_startup_func:
         Option<unsafe extern "C" fn(type_: c_int, module_number: c_int) -> ZendResult>,
@@ -209,11 +209,11 @@ pub use ModuleEntry as zend_module_entry;
 
 #[repr(C)]
 pub struct ZendExtension {
-    pub name: *const u8,
-    pub version: *const u8,
-    pub author: *const u8,
-    pub url: *const u8,
-    pub copyright: *const u8,
+    pub name: *const c_char,
+    pub version: *const c_char,
+    pub author: *const c_char,
+    pub url: *const c_char,
+    pub copyright: *const c_char,
     pub startup: Option<unsafe extern "C" fn(extension: *mut ZendExtension) -> ZendResult>,
     pub shutdown: shutdown_func_t,
     pub activate: activate_func_t,
@@ -248,7 +248,7 @@ impl Default for ModuleEntry {
             zts: USING_ZTS as u8,
             ini_entry: ptr::null(),
             deps: ptr::null(),
-            name: b"\0".as_ptr(),
+            name: c"".as_ptr(),
             functions: ptr::null(),
             module_startup_func: None,
             module_shutdown_func: None,
@@ -278,11 +278,11 @@ impl Default for ModuleEntry {
 impl Default for ZendExtension {
     fn default() -> Self {
         Self {
-            name: b"\0".as_ptr(),
-            version: b"\0".as_ptr(),
-            author: b"\0".as_ptr(),
-            url: b"\0".as_ptr(),
-            copyright: b"\0".as_ptr(),
+            name: c"".as_ptr(),
+            version: c"".as_ptr(),
+            author: c"".as_ptr(),
+            url: c"".as_ptr(),
+            copyright: c"".as_ptr(),
             startup: None,
             shutdown: None,
             activate: None,
diff --git a/profiling/src/config.rs b/profiling/src/config.rs
index f43882417da..efe9933f20a 100644
--- a/profiling/src/config.rs
+++ b/profiling/src/config.rs
@@ -88,7 +88,9 @@ impl SystemSettings {
     /// # Safety
     /// Must be called after [first_rinit] and before [shutdown].
     pub unsafe fn get() -> ptr::NonNull<SystemSettings> {
-        ptr::NonNull::from(SYSTEM_SETTINGS.assume_init_ref())
+        // SAFETY: required by this function's own safety requirements.
+        let addr = unsafe { (*ptr::addr_of_mut!(SYSTEM_SETTINGS)).assume_init_mut() };
+        ptr::NonNull::from(addr)
     }
 
     /// # Safety
@@ -116,21 +118,24 @@ impl SystemSettings {
         if allocation::allocation_ge84::first_rinit_should_disable_due_to_jit() {
             system_settings.profiling_allocation_enabled = false;
         }
-        swap(&mut system_settings, SYSTEM_SETTINGS.assume_init_mut());
+        swap(
+            &mut system_settings,
+            (*ptr::addr_of_mut!(SYSTEM_SETTINGS)).assume_init_mut(),
+        );
     }
 
     /// # Safety
     /// Must be called exactly once each startup in either minit or startup,
     /// whether profiling is enabled or not.
     unsafe fn on_startup() {
-        SYSTEM_SETTINGS.write(INITIAL_SYSTEM_SETTINGS.clone());
+        (*ptr::addr_of_mut!(SYSTEM_SETTINGS)).write(INITIAL_SYSTEM_SETTINGS.clone());
     }
 
     /// # Safety
     /// Must be called exactly once per shutdown in either mshutdown or
     /// shutdown, before zai config is shutdown.
     unsafe fn on_shutdown() {
-        let system_settings = SYSTEM_SETTINGS.assume_init_mut();
+        let system_settings = (*ptr::addr_of_mut!(SYSTEM_SETTINGS)).assume_init_mut();
         *system_settings = SystemSettings {
             profiling_enabled: false,
             profiling_experimental_features_enabled: false,
@@ -150,7 +155,7 @@ impl SystemSettings {
     }
 
     unsafe fn on_fork_in_child() {
-        let system_settings = SYSTEM_SETTINGS.assume_init_mut();
+        let system_settings = (*ptr::addr_of_mut!(SYSTEM_SETTINGS)).assume_init_mut();
         system_settings.profiling_enabled = false;
         system_settings.profiling_experimental_features_enabled = false;
         system_settings.profiling_endpoint_collection_enabled = false;
@@ -207,7 +212,7 @@ impl TryFrom<&AgentEndpoint> for ddcommon::Endpoint {
 impl Display for AgentEndpoint {
     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
         match self {
-            AgentEndpoint::Uri(uri) => write!(f, "{}", uri),
+            AgentEndpoint::Uri(uri) => write!(f, "{uri}"),
             AgentEndpoint::Socket(path) => write!(f, "unix://{}", path.to_string_lossy()),
         }
     }
@@ -317,11 +322,12 @@ unsafe extern "C" fn env_to_ini_name(env_name: ZaiStr, ini_name: *mut zai_config
     let dest_suffix = &mut ini_name.ptr[dest_prefix.len()..];
     let src_suffix = &name[src_prefix.len()..];
     for (dest, src) in dest_suffix.iter_mut().zip(src_suffix.bytes()) {
-        *dest = transmute::<u8, c_char>(src.to_ascii_lowercase());
+        // Casting between same-sized integers is a no-op.
+        *dest = src.to_ascii_lowercase() as c_char;
     }
 
     // Add the null terminator.
-    dest_suffix[src_suffix.len()] = transmute::<u8, c_char>(b'\0');
+    dest_suffix[src_suffix.len()] = b'\0' as c_char;
 
     // Store the length without the null.
     ini_name.len = dest_prefix.len() + src_suffix.len();
@@ -1132,9 +1138,10 @@ pub(crate) fn minit(module_number: libc::c_int) {
             ]
         };
 
+        let entries = &mut *ptr::addr_of_mut!(ENTRIES);
         let tmp = zai_config_minit(
-            ENTRIES.as_mut_ptr(),
-            ENTRIES.len(),
+            entries.as_mut_ptr(),
+            entries.len(),
             Some(env_to_ini_name),
             module_number,
         );
diff --git a/profiling/src/lib.rs b/profiling/src/lib.rs
index 6ab33fd9476..32b3be589f9 100644
--- a/profiling/src/lib.rs
+++ b/profiling/src/lib.rs
@@ -32,20 +32,17 @@ use bindings::{
     ZendResult,
 };
 use clocks::*;
+use core::ffi::{c_char, c_int, c_void, CStr};
 use core::ptr;
 use ddcommon::{cstr, tag, tag::Tag};
 use lazy_static::lazy_static;
-use libc::c_char;
-use libc::c_void;
 use log::{debug, error, info, trace, warn};
 use once_cell::sync::{Lazy, OnceCell};
 use profiling::{LocalRootSpanResourceMessage, Profiler, VmInterrupt};
 use sapi::Sapi;
 use std::borrow::Cow;
 use std::cell::RefCell;
-use std::ffi::CStr;
 use std::ops::Deref;
-use std::os::raw::c_int;
 use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
 use std::sync::{Arc, Once};
 use std::time::{Duration, Instant};
@@ -53,11 +50,7 @@ use uuid::Uuid;
 
 /// Name of the profiling module and zend_extension. Must not contain any
 /// interior null bytes and must be null terminated.
-static PROFILER_NAME: &[u8] = b"datadog-profiling\0";
-
-/// Name of the profiling module and zend_extension, but as a &CStr.
-// Safety: null terminated, contains no interior null bytes.
-static PROFILER_NAME_CSTR: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(PROFILER_NAME) };
+static PROFILER_NAME: &CStr = c"datadog-profiling";
 
 /// Version of the profiling module and zend_extension. Must not contain any
 /// interior null bytes and must be null terminated.
@@ -86,7 +79,7 @@ lazy_static! {
         vec![
             tag!("language", "php"),
             tag!("profiler_version", env!("PROFILER_VERSION")),
-            // Safety: calling getpid() is safe.
+            // SAFETY: calling getpid() is safe.
             Tag::new("process_id", unsafe { libc::getpid() }.to_string())
                 .expect("process_id tag to be valid"),
             Tag::new("runtime-id", runtime_id().to_string()).expect("runtime-id tag to be valid"),
@@ -97,14 +90,14 @@ lazy_static! {
     static ref SAPI: Sapi = {
         #[cfg(not(test))]
         {
-            // Safety: sapi_module is initialized before minit and there should be
+            // SAFETY: sapi_module is initialized before minit and there should be
             // no concurrent threads.
             let sapi_module = unsafe { zend::sapi_module };
             if sapi_module.name.is_null() {
                 panic!("the sapi_module's name is a null pointer");
             }
 
-            // Safety: value has been checked for NULL; I haven't checked that the
+            // SAFETY: value has been checked for NULL; I haven't checked that the
             // engine ensures its length is less than `isize::MAX`, but it is a
             // risk I'm willing to take.
             let sapi_name = unsafe { CStr::from_ptr(sapi_module.name) };
@@ -118,11 +111,11 @@ lazy_static! {
         }
     };
 
-    // Safety: PROFILER_NAME is a byte slice that satisfies the safety requirements.
+    // SAFETY: PROFILER_NAME is a byte slice that satisfies the safety requirements.
     // Panic: we own this string and it should be UTF8 (see PROFILER_NAME above).
-    static ref PROFILER_NAME_STR: &'static str = PROFILER_NAME_CSTR.to_str().unwrap();
+    static ref PROFILER_NAME_STR: &'static str = PROFILER_NAME.to_str().unwrap();
 
-    // Safety: PROFILER_VERSION is a byte slice that satisfies the safety requirements.
+    // SAFETY: PROFILER_VERSION is a byte slice that satisfies the safety requirements.
     static ref PROFILER_VERSION_STR: &'static str = unsafe { CStr::from_ptr(PROFILER_VERSION.as_ptr() as *const c_char) }
         .to_str()
         // Panic: we own this string and it should be UTF8 (see PROFILER_VERSION above).
@@ -168,15 +161,14 @@ pub extern "C" fn get_module() -> &'static mut zend::ModuleEntry {
         zend::ModuleDep::end(),
     ];
 
-    /* In PHP modules written in C, this just returns the address of a global,
-     * mutable variable. In Rust, you cannot initialize such a complicated
-     * global variable because of initialization order issues that have been
-     * found through decades of C++ experience.
-     * There are a variety of ways to deal with this; this is just one way.
-     */
+    // In PHP modules written in C, this just returns the address of a global,
+    // mutable variable. In Rust, you cannot initialize such a complicated
+    // global variable because of initialization order issues that have been
+    // found through decades of C++ experience.
+    // There are a variety of ways to deal with this; this is just one way.
     static mut MODULE: Lazy<zend::ModuleEntry> = Lazy::new(|| zend::ModuleEntry {
         name: PROFILER_NAME.as_ptr(),
-        // Safety: php_ffi.c defines this correctly
+        // SAFETY: php_ffi.c defines this correctly
         functions: unsafe { bindings::ddog_php_prof_functions },
         module_startup_func: Some(minit),
         module_shutdown_func: Some(mshutdown),
@@ -216,26 +208,24 @@ unsafe extern "C" fn gshutdown(_globals_ptr: *mut c_void) {
     allocation::alloc_prof_gshutdown();
 }
 
-/* Important note on the PHP lifecycle:
- * Based on how some SAPIs work and the documentation, one might expect that
- * MINIT is called once per process, but this is only sort-of true. Some SAPIs
- * will call MINIT once and then fork for additional processes.
- * This means you cannot do certain things in MINIT and have them work across
- * all SAPIs, like spawn threads.
- *
- * Additionally, when Apache does a reload it will go through the shutdown
- * routines and then in the same process do the startup routines, so MINIT can
- * actually be called more than once per process as well. This means some
- * mechanisms like std::sync::Once::call_once may not be suitable.
- * Be careful out there!
- */
+// Important note on the PHP lifecycle:
+// Based on how some SAPIs work and the documentation, one might expect that
+// MINIT is called once per process, but this is only sort-of true. Some SAPIs
+// will call MINIT once and then fork for additional processes.
+// This means you cannot do certain things in MINIT and have them work across
+// all SAPIs, like spawn threads.
+//
+// Additionally, when Apache does a reload it will go through the shutdown
+// routines and then in the same process do the startup routines, so MINIT can
+// actually be called more than once per process as well. This means some
+// mechanisms like std::sync::Once::call_once may not be suitable.
+// Be careful out there!
 extern "C" fn minit(_type: c_int, module_number: c_int) -> ZendResult {
     // todo: merge these lifecycle things to tracing feature?
-    /* When developing the extension, it's useful to see log messages that
-     * occur before the user can configure the log level. However, if we
-     * initialized the logger here unconditionally then they'd have no way to
-     * hide these messages. That's why it's done only for debug builds.
-     */
+    // When developing the extension, it's useful to see log messages that
+    // occur before the user can configure the log level. However, if we
+    // initialized the logger here unconditionally, then they'd have no way to
+    // hide these messages. That's why it's done only for debug builds.
     #[cfg(debug_assertions)]
     {
         logging::log_init(log::LevelFilter::Trace);
@@ -274,18 +264,17 @@ extern "C" fn minit(_type: c_int, module_number: c_int) -> ZendResult {
 
     #[cfg(target_vendor = "apple")]
     {
-        /* If PHP forks and certain ObjC classes are not initialized before the
-         * fork, then on High Sierra and above the child process will crash,
-         * for example:
-         * > objc[25938]: +[__NSCFConstantString initialize] may have been in
-         * > progress in another thread when fork() was called. We cannot
-         * > safely call it or ignore it in the fork() child process. Crashing
-         * > instead. Set a breakpoint on objc_initializeAfterForkError to
-         * > debug.
-         * In our case, it's things related to TLS that fail, so when we
-         * support forking, load this at the beginning:
-         * let _ = ddcommon::connector::load_root_certs();
-         */
+        // If PHP forks and certain ObjC classes are not initialized before the
+        // fork, then on High Sierra and above the child process will crash,
+        // for example:
+        // > objc[25938]: +[__NSCFConstantString initialize] may have been in
+        // > progress in another thread when fork() was called. We cannot
+        // > safely call it or ignore it in the fork() child process. Crashing
+        // > instead. Set a breakpoint on objc_initializeAfterForkError to
+        // > debug.
+        // In our case, it's things related to TLS that fail, so when we
+        // support forking, load this at the beginning:
+        // let _ = ddcommon::connector::load_root_certs();
     }
 
     // Update the runtime PHP_VERSION and PHP_VERSION_ID.
@@ -308,38 +297,32 @@ extern "C" fn minit(_type: c_int, module_number: c_int) -> ZendResult {
 
     config::minit(module_number);
 
-    /* Use a hybrid extension hack to load as a module but have the
-     * zend_extension hooks available:
-     * https://www.phpinternalsbook.com/php7/extensions_design/zend_extensions.html#hybrid-extensions
-     * In this case, use the same technique as the tracer: transfer the module
-     * handle to the zend_extension as extensions have longer lifetimes than
-     * modules in the engine.
-     */
+    // Use a hybrid extension hack to load as a module but have the
+    // zend_extension hooks available:
+    // https://www.phpinternalsbook.com/php7/extensions_design/zend_extensions.html#hybrid-extensions
+    // In this case, use the same technique as the tracer: transfer the module
+    // handle to the zend_extension as extensions have longer lifetimes than
+    // modules in the engine.
     let handle = {
-        /* The engine copies the module entry we provide it, so we have to
-         * lookup the module entry in the registry and modify it there
-         * instead of just modifying the result of get_module().
-         *
-         * I modified the engine for PHP 8.2 to stop copying the module:
-         * https://github.com/php/php-src/pull/8551
-         * At the time of this writing, PHP 8.2 isn't out yet so it's possible
-         * it may get reverted if issues are found.
-         */
-        let str = PROFILER_NAME_CSTR.as_ptr();
-        let len = PROFILER_NAME.len() - 1; // ignore trailing null byte
-
-        // Safety: str is valid for at least len values.
+        // Levi modified the engine for PHP 8.2 to stop copying the module:
+        // https://github.com/php/php-src/pull/8551
+        // Before then, the engine copied the module entry we provided. We
+        // find the module entry in the registry and modify it there instead
+        // of just modifying the result of get_module().
+        let str = PROFILER_NAME.as_ptr();
+        let len = PROFILER_NAME_STR.len();
+
+        // SAFETY: str is valid for at least len values.
         let ptr = unsafe { zend::datadog_get_module_entry(str, len) };
         if ptr.is_null() {
             error!("Unable to locate our own module in the engine registry.");
             return ZendResult::Failure;
         }
 
-        /* Safety: `ptr` was checked for nullability already. Transferring the
-         * handle from the module to the extension extends the lifetime, not
-         * shortens it, so it's safe. But of course, be sure the code below
-         * actually passes it to the extension.
-         */
+        // SAFETY: `ptr` was checked for nullability already. Transferring the
+        // handle from the module to the extension extends the lifetime, not
+        // shortens it, so it's safe. But of course, be sure the code below
+        // actually passes it to the extension.
         unsafe {
             let module = &mut *ptr;
             let handle = module.handle;
@@ -348,29 +331,27 @@ extern "C" fn minit(_type: c_int, module_number: c_int) -> ZendResult {
         }
     };
 
-    /* Currently, the engine is always copying this struct. Every time a new
-     * PHP version is released, we should double check zend_register_extension
-     * to ensure the address is not mutated nor stored. Well, hopefully we
-     * catch it _before_ a release.
-     */
+    // Currently, the engine is always copying this struct into a
+    // zend_llist_element. Every time a new PHP version is released, we should
+    // double-check zend_register_extension to ensure the address is not
+    // mutated nor stored. Well, hopefully we catch it _before_ a release.
     let extension = ZendExtension {
         name: PROFILER_NAME.as_ptr(),
-        version: PROFILER_VERSION.as_ptr(),
-        author: b"Datadog\0".as_ptr(),
-        url: b"https://github.com/DataDog\0".as_ptr(),
-        copyright: b"Copyright Datadog\0".as_ptr(),
+        version: PROFILER_VERSION.as_ptr().cast::<c_char>(),
+        author: c"Datadog".as_ptr(),
+        url: c"https://github.com/DataDog/dd-trace-php".as_ptr(),
+        copyright: c"Copyright Datadog".as_ptr(),
         startup: Some(startup),
         shutdown: Some(shutdown),
         activate: Some(activate),
         ..Default::default()
     };
 
-    // Safety: during minit there shouldn't be any threads to race against these writes.
+    // SAFETY: during minit there shouldn't be any threads to race against these writes.
     unsafe { wall_time::minit() };
 
-    /* Safety: all arguments are valid for this C call.
-     * Note that on PHP 7 this never fails, and on PHP 8 it returns void.
-     */
+    // SAFETY: all arguments are valid for this C call.
+    // Note that on PHP 7 this never fails, and on PHP 8 it returns void.
     unsafe { zend::zend_register_extension(&extension, handle) };
 
     #[cfg(feature = "timeline")]
@@ -397,9 +378,8 @@ extern "C" fn prshutdown() -> ZendResult {
     #[cfg(debug_assertions)]
     trace!("PRSHUTDOWN");
 
-    /* ZAI config may be accessed indirectly via other modules RSHUTDOWN, so
-     * delay this until the last possible time.
-     */
+    // ZAI config may be accessed indirectly via other modules RSHUTDOWN, so
+    // delay this until the last possible time.
     unsafe { bindings::zai_config_rshutdown() };
 
     #[cfg(feature = "timeline")]
@@ -472,7 +452,7 @@ fn runtime_id() -> &'static Uuid {
 }
 
 extern "C" fn activate() {
-    // Safety: calling in activate as required.
+    // SAFETY: calling in activate as required.
     unsafe { profiling::stack_walking::activate() };
 }
 
@@ -488,9 +468,8 @@ thread_local! {
     };
 }
 
-/* If Failure is returned the VM will do a C exit; try hard to avoid that,
- * using it for catastrophic errors only.
- */
+// If Failure is returned, the VM will do a C exit. Try hard to avoid that,
+// using it for catastrophic errors only.
 extern "C" fn rinit(_type: c_int, _module_number: c_int) -> ZendResult {
     #[cfg(feature = "tracing")]
     REQUEST_SPAN.set(Some(tracing::info_span!("request").entered()));
@@ -516,17 +495,17 @@ extern "C" fn rinit(_type: c_int, _module_number: c_int) -> ZendResult {
     // initialize the thread local storage and cache some items
     REQUEST_LOCALS.with(|cell| {
         let mut locals = cell.borrow_mut();
-        // Safety: we are in rinit on a PHP thread.
+        // SAFETY: we are in rinit on a PHP thread.
         locals.vm_interrupt_addr = unsafe { zend::datadog_php_profiling_vm_interrupt_addr() };
         locals.interrupt_count.store(0, Ordering::SeqCst);
 
-        // Safety: We are after first rinit and before mshutdown.
+        // SAFETY: We are after first rinit and before mshutdown.
         unsafe {
             locals.env = config::env();
             locals.service = config::service().or_else(|| {
                 match *SAPI {
                     Sapi::Cli => {
-                        // Safety: sapi globals are safe to access during rinit
+                        // SAFETY: sapi globals are safe to access during rinit
                         SAPI.request_script_name(datadog_sapi_globals_request_info())
                             .map(Cow::into_owned)
                             .or(Some(String::from("cli.command")))
@@ -572,16 +551,15 @@ extern "C" fn rinit(_type: c_int, _module_number: c_int) -> ZendResult {
     let once = unsafe { &*ptr::addr_of!(RINIT_ONCE) };
     once.call_once(|| {
         if system_settings.profiling_enabled {
-            /* Safety: sapi_module is initialized by rinit and shouldn't be
-             * modified at this point (safe to read values).
-             */
+            // SAFETY: sapi_module is initialized by rinit and shouldn't be
+            // modified at this point (safe to read values).
             let sapi_module = unsafe { &*ptr::addr_of!(zend::sapi_module) };
             if sapi_module.pretty_name.is_null() {
-                // Safety: I'm willing to bet the module name is less than `isize::MAX`.
+                // SAFETY: I'm willing to bet the module name is less than `isize::MAX`.
                 let name = unsafe { CStr::from_ptr(sapi_module.name) }.to_string_lossy();
                 warn!("The SAPI module {name}'s pretty name was not set!")
             } else {
-                // Safety: I'm willing to bet the module pretty name is less than `isize::MAX`.
+                // SAFETY: I'm willing to bet the module pretty name is less than `isize::MAX`.
                 let pretty_name =
                     unsafe { CStr::from_ptr(sapi_module.pretty_name) }.to_string_lossy();
                 if *SAPI != Sapi::Unknown {
@@ -743,21 +721,21 @@ unsafe extern "C" fn minfo(module_ptr: *mut zend::ModuleEntry) {
     REQUEST_LOCALS.with(|cell| {
         let locals = cell.borrow();
         let system_settings = locals.system_settings();
-        let yes: &[u8] = b"true\0";
-        let yes_exp: &[u8] = b"true (all experimental features enabled)\0";
-        let no: &[u8] = b"false\0";
-        let no_all: &[u8] = b"false (profiling disabled)\0";
+        let yes = c"true".as_ptr();
+        let yes_exp = c"true (all experimental features enabled)".as_ptr();
+        let no = c"false".as_ptr();
+        let no_all = c"false (profiling disabled)".as_ptr();
         zend::php_info_print_table_start();
-        zend::php_info_print_table_row(2, b"Version\0".as_ptr(), module.version);
+        zend::php_info_print_table_row(2, c"Version".as_ptr(), module.version);
         zend::php_info_print_table_row(
             2,
-            b"Profiling Enabled\0".as_ptr(),
+            c"Profiling Enabled".as_ptr(),
             if system_settings.profiling_enabled { yes } else { no },
         );
 
         zend::php_info_print_table_row(
             2,
-            b"Profiling Experimental Features Enabled\0".as_ptr(),
+            c"Profiling Experimental Features Enabled".as_ptr(),
             if system_settings.profiling_experimental_features_enabled {
                 yes
             } else if system_settings.profiling_enabled {
@@ -769,7 +747,7 @@ unsafe extern "C" fn minfo(module_ptr: *mut zend::ModuleEntry) {
 
         zend::php_info_print_table_row(
             2,
-            b"Experimental CPU Time Profiling Enabled\0".as_ptr(),
+            c"Experimental CPU Time Profiling Enabled".as_ptr(),
             if system_settings.profiling_experimental_cpu_time_enabled {
                 if system_settings.profiling_experimental_features_enabled {
                     yes_exp
@@ -787,15 +765,15 @@ unsafe extern "C" fn minfo(module_ptr: *mut zend::ModuleEntry) {
             if #[cfg(feature = "allocation_profiling")] {
                 zend::php_info_print_table_row(
                     2,
-                    b"Allocation Profiling Enabled\0".as_ptr(),
+                    c"Allocation Profiling Enabled".as_ptr(),
                     if system_settings.profiling_allocation_enabled {
                         yes
                     } else if zend::ddog_php_jit_enabled() {
                         // Work around version-specific issues.
                         if cfg!(not(php_zend_mm_set_custom_handlers_ex)) {
-                            b"Not available due to JIT being active, see https://github.com/DataDog/dd-trace-php/pull/2088 for more information.\0"
+                            c"Not available due to JIT being active, see https://github.com/DataDog/dd-trace-php/pull/2088 for more information.".as_ptr()
                         } else {
-                            b"Not available due to JIT being active, see https://github.com/DataDog/dd-trace-php/pull/3199 for more information.\0"
+                            c"Not available due to JIT being active, see https://github.com/DataDog/dd-trace-php/pull/3199 for more information.".as_ptr()
                         }
                     } else if system_settings.profiling_enabled {
                         no
@@ -816,7 +794,7 @@ unsafe extern "C" fn minfo(module_ptr: *mut zend::ModuleEntry) {
             if #[cfg(feature = "timeline")] {
                 zend::php_info_print_table_row(
                     2,
-                    b"Timeline Enabled\0".as_ptr(),
+                    c"Timeline Enabled".as_ptr(),
                     if system_settings.profiling_timeline_enabled {
                         yes
                     } else if system_settings.profiling_enabled {
@@ -828,8 +806,8 @@ unsafe extern "C" fn minfo(module_ptr: *mut zend::ModuleEntry) {
             } else {
                 zend::php_info_print_table_row(
                     2,
-                    b"Timeline Enabled\0".as_ptr(),
-                    b"Not available. The profiler was build without timeline support.\0"
+                    c"Timeline Enabled".as_ptr(),
+                    c"Not available. The profiler was build without timeline support.".as_ptr()
                 );
             }
         }
@@ -838,7 +816,7 @@ unsafe extern "C" fn minfo(module_ptr: *mut zend::ModuleEntry) {
             if #[cfg(feature = "exception_profiling")] {
                 zend::php_info_print_table_row(
                     2,
-                    b"Exception Profiling Enabled\0".as_ptr(),
+                    c"Exception Profiling Enabled".as_ptr(),
                     if system_settings.profiling_exception_enabled {
                         yes
                     } else if system_settings.profiling_enabled {
@@ -850,8 +828,8 @@ unsafe extern "C" fn minfo(module_ptr: *mut zend::ModuleEntry) {
             } else {
                 zend::php_info_print_table_row(
                     2,
-                    b"Exception Profiling Enabled\0".as_ptr(),
-                    b"Not available. The profiler was built without exception profiling support.\0"
+                    c"Exception Profiling Enabled".as_ptr(),
+                    c"Not available. The profiler was built without exception profiling support.".as_ptr()
                 );
             }
         }
@@ -860,7 +838,7 @@ unsafe extern "C" fn minfo(module_ptr: *mut zend::ModuleEntry) {
             if #[cfg(feature = "io_profiling")] {
                 zend::php_info_print_table_row(
                     2,
-                    b"I/O Profiling Enabled\0".as_ptr(),
+                    c"I/O Profiling Enabled".as_ptr(),
                     if system_settings.profiling_io_enabled {
                         yes
                     } else if system_settings.profiling_enabled {
@@ -872,15 +850,15 @@ unsafe extern "C" fn minfo(module_ptr: *mut zend::ModuleEntry) {
             } else {
                 zend::php_info_print_table_row(
                     2,
-                    b"I/O Profiling Enabled\0".as_ptr(),
-                    b"Not available. The profiler was built without I/O profiling support.\0"
+                    c"I/O Profiling Enabled".as_ptr(),
+                    c"Not available. The profiler was built without I/O profiling support.".as_ptr()
                 );
             }
         }
 
         zend::php_info_print_table_row(
             2,
-            b"Endpoint Collection Enabled\0".as_ptr(),
+            c"Endpoint Collection Enabled".as_ptr(),
             if system_settings.profiling_endpoint_collection_enabled {
                 yes
             } else if system_settings.profiling_enabled {
@@ -892,7 +870,7 @@ unsafe extern "C" fn minfo(module_ptr: *mut zend::ModuleEntry) {
 
         zend::php_info_print_table_row(
             2,
-            b"Platform's CPU Time API Works\0".as_ptr(),
+            c"Platform's CPU Time API Works".as_ptr(),
             if cpu_time::ThreadTime::try_now().is_ok() {
                 yes
             } else {
@@ -910,18 +888,18 @@ unsafe extern "C" fn minfo(module_ptr: *mut zend::ModuleEntry) {
 
         zend::php_info_print_table_row(
             2,
-            b"Profiling Log Level\0".as_ptr(),
-            printable_log_level.as_ptr()
+            c"Profiling Log Level".as_ptr(),
+            printable_log_level.as_ptr().cast::<c_char>()
         );
 
-        let key = b"Profiling Agent Endpoint\0".as_ptr();
+        let key = c"Profiling Agent Endpoint".as_ptr();
         let agent_endpoint = format!("{}\0", system_settings.uri);
         zend::php_info_print_table_row(2, key, agent_endpoint.as_ptr());
 
         let vars = [
-            (b"Application's Environment (DD_ENV)\0", &locals.env),
-            (b"Application's Service (DD_SERVICE)\0", &locals.service),
-            (b"Application's Version (DD_VERSION)\0", &locals.version),
+            (c"Application's Environment (DD_ENV)".as_ptr(), &locals.env),
+            (c"Application's Service (DD_SERVICE)".as_ptr(), &locals.service),
+            (c"Application's Version (DD_VERSION)".as_ptr(), &locals.version),
         ];
 
         for (key, value) in vars {
@@ -930,7 +908,7 @@ unsafe extern "C" fn minfo(module_ptr: *mut zend::ModuleEntry) {
                 None => String::new(),
             };
             value.push('\0');
-            zend::php_info_print_table_row(2, key, value.as_ptr());
+            zend::php_info_print_table_row(2, key, value.as_ptr().cast::<c_char>());
         }
 
         zend::php_info_print_table_end();
@@ -951,7 +929,8 @@ extern "C" fn mshutdown(_type: c_int, _module_number: c_int) -> ZendResult {
     #[cfg(feature = "exception_profiling")]
     exception::exception_profiling_mshutdown();
 
-    Profiler::stop(Duration::from_secs(1));
+    // SAFETY: calling in mshutdown as required.
+    unsafe { Profiler::stop(Duration::from_secs(1)) };
 
     ZendResult::Success
 }
@@ -961,16 +940,16 @@ extern "C" fn startup(extension: *mut ZendExtension) -> ZendResult {
     #[cfg(debug_assertions)]
     trace!("startup({:p})", extension);
 
-    // Safety: called during startup hook with correct params.
+    // SAFETY: called during startup hook with correct params.
     unsafe { zend::datadog_php_profiling_startup(extension) };
 
     #[cfg(php_run_time_cache)]
-    // Safety: calling this in startup/minit as required.
+    // SAFETY: calling this in startup/minit as required.
     unsafe {
-        bindings::ddog_php_prof_function_run_time_cache_init(PROFILER_NAME_CSTR.as_ptr())
+        bindings::ddog_php_prof_function_run_time_cache_init(PROFILER_NAME.as_ptr())
     };
 
-    // Safety: calling this in zend_extension startup.
+    // SAFETY: calling this in zend_extension startup.
     unsafe {
         pthread::startup();
         #[cfg(feature = "timeline")]
@@ -995,9 +974,10 @@ extern "C" fn shutdown(extension: *mut ZendExtension) {
     trace!("shutdown({:p})", extension);
 
     // If a timeout was reached, then the thread is possibly alive.
-    // This means the engine cannot unload our handle, or else we'd hit
-    // immediate undefined behavior (and likely crash).
-    if let Err(err) = Profiler::shutdown(Duration::from_secs(2)) {
+    // This means the engine cannot unload our handle, or else we'd
+    // immediately hit undefined behavior (and likely crash).
+    // SAFETY: calling in Zend Extension shutdown as required.
+    if let Err(err) = unsafe { Profiler::shutdown(Duration::from_secs(2)) } {
         let num_failures = err.num_failures;
         error!("{num_failures} thread(s) failed to join, intentionally leaking the extension's handle to prevent unloading");
         // SAFETY: during mshutdown, we have ownership of the extension struct.
diff --git a/profiling/src/profiling/mod.rs b/profiling/src/profiling/mod.rs
index cbfc6517636..2e0823d5bc9 100644
--- a/profiling/src/profiling/mod.rs
+++ b/profiling/src/profiling/mod.rs
@@ -19,6 +19,7 @@ use crate::bindings::{datadog_php_profiling_get_profiling_context, zend_execute_
 use crate::config::SystemSettings;
 use crate::{CLOCKS, TAGS};
 use chrono::Utc;
+use core::mem::forget;
 use crossbeam_channel::{Receiver, Sender, TrySendError};
 use datadog_profiling::api::{
     Function, Label as ApiLabel, Location, Period, Sample, ValueType as ApiValueType,
@@ -30,8 +31,6 @@ use once_cell::sync::OnceCell;
 use std::borrow::Cow;
 use std::collections::HashMap;
 use std::hash::Hash;
-use std::intrinsics::transmute;
-use std::mem::forget;
 use std::num::NonZeroI64;
 use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, Ordering};
 use std::sync::{Arc, Barrier};
@@ -678,14 +677,14 @@ impl Profiler {
         // SAFETY: the `get_or_init` access is a thread-safe API, and the
         // PROFILER is not being mutated outside single-threaded phases such
         // as minit/mshutdown.
-        unsafe { PROFILER.get_or_init(|| Profiler::new(system_settings)) };
+        unsafe { (*ptr::addr_of!(PROFILER)).get_or_init(|| Profiler::new(system_settings)) };
     }
 
     pub fn get() -> Option<&'static Profiler> {
         // SAFETY: the `get` access is a thread-safe API, and the PROFILER is
         // not being mutated outside single-threaded phases such as minit and
         // mshutdown.
-        unsafe { PROFILER.get() }
+        unsafe { (*ptr::addr_of!(PROFILER)).get() }
     }
 
     pub fn new(system_settings: &mut SystemSettings) -> Self {
@@ -801,11 +800,13 @@ impl Profiler {
     /// Note that you must call [Profiler::shutdown] afterwards; it's two
     /// parts of the same operation. It's split so you (or other extensions)
     /// can do something while the other threads finish up.
-    pub fn stop(timeout: Duration) {
-        // SAFETY: the `get_mut` access is a thread-safe API, and the PROFILER
-        // is not being mutated outside single-threaded phases such as minit
-        // and mshutdown.
-        if let Some(profiler) = unsafe { PROFILER.get_mut() } {
+    ///
+    /// # Safety
+    /// Must be called in mshutdown.
+    pub unsafe fn stop(timeout: Duration) {
+        // SAFETY: only called during mshutdown, where we have ownership of
+        // the PROFILER object.
+        if let Some(profiler) = unsafe { (*ptr::addr_of_mut!(PROFILER)).get_mut() } {
             profiler.join_and_drop_sender(timeout);
         }
     }
@@ -843,12 +844,12 @@ impl Profiler {
     /// Note the timeout is per thread, and there may be multiple threads.
     /// Returns Ok(true) if any thread hit a timeout.
     ///
-    /// Safety: only safe to be called in `SHUTDOWN`/`MSHUTDOWN` phase
-    pub fn shutdown(timeout: Duration) -> Result<(), JoinError> {
-        // SAFETY: the `take` access is a thread-safe API, and the PROFILER is
-        // not being mutated outside single-threaded phases such  as minit and
-        // mshutdown.
-        if let Some(profiler) = unsafe { PROFILER.take() } {
+    /// # Safety
+    /// Only safe to be called in Zend Extension shutdown.
+    pub unsafe fn shutdown(timeout: Duration) -> Result<(), JoinError> {
+        // SAFETY: only called during extension shutdown, where we have
+        // ownership of the PROFILER object.
+        if let Some(profiler) = unsafe { (*ptr::addr_of_mut!(PROFILER)).take() } {
             profiler.join_collector_and_uploader(timeout)
         } else {
             Ok(())
@@ -893,7 +894,7 @@ impl Profiler {
     /// includes this thread in some kind of recursive manner.
     pub unsafe fn kill() {
         // SAFETY: see this function's safety conditions.
-        if let Some(mut profiler) = PROFILER.take() {
+        if let Some(mut profiler) = unsafe { (*ptr::addr_of_mut!(PROFILER)).take() } {
             // Drop some things to reduce memory.
             profiler.interrupt_manager = Arc::new(InterruptManager::new());
             profiler.message_sender = crossbeam_channel::bounded(0).0;
@@ -920,9 +921,7 @@ impl Profiler {
                 let labels = Profiler::common_labels(0);
                 let n_labels = labels.len();
 
-                #[cfg(not(feature = "timeline"))]
-                let mut timestamp = NO_TIMESTAMP;
-                #[cfg(feature = "timeline")]
+                #[cfg_attr(not(feature = "timeline"), allow(unused_mut))]
                 let mut timestamp = NO_TIMESTAMP;
                 #[cfg(feature = "timeline")]
                 {
@@ -1029,9 +1028,7 @@ impl Profiler {
 
                 let n_labels = labels.len();
 
-                #[cfg(not(feature = "timeline"))]
-                let timestamp = NO_TIMESTAMP;
-                #[cfg(feature = "timeline")]
+                #[cfg_attr(not(feature = "timeline"), allow(unused_mut))]
                 let mut timestamp = NO_TIMESTAMP;
                 #[cfg(feature = "timeline")]
                 {
@@ -1462,7 +1459,7 @@ impl Profiler {
     /// Creates the common message labels for all samples.
     ///
     /// * `n_extra_labels` - Reserve room for extra labels, such as when the
-    ///                      caller adds gc or exception labels.
+    ///   caller adds gc or exception labels.
     fn common_labels(n_extra_labels: usize) -> Vec<Label> {
         let mut labels = Vec::with_capacity(5 + n_extra_labels);
         labels.push(Label {
@@ -1479,12 +1476,10 @@ impl Profiler {
         // we're getting the profiling context on a PHP thread.
         let context = unsafe { datadog_php_profiling_get_profiling_context.unwrap_unchecked()() };
         if context.local_root_span_id != 0 {
-            /* Safety: PProf only has signed integers for label.num.
-             * We bit-cast u64 to i64, and the backend does the
-             * reverse so the conversion is lossless.
-             */
-            let local_root_span_id: i64 = unsafe { transmute(context.local_root_span_id) };
-            let span_id: i64 = unsafe { transmute(context.span_id) };
+            // Casting between two integers of the same size is a no-op, and
+            // Rust uses 2's complement for negative numbers.
+            let local_root_span_id = context.local_root_span_id as i64;
+            let span_id = context.span_id as i64;
 
             labels.push(Label {
                 key: "local root span id",
diff --git a/profiling/src/profiling/stack_walking.rs b/profiling/src/profiling/stack_walking.rs
index 29f2b14cd1a..c99be35e49b 100644
--- a/profiling/src/profiling/stack_walking.rs
+++ b/profiling/src/profiling/stack_walking.rs
@@ -461,6 +461,8 @@ mod detail {
 
 pub use detail::*;
 
+// todo: this should be feature = "stack_walking_tests" but it seemed to
+//       cause a failure in CI to migrate it.
 #[cfg(all(test, stack_walking_tests))]
 mod tests {
     use super::*;
diff --git a/profiling/src/wall_time.rs b/profiling/src/wall_time.rs
index ea4d238734d..a0498aa7378 100644
--- a/profiling/src/wall_time.rs
+++ b/profiling/src/wall_time.rs
@@ -3,6 +3,7 @@
 
 use crate::bindings::{zend_execute_data, zend_interrupt_function, VmInterruptFn};
 use crate::{profiling::Profiler, REQUEST_LOCALS};
+use core::ptr;
 use std::sync::atomic::Ordering;
 
 #[cfg(not(php_frameless))]
@@ -13,8 +14,8 @@ mod execute_internal {
     use zend::{zend_execute_internal, zval, ZEND_ACC_CALL_VIA_TRAMPOLINE};
 
     /// The engine's previous [zend::zend_execute_internal] value, or
-    /// [zend::execute_internal] if none. This is a highly active path, so although
-    /// it could be made safe with Mutex, the cost is too high.
+    /// [zend::execute_internal] if none. This is a highly active path, so
+    /// although it could be made safe with Mutex, the cost is too high.
     static mut PREV_EXECUTE_INTERNAL: MaybeUninit<
         unsafe extern "C" fn(execute_data: *mut zend_execute_data, return_value: *mut zval),
     > = MaybeUninit::uninit();
@@ -39,15 +40,27 @@ mod execute_internal {
         ((*(*execute_data).func).common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) != 0
     }
 
-    /// Overrides the engine's zend_execute_internal hook in order to process pending VM interrupts
-    /// while the internal function is still on top of the call stack. The VM does not process the
-    /// interrupt until the call returns so that it could theoretically jump to a different opcode,
-    /// like a fiber scheduler.
-    /// For our particular case this is problematic. For example, when the user does something like
-    /// `sleep(seconds: 10)`, the normal interrupt handling will not trigger until sleep returns,
-    /// so we'd then attribute all that time spent sleeping to whatever runs next. This is why we
-    /// intercept `zend_execute_internal` and process our own VM interrupts, but it doesn't delegate
-    /// to the  previous VM interrupt hook, as it's not expecting to be called from this state.
+    /// Overrides the engine's zend_execute_internal hook to process pending
+    /// VM interrupts while the internal function is still on top of the call
+    /// stack.
+    ///
+    /// Before PHP 8.4, the VM does not process the interrupt until the call
+    /// returns so that it could theoretically jump to a different opcode,
+    /// like a fiber scheduler. However, in practice, this hasn't been
+    /// possible since PHP 8.0 when zend_call_function started calling
+    /// zend_interrupt_function, while the internal frame is still on the call
+    /// stack.
+    ///
+    /// Consider when the user does something like `sleep(seconds: 10)`. The
+    /// normal interrupt handling will not trigger until sleep returns, so
+    /// we'll attribute all the time spent sleeping to whatever runs next.
+    /// This is why we intercept `zend_execute_internal` and process our own
+    /// VM interrupts, but it doesn't delegate to the previous VM interrupt
+    /// hook, as the interrupt hooks weren't expected to be called from this
+    /// place.
+    ///
+    /// Levi changed this in 8.4: https://github.com/php/php-src/pull/14627,
+    /// which is why this isn't needed on 8.4+.
     extern "C" fn execute_internal(execute_data: *mut zend_execute_data, return_value: *mut zval) {
         // SAFETY: called before executing the trampoline.
         let leaf_frame = if unsafe { execute_data_func_is_trampoline(execute_data) } {
@@ -58,7 +71,8 @@ mod execute_internal {
         };
 
         // SAFETY: PREV_EXECUTE_INTERNAL was written during minit, doesn't change during runtime.
-        let prev_execute_internal = unsafe { *PREV_EXECUTE_INTERNAL.as_mut_ptr() };
+        let prev_execute_internal =
+            unsafe { (*ptr::addr_of_mut!(PREV_EXECUTE_INTERNAL)).assume_init_mut() };
 
         // SAFETY: calling prev_execute without modification will be safe.
         unsafe { prev_execute_internal(execute_data, return_value) };
@@ -68,8 +82,11 @@ mod execute_internal {
         ddog_php_prof_interrupt_function(leaf_frame);
     }
 
+    /// # Safety
+    /// Only call during extension MINIT.
     pub unsafe fn minit() {
-        PREV_EXECUTE_INTERNAL.write(zend_execute_internal.unwrap_or(zend::execute_internal));
+        (*ptr::addr_of_mut!(PREV_EXECUTE_INTERNAL))
+            .write(zend_execute_internal.unwrap_or(zend::execute_internal));
         zend_execute_internal = Some(execute_internal);
     }
 }
@@ -127,7 +144,8 @@ extern "C" fn ddog_php_prof_interrupt_function_wrapper(execute_data: *mut zend_e
     ddog_php_prof_interrupt_function(execute_data);
 
     // SAFETY: PREV_INTERRUPT_FUNCTION was written during minit, doesn't change during runtime.
-    if let Some(prev_interrupt) = unsafe { PREV_INTERRUPT_FUNCTION.as_ref() } {
+    if let Some(prev_interrupt) = unsafe { (*ptr::addr_of_mut!(PREV_INTERRUPT_FUNCTION)).as_ref() }
+    {
         // SAFETY: calling the interrupt handler with correct args at right place.
         unsafe { prev_interrupt(execute_data) };
     }
@@ -136,13 +154,14 @@ extern "C" fn ddog_php_prof_interrupt_function_wrapper(execute_data: *mut zend_e
 /// # Safety
 /// Only call during PHP's minit phase.
 pub unsafe fn minit() {
-    PREV_INTERRUPT_FUNCTION = zend_interrupt_function;
-    let function = if zend_interrupt_function.is_some() {
+    ptr::addr_of_mut!(PREV_INTERRUPT_FUNCTION).write(zend_interrupt_function);
+    let interrupt_function = ptr::addr_of_mut!(zend_interrupt_function);
+    let function = if interrupt_function.read().is_some() {
         ddog_php_prof_interrupt_function_wrapper
     } else {
         ddog_php_prof_interrupt_function
     };
-    zend_interrupt_function = Some(function);
+    interrupt_function.write(Some(function));
 
     #[cfg(not(php_frameless))]
     execute_internal::minit();


Reviews:
user: login: morrisonlevi
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-07T00:04:59Z
user: login: realFlowControl
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-07T07:02:39Z
user: login: realFlowControl
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-07T07:02:45Z
user: login: morrisonlevi
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-07T16:05:57Z

Review comments:
diff_hunk: @@ -461,6 +461,8 @@ mod detail {
 
 pub use detail::*;
 
+// todo: this should be feature = "stack_walking_tests" but it seemed to
+//       cause a failure in CI to migrate it.
 #[cfg(all(test, stack_walking_tests))]
 mod tests {
path: profiling/src/profiling/stack_walking.rs
user: login: morrisonlevi
body: Example of the error ([job](https://app.circleci.com/pipelines/github/DataDog/dd-trace-php/20870/workflows/6c0f6e63-502d-45d1-b234-c798f6c98fd1/jobs/6930919)):

```
  = note: /mnt/ramdisk/cargo/release/deps/datadog_php_profiling-a15e7ad77c649749.datadog_php_profiling.b94f957b4ddf90d7-cgu.0.rcgu.o: In function `{closure#0}':
          /home/circleci/datadog/profiling/src/profiling/stack_walking.rs:263: undefined reference to `zend_flf_functions'
          collect2: error: ld returned 1 exit status
          
  = note: some `extern` functions couldn't be found; some native libraries may need to be installed or have their path specified
  = note: use the `-l` flag to specify native libraries to link
  = note: use the `cargo:rustc-link-lib` directive to specify the native libraries to link with Cargo (see https://doc.rust-lang.org/cargo/reference/build-scripts.html#rustc-link-lib)

error: could not compile `datadog-php-profiling` (lib test) due to 1 previous error
```
created_at: 2025-05-07T00:04:59Z
author_association: COLLABORATOR
diff_hunk: @@ -374,12 +377,13 @@ unsafe fn alloc_prof_orig_alloc(len: size_t) -> *mut c_void {
 
 /// This function exists because when calling `zend_mm_set_custom_handlers()`,
 /// you need to pass a pointer to a `free()` function as well, otherwise your
-/// custom handlers won't be installed. We can not just point to the original
+/// custom handlers won't be installed. We cannot just point to the original
 /// `zend::_zend_mm_free()` as the function definitions differ.
 unsafe extern "C" fn alloc_prof_free(ptr: *mut c_void) {
     tls_zend_mm_state_get!(free)(ptr);
 }
 
+#[export_name = "ddog_php_prof_alloc_prof_prev_free"]
path: profiling/src/allocation/allocation_le83.rs
user: login: realFlowControl
body: ```suggestion
```

Do we need this and if so, why do we not need to change the exported symbol name for the other functions?
created_at: 2025-05-07T07:02:38Z
author_association: MEMBER
diff_hunk: @@ -374,12 +377,13 @@ unsafe fn alloc_prof_orig_alloc(len: size_t) -> *mut c_void {
 
 /// This function exists because when calling `zend_mm_set_custom_handlers()`,
 /// you need to pass a pointer to a `free()` function as well, otherwise your
-/// custom handlers won't be installed. We can not just point to the original
+/// custom handlers won't be installed. We cannot just point to the original
 /// `zend::_zend_mm_free()` as the function definitions differ.
 unsafe extern "C" fn alloc_prof_free(ptr: *mut c_void) {
     tls_zend_mm_state_get!(free)(ptr);
 }
 
+#[export_name = "ddog_php_prof_alloc_prof_prev_free"]
path: profiling/src/allocation/allocation_le83.rs
user: login: morrisonlevi
body: No, I don't even remember why I put it on there, it was during experimentation with some error I was getting. Removed. Thanks, good catch.
created_at: 2025-05-07T16:05:57Z
author_association: COLLABORATOR

PR comments:
user: login: pr-commenter[bot]
created_at: 2025-05-07T00:02:19Z
body: ## Benchmarks [ profiler ]

Benchmark execution time: 2025-05-07 15:52:19

Comparing candidate commit bf02b95ae2c56d22bccf0a7bc44a848ec6823a4b in PR branch `levi/clippy-fixes` with baseline commit 50840e0a5d58bdc551dd53835ed66a675f6a549c in branch `florian/zts-global-state`.

Found 0 performance improvements and 0 performance regressions! Performance is the same for 29 metrics, 7 unstable metrics.


author_association: NONE
user: login: codecov-commenter
created_at: 2025-05-07T00:32:09Z
body: ## [Codecov](https://app.codecov.io/gh/DataDog/dd-trace-php/pull/3235?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog) Report
:white_check_mark: All modified and coverable lines are covered by tests.
:white_check_mark: Project coverage is 79.28%. Comparing base ([`50840e0`](https://app.codecov.io/gh/DataDog/dd-trace-php/commit/50840e0a5d58bdc551dd53835ed66a675f6a549c?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog)) to head ([`bf02b95`](https://app.codecov.io/gh/DataDog/dd-trace-php/commit/bf02b95ae2c56d22bccf0a7bc44a848ec6823a4b?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog)).

<details><summary>Additional details and impacted files</summary>



[![Impacted file tree graph](https://app.codecov.io/gh/DataDog/dd-trace-php/pull/3235/graphs/tree.svg?width=650&height=150&src=pr&token=eXio8H7vwF&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog)](https://app.codecov.io/gh/DataDog/dd-trace-php/pull/3235?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog)

```diff
@@                      Coverage Diff                       @@
##             florian/zts-global-state    #3235      +/-   ##
==============================================================
+ Coverage                       79.26%   79.28%   +0.01%     
  Complexity                       2948     2948              
==============================================================
  Files                             118      118              
  Lines                           11633    11633              
==============================================================
+ Hits                             9221     9223       +2     
+ Misses                           2412     2410       -2     
```

| [Flag](https://app.codecov.io/gh/DataDog/dd-trace-php/pull/3235/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog) | Coverage Δ | |
|---|---|---|
| [tracer-php](https://app.codecov.io/gh/DataDog/dd-trace-php/pull/3235/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog) | `79.28% <ø> (+0.01%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog#carryforward-flags-in-the-pull-request-comment) to find out more.
[see 1 file with indirect coverage changes](https://app.codecov.io/gh/DataDog/dd-trace-php/pull/3235/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog)

------

[Continue to review full report in Codecov by Sentry](https://app.codecov.io/gh/DataDog/dd-trace-php/pull/3235?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://app.codecov.io/gh/DataDog/dd-trace-php/pull/3235?dropdown=coverage&src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog). Last update [50840e0...bf02b95](https://app.codecov.io/gh/DataDog/dd-trace-php/pull/3235?dropdown=coverage&src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog).
</details>
<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE