@@ -390,19 +390,68 @@ uintptr_t *ddog_test_php_prof_function_run_time_cache(zend_function const *func)
390390}
391391#endif
392392
393- #if CFG_STACK_WALKING_TESTS
393+ #if CFG_STACK_WALKING_TESTS || defined( CFG_TEST )
394394static int (* og_snprintf )(char * , size_t , const char * , ...);
395395
396- // "weak" let's us polyfill, needed by zend_string_init(..., persistent: 1).
397- void * __attribute__ ( (weak )) __zend_malloc (size_t len ) {
398- void * tmp = malloc (len );
399- if (EXPECTED (tmp || !len )) {
400- return tmp ;
401- }
396+ static ZEND_COLD ZEND_NORETURN void out_of_memory (void ) {
402397 fprintf (stderr , "Out of memory\n" );
403398 exit (1 );
404399}
405400
401+ static zend_string * test_zend_string_alloc (size_t len ) {
402+ // We don't need to handle large strings.
403+ if (len > INT32_MAX ) {
404+ out_of_memory ();
405+ }
406+
407+ // zend_string logically has a flexible array member val, but it uses the
408+ // so-called "struct hack" instead. So we calculate the number of bytes
409+ // needed to get to `val`, then add the `len` plus 1 (for a null byte).
410+ uint32_t alloc_len = offsetof(zend_string , val ) + len + 1 ;
411+
412+ // Need to round up to 8 bytes on 64-bit platforms, won't overflow because
413+ // of above large string check.
414+ uint32_t rounded = (alloc_len + UINT32_C (7 )) & ~UINT32_C (7 );
415+ zend_string * zs = calloc (rounded , 1 );
416+ if (!zs ) {
417+ out_of_memory ();
418+ }
419+
420+ // Initialize the refcounted header
421+ #if PHP_VERSION_ID < 70299
422+ GC_REFCOUNT (zs ) = 1 ;
423+ #else
424+ GC_SET_REFCOUNT (zs , 1 );
425+ #endif
426+
427+ #if PHP_VERSION_ID < 70200
428+ #undef GC_FLAGS_SHIFT
429+ #define GC_FLAGS_SHIFT 8
430+ #endif
431+
432+ #if PHP_VERSION_ID < 80000
433+ #undef GC_STRING
434+ #define GC_STRING IS_STRING
435+ #endif
436+
437+ // IS_STR_PERSISTENT means it's allocated with malloc, not emalloc.
438+ GC_TYPE_INFO (zs ) = GC_STRING | (IS_STR_PERSISTENT << GC_FLAGS_SHIFT );
439+
440+ zs -> h = 0 ;
441+ zs -> len = len ;
442+ ZSTR_VAL (zs )[len ] = '\0' ;
443+
444+ return zs ;
445+ }
446+
447+ // Manually create a zend_string without using zend_string_init(), since we
448+ // do not link against PHP at test time
449+ static zend_string * test_zend_string_create (const char * str , size_t len ) {
450+ zend_string * zstr = test_zend_string_alloc (len );
451+ memcpy (ZSTR_VAL (zstr ), str , len );
452+ return zstr ;
453+ }
454+
406455static zend_execute_data * create_fake_frame (int depth ) {
407456 zend_execute_data * execute_data = calloc (1 , sizeof (zend_execute_data ));
408457 zend_op_array * op_array = calloc (1 , sizeof (zend_function ));
@@ -412,11 +461,11 @@ static zend_execute_data *create_fake_frame(int depth) {
412461 char buffer [64 ] = {0 };
413462 int len = og_snprintf (buffer , sizeof buffer , "function name %03d" , depth ) + 1 ;
414463 ZEND_ASSERT (len >= 0 && sizeof buffer > (size_t )len );
415- op_array -> function_name = zend_string_init (buffer , len - 1 , true );
464+ op_array -> function_name = test_zend_string_create (buffer , len - 1 );
416465
417466 len = og_snprintf (buffer , sizeof buffer , "filename-%03d.php" , depth ) + 1 ;
418467 ZEND_ASSERT (len >= 0 && sizeof buffer > (size_t )len );
419- op_array -> filename = zend_string_init (buffer , len - 1 , true );
468+ op_array -> filename = test_zend_string_create (buffer , len - 1 );
420469
421470 return execute_data ;
422471}
@@ -463,7 +512,29 @@ void ddog_php_test_free_fake_zend_execute_data(zend_execute_data *execute_data)
463512
464513 free (execute_data );
465514}
466- #endif
515+
516+ zend_function * ddog_php_test_create_fake_zend_function_with_name_len (size_t len ) {
517+ zend_op_array * op_array = calloc (1 , sizeof (zend_function ));
518+ if (!op_array ) return NULL ;
519+
520+ op_array -> type = ZEND_USER_FUNCTION ;
521+
522+ if (len > 0 ) {
523+ zend_string * zstr = test_zend_string_alloc (len );
524+ memset (ZSTR_VAL (zstr ), 'x' , len );
525+ op_array -> function_name = zstr ;
526+ }
527+
528+ return (zend_function * )op_array ;
529+ }
530+
531+ void ddog_php_test_free_fake_zend_function (zend_function * func ) {
532+ if (!func ) return ;
533+
534+ free (func -> common .function_name );
535+ free (func );
536+ }
537+ #endif // CFG_STACK_WALKING_TESTS || CFG_TEST
467538
468539void * opcache_handle = NULL ;
469540
0 commit comments