diff --git a/profiling/src/php_ffi.c b/profiling/src/php_ffi.c index fd5d07a1755..4a594221247 100644 --- a/profiling/src/php_ffi.c +++ b/profiling/src/php_ffi.c @@ -390,17 +390,39 @@ uintptr_t *ddog_test_php_prof_function_run_time_cache(zend_function const *func) } #endif -#if CFG_STACK_WALKING_TESTS +#if CFG_STACK_WALKING_TESTS || defined(CFG_TEST) static int (*og_snprintf)(char *, size_t, const char *, ...); -// "weak" let's us polyfill, needed by zend_string_init(..., persistent: 1). -void *__attribute__((weak)) __zend_malloc(size_t len) { - void *tmp = malloc(len); - if (EXPECTED(tmp || !len)) { - return tmp; +// Manually create a zend_string without using zend_string_init(), since we +// do not link against PHP at test time +static zend_string *test_zend_string_create(const char *str, size_t len) { + // zend_string has a flexible array member val[1], so we allocate + // sizeof(zend_string) - 1 (for the val[1]) + len + 1 (for null terminator) + zend_string* zs = calloc(1, sizeof(zend_string) + len); + if (!zs) { + return NULL; + } + + // Initialize the refcounted header +#if PHP_VERSION_ID < 70299 + GC_REFCOUNT(zs) = 1; +#else + GC_SET_REFCOUNT(zs, 1); +#endif +#if PHP_VERSION_ID < 70499 + GC_TYPE_INFO(zs) = IS_STRING; +#else + GC_TYPE_INFO(zs) = GC_STRING; +#endif + + zs->h = 0; + zs->len = len; + if (len > 0) { + memcpy(ZSTR_VAL(zs), str, len); } - fprintf(stderr, "Out of memory\n"); - exit(1); + ZSTR_VAL(zs)[len] = '\0'; + + return zs; } static zend_execute_data *create_fake_frame(int depth) { @@ -412,11 +434,11 @@ static zend_execute_data *create_fake_frame(int depth) { char buffer[64] = {0}; int len = og_snprintf(buffer, sizeof buffer, "function name %03d", depth) + 1; ZEND_ASSERT(len >= 0 && sizeof buffer > (size_t)len); - op_array->function_name = zend_string_init(buffer, len - 1, true); + op_array->function_name = test_zend_string_create(buffer, len - 1); len = og_snprintf(buffer, sizeof buffer, "filename-%03d.php", depth) + 1; ZEND_ASSERT(len >= 0 && sizeof buffer > (size_t)len); - op_array->filename = zend_string_init(buffer, len - 1, true); + op_array->filename = test_zend_string_create(buffer, len - 1); return execute_data; } @@ -463,7 +485,39 @@ void ddog_php_test_free_fake_zend_execute_data(zend_execute_data *execute_data) free(execute_data); } -#endif + +zend_function *ddog_php_test_create_fake_zend_function_with_name_len(size_t len) { + zend_op_array *op_array = calloc(1, sizeof(zend_function)); + if (!op_array) return NULL; + + op_array->type = ZEND_USER_FUNCTION; + + if (len > 0) { + char *buffer = malloc(len + 1); + if (!buffer) { + free(op_array); + return NULL; + } + memset(buffer, 'x', len); + buffer[len] = '\0'; + op_array->function_name = test_zend_string_create(buffer, len); + free(buffer); + if (!op_array->function_name) { + free(op_array); + return NULL; + } + } + + return (zend_function *)op_array; +} + +void ddog_php_test_free_fake_zend_function(zend_function *func) { + if (!func) return; + + free(func->common.function_name); + free(func); +} +#endif // CFG_STACK_WALKING_TESTS || CFG_TEST void *opcache_handle = NULL; diff --git a/profiling/src/profiling/stack_walking.rs b/profiling/src/profiling/stack_walking.rs index 04a8babfca0..6f38b74dbb1 100644 --- a/profiling/src/profiling/stack_walking.rs +++ b/profiling/src/profiling/stack_walking.rs @@ -528,14 +528,20 @@ 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))] +#[cfg(test)] mod tests { use super::*; use crate::bindings as zend; + extern "C" { + fn ddog_php_test_create_fake_zend_function_with_name_len( + len: libc::size_t, + ) -> *mut zend::zend_function; + fn ddog_php_test_free_fake_zend_function(func: *mut zend::zend_function); + } + #[test] + #[cfg(stack_walking_tests)] fn test_collect_stack_sample() { unsafe { let fake_execute_data = zend::ddog_php_test_create_fake_zend_execute_data(3); @@ -560,4 +566,57 @@ mod tests { zend::ddog_php_test_free_fake_zend_execute_data(fake_execute_data); } } + + #[test] + fn test_extract_function_name_short_string() { + unsafe { + let func = ddog_php_test_create_fake_zend_function_with_name_len(10); + assert!(!func.is_null()); + + let name = extract_function_name(&*func).expect("should extract name"); + assert_eq!(name, "xxxxxxxxxx"); + + ddog_php_test_free_fake_zend_function(func); + } + } + + #[test] + fn test_extract_function_name_at_limit_minus_one() { + unsafe { + let func = ddog_php_test_create_fake_zend_function_with_name_len(STR_LEN_LIMIT - 1); + assert!(!func.is_null()); + + let name = extract_function_name(&*func).expect("should extract name"); + assert_eq!(name.len(), STR_LEN_LIMIT - 1); + assert_ne!(name, COW_LARGE_STRING); + + ddog_php_test_free_fake_zend_function(func); + } + } + + #[test] + fn test_extract_function_name_at_limit() { + unsafe { + let func = ddog_php_test_create_fake_zend_function_with_name_len(STR_LEN_LIMIT); + assert!(!func.is_null()); + + let name = extract_function_name(&*func).expect("should return large string marker"); + assert_eq!(name, COW_LARGE_STRING); + + ddog_php_test_free_fake_zend_function(func); + } + } + + #[test] + fn test_extract_function_name_over_limit() { + unsafe { + let func = ddog_php_test_create_fake_zend_function_with_name_len(STR_LEN_LIMIT + 1000); + assert!(!func.is_null()); + + let name = extract_function_name(&*func).expect("should return large string marker"); + assert_eq!(name, COW_LARGE_STRING); + + ddog_php_test_free_fake_zend_function(func); + } + } }