Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 65 additions & 11 deletions profiling/src/php_ffi.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;

Expand Down
65 changes: 62 additions & 3 deletions profiling/src/profiling/stack_walking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
}
}
}
Loading