From 761d7891bddcc3658c6143bb5e04dc7db82fdd11 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Thu, 11 Jul 2024 21:54:39 +0900 Subject: [PATCH 01/43] Add method_intercept_init function to Ray AOP extension A new function, method_intercept_init, has been added to the Ray AOP PHP extension. This function is designed to reset the intercept hash table, ensuring it starts empty. This change should help improve the handling of method interception by allowing a more controlled initialization of the intercept hash table. --- php_rayaop.h | 1 + rayaop.c | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/php_rayaop.h b/php_rayaop.h index 87742f1..c301275 100644 --- a/php_rayaop.h +++ b/php_rayaop.h @@ -64,6 +64,7 @@ PHP_RINIT_FUNCTION(rayaop); /* Request initialization function */ PHP_RSHUTDOWN_FUNCTION(rayaop); /* Request shutdown function */ PHP_MINFO_FUNCTION(rayaop); /* Module information function */ PHP_FUNCTION(method_intercept); /* Method intercept function */ +PHP_FUNCTION(method_intercept_init); /* Add this line for the new function */ /* Utility function declarations */ void php_rayaop_handle_error(const char *message); /* Error handling function */ diff --git a/rayaop.c b/rayaop.c index f8fff18..98c20ab 100644 --- a/rayaop.c +++ b/rayaop.c @@ -45,6 +45,10 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_method_intercept, 0, 0, 3) ZEND_ARG_OBJ_INFO(0, interceptor, Ray\\Aop\\MethodInterceptorInterface, 0) /* Argument information for intercept handler */ ZEND_END_ARG_INFO() +/* Argument information for method_intercept_init function */ +ZEND_BEGIN_ARG_INFO(arginfo_method_intercept_init, 0) +ZEND_END_ARG_INFO() + /* {{{ proto void php_rayaop_handle_error(const char *message) Error handling function @@ -306,6 +310,24 @@ RETURN_FALSE; /* Return false and end */ } /* }}} */ +/* {{{ proto void method_intercept_init() + Function to reset intercept table + + This function clears the intercept hash table, ensuring it starts empty. + +*/ +PHP_FUNCTION(method_intercept_init) { + PHP_RAYAOP_DEBUG_PRINT("method_intercept_init called"); /* Output debug information */ + if (RAYAOP_G(intercept_ht)) { + zend_hash_clean(RAYAOP_G(intercept_ht)); /* Clear hash table */ + } else { + ALLOC_HASHTABLE(RAYAOP_G(intercept_ht)); /* Allocate memory for hash table if not already allocated */ + zend_hash_init(RAYAOP_G(intercept_ht), 8, NULL, php_rayaop_free_intercept_info, 0); /* Initialize hash table */ + } + RETURN_TRUE; /* Return true to indicate success */ +} +/* }}} */ + /* Interface definition */ zend_class_entry *ray_aop_method_interceptor_interface_ce; @@ -456,6 +478,7 @@ static void php_rayaop_dump_intercept_info(void) /* Definition of functions provided by the extension */ static const zend_function_entry rayaop_functions[] = { PHP_FE(method_intercept, arginfo_method_intercept) /* Register method_intercept function */ + PHP_FE(method_intercept_init, arginfo_method_intercept_init) /* Register method_intercept_init function */ PHP_FE_END /* End of function entries */ }; From dc779fbd2cf6c79f2ca3d653914b54f1564e661c Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Thu, 11 Jul 2024 22:12:35 +0900 Subject: [PATCH 02/43] Add test for method_intercept_init function A new test file has been added for the method_intercept_init function. This test verifies that the function correctly initializes the intercept table, intercepts the method, and stops intercepting once the table is re-initialized. It ensures the functionality of the RayAOP extension. --- tests/004-rayaop_method_intercept_init.phpt | 56 +++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 tests/004-rayaop_method_intercept_init.phpt diff --git a/tests/004-rayaop_method_intercept_init.phpt b/tests/004-rayaop_method_intercept_init.phpt new file mode 100644 index 0000000..d2ea252 --- /dev/null +++ b/tests/004-rayaop_method_intercept_init.phpt @@ -0,0 +1,56 @@ +--TEST-- +Test method_intercept_init function +--SKIPIF-- + +--FILE-- +testMethod(); + + // Re-initialize intercept table + method_intercept_init(); + + // Call the method again to ensure no interception after init + $testObject->testMethod(); +} + +class Test { + public function testMethod() { + echo "Original method executed.\n"; + } +} + +// Run the test +test_intercept_init(); +?> +--EXPECT-- +Original method executed. +Original method executed. From f40bf73b69f841703685a2f23c77b1f591bfe8f4 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Thu, 11 Jul 2024 22:46:18 +0900 Subject: [PATCH 03/43] Enable debug mode and add detailed debug printing in rayaop The commit enables the RAYAOP_DEBUG for more thorough debugging. Additionally, it adds substantial debug print statements in the php_rayaop_execute_ex function. These statements log detailed information about the execution data and method calls, which will be invaluable in identifying issues and troubleshooting errors. --- rayaop.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 2 deletions(-) diff --git a/rayaop.c b/rayaop.c index 98c20ab..04982d3 100644 --- a/rayaop.c +++ b/rayaop.c @@ -8,7 +8,7 @@ #define RAYAOP_QUIET 0 #endif -// #define RAYAOP_DEBUG +#define RAYAOP_DEBUG #include "php_rayaop.h" /* Include header file for RayAOP extension */ @@ -211,11 +211,41 @@ void php_rayaop_free_intercept_info(zval *zv) { @param zend_execute_data *execute_data The execution data */ + static void php_rayaop_execute_ex(zend_execute_data *execute_data) { PHP_RAYAOP_DEBUG_PRINT("php_rayaop_execute_ex called"); /* Output debug information */ if (!php_rayaop_should_intercept(execute_data)) { /* If interception is not necessary */ + PHP_RAYAOP_DEBUG_PRINT("Interception not necessary, calling original execute_ex function"); + + // Debug information about execute_data + if (execute_data == NULL) { + PHP_RAYAOP_DEBUG_PRINT("execute_data is NULL"); + } else { + if (execute_data->func == NULL) { + PHP_RAYAOP_DEBUG_PRINT("execute_data->func is NULL"); + } else { + if (execute_data->func->common.function_name == NULL) { + PHP_RAYAOP_DEBUG_PRINT("execute_data->func->common.function_name is NULL"); + } else { + PHP_RAYAOP_DEBUG_PRINT("Function name: %s", ZSTR_VAL(execute_data->func->common.function_name)); + } + + if (execute_data->func->common.scope == NULL) { + PHP_RAYAOP_DEBUG_PRINT("execute_data->func->common.scope is NULL"); + } else { + PHP_RAYAOP_DEBUG_PRINT("Class name: %s", ZSTR_VAL(execute_data->func->common.scope->name)); + } + } + } + + PHP_RAYAOP_DEBUG_PRINT("Calling original execute_ex function"); + + if (php_rayaop_original_execute_ex == NULL) { + PHP_RAYAOP_DEBUG_PRINT("php_rayaop_original_execute_ex is NULL"); + } + php_rayaop_original_execute_ex(execute_data); /* Call the original execution function */ return; /* End processing */ } @@ -224,6 +254,8 @@ static void php_rayaop_execute_ex(zend_execute_data *execute_data) { zend_string *class_name = current_function->common.scope->name; /* Get class name */ zend_string *method_name = current_function->common.function_name; /* Get method name */ + PHP_RAYAOP_DEBUG_PRINT("Function: %s::%s", ZSTR_VAL(class_name), ZSTR_VAL(method_name)); + size_t key_len; char *key = php_rayaop_generate_intercept_key(class_name, method_name, &key_len); /* Generate intercept key */ @@ -236,11 +268,51 @@ static void php_rayaop_execute_ex(zend_execute_data *execute_data) { } else { /* If intercept information is not found */ PHP_RAYAOP_DEBUG_PRINT("No intercept info found for key: %s", key); /* Output debug information */ - php_rayaop_original_execute_ex(execute_data); /* Call the original execution function */ + PHP_RAYAOP_DEBUG_PRINT("Calling original execute_ex function"); + + // Debug information about execute_data before calling the original function + if (execute_data == NULL) { + PHP_RAYAOP_DEBUG_PRINT("execute_data is NULL"); + } else { + if (execute_data->func == NULL) { + PHP_RAYAOP_DEBUG_PRINT("execute_data->func is NULL"); + } else { + if (execute_data->func->common.function_name == NULL) { + PHP_RAYAOP_DEBUG_PRINT("execute_data->func->common.function_name is NULL"); + } else { + PHP_RAYAOP_DEBUG_PRINT("Function name: %s", ZSTR_VAL(execute_data->func->common.function_name)); + } + + if (execute_data->func->common.scope == NULL) { + PHP_RAYAOP_DEBUG_PRINT("execute_data->func->common.scope is NULL"); + } else { + PHP_RAYAOP_DEBUG_PRINT("Class name: %s", ZSTR_VAL(execute_data->func->common.scope->name)); + } + } + } + + if (php_rayaop_original_execute_ex == NULL) { + PHP_RAYAOP_DEBUG_PRINT("php_rayaop_original_execute_ex is NULL"); + } + PHP_RAYAOP_DEBUG_PRINT("Origilan method calling..."); + if (execute_data == NULL || execute_data->func == NULL || php_rayaop_original_execute_ex == NULL) { + PHP_RAYAOP_DEBUG_PRINT("Invalid pointers detected"); + // エラー処理 + return; + } + PHP_RAYAOP_DEBUG_PRINT("Calling original execute_ex. execute_data: %p, func: %p, original_execute_ex: %p", + (void*)execute_data, + (void*)(execute_data ? execute_data->func : NULL), + (void*)php_rayaop_original_execute_ex); + php_rayaop_original_execute_ex(execute_data); + /* Call the original execution function */ + PHP_RAYAOP_DEBUG_PRINT("Origilan method called sucsessfully!"); + } efree(key); /* Free memory for key */ } + /* }}} */ /* {{{ proto void php_rayaop_hash_update_failed(php_rayaop_intercept_info *new_info, char *key) From d7347b69f160dba25cdb2c7e0103e7e77cfdd3e1 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Thu, 11 Jul 2024 23:26:30 +0900 Subject: [PATCH 04/43] Add execution depth tracking to rayaop module The execution depth tracking was implemented in the rayaop module. A new variable 'execution_depth' was introduced, incrementing and decrementing during the execution. This allows tracking the depth of the execution and logging it for better understanding and debugging code paths. --- php_rayaop.h | 1 + rayaop.c | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/php_rayaop.h b/php_rayaop.h index c301275..794151b 100644 --- a/php_rayaop.h +++ b/php_rayaop.h @@ -83,6 +83,7 @@ ZEND_BEGIN_MODULE_GLOBALS(rayaop) /* Start of rayaop module global variables */ HashTable *intercept_ht; /* Intercept hash table */ zend_bool is_intercepting; /* Intercepting flag */ + int execution_depth; ZEND_END_MODULE_GLOBALS(rayaop) /* End of rayaop module global variables */ /* If in thread-safe mode, global variable access macro (thread-safe version) */ diff --git a/rayaop.c b/rayaop.c index 04982d3..2605b1f 100644 --- a/rayaop.c +++ b/rayaop.c @@ -28,6 +28,7 @@ static void (*php_rayaop_original_execute_ex)(zend_execute_data *execute_data); static void php_rayaop_init_globals(zend_rayaop_globals *rayaop_globals) { rayaop_globals->intercept_ht = NULL; /* Initialize intercept hash table */ rayaop_globals->is_intercepting = 0; /* Initialize intercept flag */ + rayaop_globals->execution_depth = 0; } /* }}} */ @@ -215,6 +216,11 @@ void php_rayaop_free_intercept_info(zval *zv) { static void php_rayaop_execute_ex(zend_execute_data *execute_data) { PHP_RAYAOP_DEBUG_PRINT("php_rayaop_execute_ex called"); /* Output debug information */ + // 実行深度を増加させ、ログに記録 + RAYAOP_G(execution_depth)++; + + PHP_RAYAOP_DEBUG_PRINT("Execution depth: %d", RAYAOP_G(execution_depth)); + if (!php_rayaop_should_intercept(execute_data)) { /* If interception is not necessary */ PHP_RAYAOP_DEBUG_PRINT("Interception not necessary, calling original execute_ex function"); @@ -247,6 +253,8 @@ static void php_rayaop_execute_ex(zend_execute_data *execute_data) { } php_rayaop_original_execute_ex(execute_data); /* Call the original execution function */ + RAYAOP_G(execution_depth)--; + return; /* End processing */ } @@ -311,6 +319,7 @@ static void php_rayaop_execute_ex(zend_execute_data *execute_data) { } efree(key); /* Free memory for key */ + RAYAOP_G(execution_depth)--; } /* }}} */ @@ -482,6 +491,7 @@ PHP_RINIT_FUNCTION(rayaop) { zend_hash_init(RAYAOP_G(intercept_ht), 8, NULL, php_rayaop_free_intercept_info, 0); /* Initialize hash table */ } RAYAOP_G(is_intercepting) = 0; /* Initialize intercept flag */ + RAYAOP_G(execution_depth) = 0; return SUCCESS; /* Return success */ } /* }}} */ From f093f3952854c684dbeb6691d035ad8c29574ab6 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Fri, 12 Jul 2024 06:11:18 +0900 Subject: [PATCH 05/43] Add method interception enabling functionality A new PHP function 'enable_method_intercept' has been added to provide the ability to enable or disable method interception at runtime. Turned off the interception by default. The argument information for this new function has been added and the function is properly registered. --- php_rayaop.h | 1 + rayaop.c | 38 +++++++++++++++++++++++++++++++++++--- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/php_rayaop.h b/php_rayaop.h index 794151b..c019aff 100644 --- a/php_rayaop.h +++ b/php_rayaop.h @@ -84,6 +84,7 @@ ZEND_BEGIN_MODULE_GLOBALS(rayaop) HashTable *intercept_ht; /* Intercept hash table */ zend_bool is_intercepting; /* Intercepting flag */ int execution_depth; + zend_bool method_intercept_enabled; ZEND_END_MODULE_GLOBALS(rayaop) /* End of rayaop module global variables */ /* If in thread-safe mode, global variable access macro (thread-safe version) */ diff --git a/rayaop.c b/rayaop.c index 2605b1f..dee23f5 100644 --- a/rayaop.c +++ b/rayaop.c @@ -50,6 +50,11 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO(arginfo_method_intercept_init, 0) ZEND_END_ARG_INFO() +/* Argument information for method_intercept_enable function */ +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_enable_method_intercept, 0, 1, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, enable, _IS_BOOL, 0) +ZEND_END_ARG_INFO() + /* {{{ proto void php_rayaop_handle_error(const char *message) Error handling function @@ -407,6 +412,24 @@ PHP_FUNCTION(method_intercept_init) { } RETURN_TRUE; /* Return true to indicate success */ } + +PHP_FUNCTION(enable_method_intercept) +{ + zend_bool enable; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_BOOL(enable) + ZEND_PARSE_PARAMETERS_END(); + + RAYAOP_G(method_intercept_enabled) = enable; + if (enable) { + zend_execute_ex = php_rayaop_execute_ex; + PHP_RAYAOP_DEBUG_PRINT("Method intercept enabled"); + } else { + zend_execute_ex = php_rayaop_original_execute_ex; + PHP_RAYAOP_DEBUG_PRINT("Method intercept disabled"); + } +} /* }}} */ /* Interface definition */ @@ -451,7 +474,9 @@ PHP_MINIT_FUNCTION(rayaop) { ray_aop_method_interceptor_interface_ce = zend_register_internal_interface(&ce); /* Register interface */ php_rayaop_original_execute_ex = zend_execute_ex; /* Save the original zend_execute_ex function */ - zend_execute_ex = php_rayaop_execute_ex; /* Set the custom zend_execute_ex function */ + + /** disenable interceptiong */ + RAYAOP_G(method_intercept_enabled) = 0; PHP_RAYAOP_DEBUG_PRINT("RayAOP extension initialized"); /* Output debug information */ return SUCCESS; /* Return success */ @@ -468,8 +493,13 @@ PHP_MINIT_FUNCTION(rayaop) { */ PHP_MSHUTDOWN_FUNCTION(rayaop) { PHP_RAYAOP_DEBUG_PRINT("RayAOP PHP_MSHUTDOWN_FUNCTION called"); /* Output debug information */ - zend_execute_ex = php_rayaop_original_execute_ex; /* Restore the original zend_execute_ex function */ - php_rayaop_original_execute_ex = NULL; /* Clear the saved pointer */ + if (php_rayaop_original_execute_ex) { + PHP_RAYAOP_DEBUG_PRINT("Restoring original execute_ex"); + zend_execute_ex = php_rayaop_original_execute_ex; /* Restore the original zend_execute_ex function */ + php_rayaop_original_execute_ex = NULL; /* Clear the saved pointer */ + } else { + PHP_RAYAOP_DEBUG_PRINT("Original execute_ex was already NULL"); + } PHP_RAYAOP_DEBUG_PRINT("RayAOP PHP_MSHUTDOWN_FUNCTION shut down"); /* Output debug information */ return SUCCESS; /* Return shutdown success */ } @@ -561,6 +591,8 @@ static void php_rayaop_dump_intercept_info(void) static const zend_function_entry rayaop_functions[] = { PHP_FE(method_intercept, arginfo_method_intercept) /* Register method_intercept function */ PHP_FE(method_intercept_init, arginfo_method_intercept_init) /* Register method_intercept_init function */ + PHP_FE(enable_method_intercept, arginfo_enable_method_intercept) /* Register enable_method_intercept function */ + PHP_FE_END /* End of function entries */ }; From 2009596ea02cdc6be88728425d3feead926cdbd3 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Fri, 12 Jul 2024 06:14:31 +0900 Subject: [PATCH 06/43] Add null check for execute_data->return_value The change adds an additional null checking condition before copying the return value in the call intercept handler method. This is to avoid a potential null pointer dereference. In case of a null value, a debug warning message is logged. --- rayaop.c | 128 +++++++++++++++++++++---------------------------------- 1 file changed, 48 insertions(+), 80 deletions(-) diff --git a/rayaop.c b/rayaop.c index dee23f5..a8d2a7b 100644 --- a/rayaop.c +++ b/rayaop.c @@ -176,7 +176,11 @@ void php_rayaop_execute_intercept(zend_execute_data *execute_data, php_rayaop_in ZVAL_UNDEF(&retval); if (call_intercept_handler(&info->handler, params, &retval)) { if (!Z_ISUNDEF(retval)) { - ZVAL_COPY(execute_data->return_value, &retval); + if (execute_data->return_value) { + ZVAL_COPY(execute_data->return_value, &retval); + } else { + PHP_RAYAOP_DEBUG_PRINT("Warning: execute_data->return_value is NULL"); + } } } else { php_error_docref(NULL, E_WARNING, "Interception failed for %s::%s", ZSTR_VAL(info->class_name), ZSTR_VAL(info->method_name)); @@ -219,104 +223,68 @@ void php_rayaop_free_intercept_info(zval *zv) { */ static void php_rayaop_execute_ex(zend_execute_data *execute_data) { - PHP_RAYAOP_DEBUG_PRINT("php_rayaop_execute_ex called"); /* Output debug information */ + if (execute_data->func && execute_data->func->common.function_name) { + const char *fname = ZSTR_VAL(execute_data->func->common.function_name); + const char *class_name = execute_data->func->common.scope ? ZSTR_VAL(execute_data->func->common.scope->name) : NULL; + + // Skip specific functions + if (class_name && strcmp(class_name, "PhpToken") == 0 && strcmp(fname, "tokenize") == 0) { + PHP_RAYAOP_DEBUG_PRINT("Skipping PhpToken::tokenize"); + php_rayaop_original_execute_ex(execute_data); + return; + } + + if (class_name && strcmp(class_name, "PhpParser\\Parser\\Php8") == 0 && strcmp(fname, "{closure}") == 0) { + PHP_RAYAOP_DEBUG_PRINT("Skipping PhpParser\\Parser\\Php8::{closure}"); + php_rayaop_original_execute_ex(execute_data); + return; + } + + if (class_name && strcmp(class_name, "PHPUnit\\Framework\\TestCase") == 0 && strcmp(fname, "startOutputBuffering") == 0) { + PHP_RAYAOP_DEBUG_PRINT("Skipping PHPUnit\\Framework\\TestCase::startOutputBuffering"); + php_rayaop_original_execute_ex(execute_data); + return; + } + } + + PHP_RAYAOP_DEBUG_PRINT("php_rayaop_execute_ex called"); - // 実行深度を増加させ、ログに記録 RAYAOP_G(execution_depth)++; PHP_RAYAOP_DEBUG_PRINT("Execution depth: %d", RAYAOP_G(execution_depth)); if (!php_rayaop_should_intercept(execute_data)) { - /* If interception is not necessary */ PHP_RAYAOP_DEBUG_PRINT("Interception not necessary, calling original execute_ex function"); - - // Debug information about execute_data - if (execute_data == NULL) { - PHP_RAYAOP_DEBUG_PRINT("execute_data is NULL"); - } else { - if (execute_data->func == NULL) { - PHP_RAYAOP_DEBUG_PRINT("execute_data->func is NULL"); - } else { - if (execute_data->func->common.function_name == NULL) { - PHP_RAYAOP_DEBUG_PRINT("execute_data->func->common.function_name is NULL"); - } else { - PHP_RAYAOP_DEBUG_PRINT("Function name: %s", ZSTR_VAL(execute_data->func->common.function_name)); - } - - if (execute_data->func->common.scope == NULL) { - PHP_RAYAOP_DEBUG_PRINT("execute_data->func->common.scope is NULL"); - } else { - PHP_RAYAOP_DEBUG_PRINT("Class name: %s", ZSTR_VAL(execute_data->func->common.scope->name)); - } - } - } - - PHP_RAYAOP_DEBUG_PRINT("Calling original execute_ex function"); - - if (php_rayaop_original_execute_ex == NULL) { - PHP_RAYAOP_DEBUG_PRINT("php_rayaop_original_execute_ex is NULL"); - } - - php_rayaop_original_execute_ex(execute_data); /* Call the original execution function */ + php_rayaop_original_execute_ex(execute_data); RAYAOP_G(execution_depth)--; - - return; /* End processing */ + return; } - zend_function *current_function = execute_data->func; /* Get current function information */ - zend_string *class_name = current_function->common.scope->name; /* Get class name */ - zend_string *method_name = current_function->common.function_name; /* Get method name */ + zend_function *current_function = execute_data->func; + zend_string *class_name = current_function->common.scope->name; + zend_string *method_name = current_function->common.function_name; PHP_RAYAOP_DEBUG_PRINT("Function: %s::%s", ZSTR_VAL(class_name), ZSTR_VAL(method_name)); size_t key_len; - char *key = php_rayaop_generate_intercept_key(class_name, method_name, &key_len); /* Generate intercept key */ + char *key = php_rayaop_generate_intercept_key(class_name, method_name, &key_len); - php_rayaop_intercept_info *info = php_rayaop_find_intercept_info(key, key_len); /* Search for intercept information */ + php_rayaop_intercept_info *info = php_rayaop_find_intercept_info(key, key_len); if (info) { - /* If intercept information is found */ - PHP_RAYAOP_DEBUG_PRINT("Found intercept info for key: %s", key); /* Output debug information */ - php_rayaop_execute_intercept(execute_data, info); /* Execute interception */ - } else { - /* If intercept information is not found */ - PHP_RAYAOP_DEBUG_PRINT("No intercept info found for key: %s", key); /* Output debug information */ - PHP_RAYAOP_DEBUG_PRINT("Calling original execute_ex function"); - - // Debug information about execute_data before calling the original function - if (execute_data == NULL) { - PHP_RAYAOP_DEBUG_PRINT("execute_data is NULL"); - } else { - if (execute_data->func == NULL) { - PHP_RAYAOP_DEBUG_PRINT("execute_data->func is NULL"); - } else { - if (execute_data->func->common.function_name == NULL) { - PHP_RAYAOP_DEBUG_PRINT("execute_data->func->common.function_name is NULL"); - } else { - PHP_RAYAOP_DEBUG_PRINT("Function name: %s", ZSTR_VAL(execute_data->func->common.function_name)); - } - - if (execute_data->func->common.scope == NULL) { - PHP_RAYAOP_DEBUG_PRINT("execute_data->func->common.scope is NULL"); - } else { - PHP_RAYAOP_DEBUG_PRINT("Class name: %s", ZSTR_VAL(execute_data->func->common.scope->name)); - } - } - } - - if (php_rayaop_original_execute_ex == NULL) { - PHP_RAYAOP_DEBUG_PRINT("php_rayaop_original_execute_ex is NULL"); - } - PHP_RAYAOP_DEBUG_PRINT("Origilan method calling..."); - if (execute_data == NULL || execute_data->func == NULL || php_rayaop_original_execute_ex == NULL) { - PHP_RAYAOP_DEBUG_PRINT("Invalid pointers detected"); - // エラー処理 + PHP_RAYAOP_DEBUG_PRINT("Found intercept info for key: %s", key); + if (RAYAOP_G(is_intercepting)) { + PHP_RAYAOP_DEBUG_PRINT("Already intercepting, skipping interception"); + php_rayaop_original_execute_ex(execute_data); + efree(key); return; } - PHP_RAYAOP_DEBUG_PRINT("Calling original execute_ex. execute_data: %p, func: %p, original_execute_ex: %p", - (void*)execute_data, - (void*)(execute_data ? execute_data->func : NULL), - (void*)php_rayaop_original_execute_ex); + + RAYAOP_G(is_intercepting) = 1; + php_rayaop_execute_intercept(execute_data, info); + RAYAOP_G(is_intercepting) = 0; + } else { + PHP_RAYAOP_DEBUG_PRINT("No intercept info found for key: %s", key); php_rayaop_original_execute_ex(execute_data); /* Call the original execution function */ PHP_RAYAOP_DEBUG_PRINT("Origilan method called sucsessfully!"); From 564364fc8a7b816d80bfe0143c175a48d1dcd87f Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Fri, 12 Jul 2024 06:21:44 +0900 Subject: [PATCH 07/43] Enhance debug logging and refactor execution interception Increased the detail and scope of debug information during function execution. Refactored the interception process by handling the case when intercept info was not found, improving the overall flow and error handling. Removed some hard-coded function skipping, streamlining the process. --- rayaop.c | 121 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 78 insertions(+), 43 deletions(-) diff --git a/rayaop.c b/rayaop.c index a8d2a7b..8aba944 100644 --- a/rayaop.c +++ b/rayaop.c @@ -223,68 +223,103 @@ void php_rayaop_free_intercept_info(zval *zv) { */ static void php_rayaop_execute_ex(zend_execute_data *execute_data) { - if (execute_data->func && execute_data->func->common.function_name) { - const char *fname = ZSTR_VAL(execute_data->func->common.function_name); - const char *class_name = execute_data->func->common.scope ? ZSTR_VAL(execute_data->func->common.scope->name) : NULL; - - // Skip specific functions - if (class_name && strcmp(class_name, "PhpToken") == 0 && strcmp(fname, "tokenize") == 0) { - PHP_RAYAOP_DEBUG_PRINT("Skipping PhpToken::tokenize"); - php_rayaop_original_execute_ex(execute_data); - return; - } - - if (class_name && strcmp(class_name, "PhpParser\\Parser\\Php8") == 0 && strcmp(fname, "{closure}") == 0) { - PHP_RAYAOP_DEBUG_PRINT("Skipping PhpParser\\Parser\\Php8::{closure}"); - php_rayaop_original_execute_ex(execute_data); - return; - } - - if (class_name && strcmp(class_name, "PHPUnit\\Framework\\TestCase") == 0 && strcmp(fname, "startOutputBuffering") == 0) { - PHP_RAYAOP_DEBUG_PRINT("Skipping PHPUnit\\Framework\\TestCase::startOutputBuffering"); - php_rayaop_original_execute_ex(execute_data); - return; - } - } - - PHP_RAYAOP_DEBUG_PRINT("php_rayaop_execute_ex called"); + PHP_RAYAOP_DEBUG_PRINT("php_rayaop_execute_ex called"); /* Output debug information */ + // 実行深度を増加させ、ログに記録 RAYAOP_G(execution_depth)++; PHP_RAYAOP_DEBUG_PRINT("Execution depth: %d", RAYAOP_G(execution_depth)); if (!php_rayaop_should_intercept(execute_data)) { PHP_RAYAOP_DEBUG_PRINT("Interception not necessary, calling original execute_ex function"); - php_rayaop_original_execute_ex(execute_data); + + // Debug information about execute_data + if (execute_data == NULL) { + PHP_RAYAOP_DEBUG_PRINT("execute_data is NULL"); + } else { + if (execute_data->func == NULL) { + PHP_RAYAOP_DEBUG_PRINT("execute_data->func is NULL"); + } else { + if (execute_data->func->common.function_name == NULL) { + PHP_RAYAOP_DEBUG_PRINT("execute_data->func->common.function_name is NULL"); + } else { + PHP_RAYAOP_DEBUG_PRINT("Function name: %s", ZSTR_VAL(execute_data->func->common.function_name)); + } + + if (execute_data->func->common.scope == NULL) { + PHP_RAYAOP_DEBUG_PRINT("execute_data->func->common.scope is NULL"); + } else { + PHP_RAYAOP_DEBUG_PRINT("Class name: %s", ZSTR_VAL(execute_data->func->common.scope->name)); + } + } + } + + PHP_RAYAOP_DEBUG_PRINT("Calling original execute_ex function"); + + if (php_rayaop_original_execute_ex == NULL) { + PHP_RAYAOP_DEBUG_PRINT("php_rayaop_original_execute_ex is NULL"); + } + + php_rayaop_original_execute_ex(execute_data); /* Call the original execution function */ RAYAOP_G(execution_depth)--; - return; + + return; /* End processing */ } - zend_function *current_function = execute_data->func; - zend_string *class_name = current_function->common.scope->name; - zend_string *method_name = current_function->common.function_name; + zend_function *current_function = execute_data->func; /* Get current function information */ + zend_string *class_name = current_function->common.scope->name; /* Get class name */ + zend_string *method_name = current_function->common.function_name; /* Get method name */ PHP_RAYAOP_DEBUG_PRINT("Function: %s::%s", ZSTR_VAL(class_name), ZSTR_VAL(method_name)); size_t key_len; - char *key = php_rayaop_generate_intercept_key(class_name, method_name, &key_len); + char *key = php_rayaop_generate_intercept_key(class_name, method_name, &key_len); /* Generate intercept key */ - php_rayaop_intercept_info *info = php_rayaop_find_intercept_info(key, key_len); + php_rayaop_intercept_info *info = php_rayaop_find_intercept_info(key, key_len); /* Search for intercept information */ if (info) { - PHP_RAYAOP_DEBUG_PRINT("Found intercept info for key: %s", key); - if (RAYAOP_G(is_intercepting)) { - PHP_RAYAOP_DEBUG_PRINT("Already intercepting, skipping interception"); - php_rayaop_original_execute_ex(execute_data); - efree(key); - return; + /* If intercept information is found */ + PHP_RAYAOP_DEBUG_PRINT("Found intercept info for key: %s", key); /* Output debug information */ + php_rayaop_execute_intercept(execute_data, info); /* Execute interception */ + } else { + /* If intercept information is not found */ + PHP_RAYAOP_DEBUG_PRINT("No intercept info found for key: %s", key); /* Output debug information */ + PHP_RAYAOP_DEBUG_PRINT("Calling original execute_ex function"); + + // Debug information about execute_data before calling the original function + if (execute_data == NULL) { + PHP_RAYAOP_DEBUG_PRINT("execute_data is NULL"); + } else { + if (execute_data->func == NULL) { + PHP_RAYAOP_DEBUG_PRINT("execute_data->func is NULL"); + } else { + if (execute_data->func->common.function_name == NULL) { + PHP_RAYAOP_DEBUG_PRINT("execute_data->func->common.function_name is NULL"); + } else { + PHP_RAYAOP_DEBUG_PRINT("Function name: %s", ZSTR_VAL(execute_data->func->common.function_name)); + } + + if (execute_data->func->common.scope == NULL) { + PHP_RAYAOP_DEBUG_PRINT("execute_data->func->common.scope is NULL"); + } else { + PHP_RAYAOP_DEBUG_PRINT("Class name: %s", ZSTR_VAL(execute_data->func->common.scope->name)); + } + } } - RAYAOP_G(is_intercepting) = 1; - php_rayaop_execute_intercept(execute_data, info); - RAYAOP_G(is_intercepting) = 0; - } else { - PHP_RAYAOP_DEBUG_PRINT("No intercept info found for key: %s", key); + if (php_rayaop_original_execute_ex == NULL) { + PHP_RAYAOP_DEBUG_PRINT("php_rayaop_original_execute_ex is NULL"); + } + PHP_RAYAOP_DEBUG_PRINT("Origilan method calling..."); + if (execute_data == NULL || execute_data->func == NULL || php_rayaop_original_execute_ex == NULL) { + PHP_RAYAOP_DEBUG_PRINT("Invalid pointers detected"); + // エラー処理 + return; + } + PHP_RAYAOP_DEBUG_PRINT("Calling original execute_ex. execute_data: %p, func: %p, original_execute_ex: %p", + (void*)execute_data, + (void*)(execute_data ? execute_data->func : NULL), + (void*)php_rayaop_original_execute_ex); php_rayaop_original_execute_ex(execute_data); /* Call the original execution function */ PHP_RAYAOP_DEBUG_PRINT("Origilan method called sucsessfully!"); From d4a80e343816f5626a56361a802dc8d08533ed9d Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Fri, 12 Jul 2024 06:40:40 +0900 Subject: [PATCH 08/43] Enable method interception in tests Interface method interception has been enabled in the basic and multiple interceptors tests. This was done by adding a line to turn on method interceptor, ensuring that the test conditions for these methods fully reflect the expected functionality. --- tests/001-rayaop-basic.phpt | 1 + tests/003-rayaop-multiple-interceptors.phpt | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/001-rayaop-basic.phpt b/tests/001-rayaop-basic.phpt index 48848fa..9985ce4 100644 --- a/tests/001-rayaop-basic.phpt +++ b/tests/001-rayaop-basic.phpt @@ -18,6 +18,7 @@ class TestInterceptor implements Ray\Aop\MethodInterceptorInterface { // Register the interceptor $result = method_intercept(TestClass::class, 'testMethod', new TestInterceptor()); +enable_method_intercept(true); var_dump($result); // Call the intercepted method diff --git a/tests/003-rayaop-multiple-interceptors.phpt b/tests/003-rayaop-multiple-interceptors.phpt index 339a480..5cf3539 100644 --- a/tests/003-rayaop-multiple-interceptors.phpt +++ b/tests/003-rayaop-multiple-interceptors.phpt @@ -27,6 +27,7 @@ class Interceptor2 implements Ray\Aop\MethodInterceptorInterface { // Register multiple interceptors method_intercept(TestClass::class, 'testMethod', new Interceptor1()); method_intercept(TestClass::class, 'testMethod', new Interceptor2()); +enable_method_intercept(true); $test = new TestClass(); $result = $test->testMethod("Hello"); From ebad93f738b4206cc170d72e14b597296d1dfba2 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Fri, 12 Jul 2024 06:44:07 +0900 Subject: [PATCH 09/43] Add new functions check to rayaop-loaded test Two new assertions were added to the 'rayaop-loaded' test to check if 'method_intercept_init' and 'enable_method_intercept' functions exist. This ensures that all necessary functions for 'rayaop' are loaded and available for use. --- tests/000-rayaop-loaded.phpt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/000-rayaop-loaded.phpt b/tests/000-rayaop-loaded.phpt index 3f2bf4c..c21ce52 100644 --- a/tests/000-rayaop-loaded.phpt +++ b/tests/000-rayaop-loaded.phpt @@ -4,6 +4,8 @@ RayAOP extension is loaded @@ -11,3 +13,5 @@ var_dump(interface_exists('Ray\\Aop\\MethodInterceptorInterface')); bool(true) bool(true) bool(true) +bool(true) +bool(true) From 7ba055ed74190b836677ddd7e157b4196911e81b Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Fri, 12 Jul 2024 06:55:56 +0900 Subject: [PATCH 10/43] Update build workflow to include a custom php.ini Added a step in the GitHub actions build workflow to create a custom php.ini file and modified the tests run step to use this custom php.ini. This ensures any required PHP extensions, like 'rayaop.so', are loaded for the test environment. --- .github/workflows/build.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c3103eb..85ddffb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,9 +34,13 @@ jobs: ./configure make + - name: Create php.ini + run: | + echo "extension=$(pwd)/modules/rayaop.so" > php.ini + - name: Run tests id: run_tests - run: make test + run: PHP_INI_SCAN_DIR=$(pwd) make test continue-on-error: true - name: Run demo with debug logging From 55b778cd7ddf357d9a861e70da942d4fa8524697 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Fri, 12 Jul 2024 07:00:17 +0900 Subject: [PATCH 11/43] Disable RAYAOP_DEBUG in rayaop.c The code change comments out the RAYAOP_DEBUG definition in rayaop.c. This disables the debug mode for RayAOP extension, presumably for production use where debug information is not required or desired. --- rayaop.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rayaop.c b/rayaop.c index 8aba944..fad8d1e 100644 --- a/rayaop.c +++ b/rayaop.c @@ -8,7 +8,7 @@ #define RAYAOP_QUIET 0 #endif -#define RAYAOP_DEBUG +// #define RAYAOP_DEBUG #include "php_rayaop.h" /* Include header file for RayAOP extension */ From ceff605443d07d3ccd5c417f9acd8a9f05a507f9 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Thu, 18 Jul 2024 11:48:39 +0900 Subject: [PATCH 12/43] Add AddressSanitizer flags and paths to config.m4 This commit introduces AddressSanitizer support to the project by adding necessary flags and paths to the config.m4 file. It incorporates AddressSanitizer library with the PHP extension and updates the compiler and linker flags. It also adds specific include and library paths necessary for the AddressSanitizer functionality. --- config.m4 | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/config.m4 b/config.m4 index 4244685..91f0a9f 100644 --- a/config.m4 +++ b/config.m4 @@ -41,4 +41,17 @@ if test "$PHP_RAYAOP" != "no"; then if test "$PHP_RAYAOP_QUIET" != "no"; then AC_DEFINE(RAYAOP_QUIET, 1, [Whether to suppress experimental notices]) fi + + dnl Add AddressSanitizer flags + dnl Add the AddressSanitizer runtime library path + PHP_ADD_LIBRARY_WITH_PATH(clang_rt.asan_osx_dynamic, /opt/homebrew/opt/llvm/lib/clang/14.0.0/lib/darwin, RAYAOP_SHARED_LIBADD) + AC_DEFINE(HAVE_ASAN, 1, [Define if you have AddressSanitizer]) + + dnl Add compiler and linker flags + CFLAGS="$CFLAGS -fsanitize=address" + LDFLAGS="$LDFLAGS -fsanitize=address" + + dnl Add include and library paths + PHP_ADD_INCLUDE(/opt/homebrew/opt/llvm/include) + PHP_ADD_LIBRARY_DIR(/opt/homebrew/opt/llvm/lib) fi From f7525e7ac04daf31f28cb499ae3b0ffe0912f64d Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Thu, 18 Jul 2024 11:49:03 +0900 Subject: [PATCH 13/43] Update AddressSanitizer flags and paths in config.m4 The changes revolve around the AddressSanitizer settings in the config.m4 file. The library paths and include directories have been altered and encompassed within square brackets for consistency. Moreover, the compiler and linker flags have been updated, which include "-g -O0" for debug information and to disable optimizations. --- config.m4 | 10 +++++----- php_rayaop.h | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/config.m4 b/config.m4 index 91f0a9f..aedefcd 100644 --- a/config.m4 +++ b/config.m4 @@ -44,14 +44,14 @@ if test "$PHP_RAYAOP" != "no"; then dnl Add AddressSanitizer flags dnl Add the AddressSanitizer runtime library path - PHP_ADD_LIBRARY_WITH_PATH(clang_rt.asan_osx_dynamic, /opt/homebrew/opt/llvm/lib/clang/14.0.0/lib/darwin, RAYAOP_SHARED_LIBADD) + PHP_ADD_LIBRARY_WITH_PATH([clang_rt.asan_osx_dynamic], [/opt/homebrew/opt/llvm/lib/clang/14.0.0/lib/darwin], [RAYAOP_SHARED_LIBADD]) AC_DEFINE(HAVE_ASAN, 1, [Define if you have AddressSanitizer]) dnl Add compiler and linker flags - CFLAGS="$CFLAGS -fsanitize=address" - LDFLAGS="$LDFLAGS -fsanitize=address" + CFLAGS="-g -O0 -fsanitize=address $CFLAGS" + LDFLAGS="-fsanitize=address $LDFLAGS" dnl Add include and library paths - PHP_ADD_INCLUDE(/opt/homebrew/opt/llvm/include) - PHP_ADD_LIBRARY_DIR(/opt/homebrew/opt/llvm/lib) + PHP_ADD_INCLUDE([/opt/homebrew/opt/llvm/include]) + PHP_ADD_LIBPATH([/opt/homebrew/opt/llvm/lib]) fi diff --git a/php_rayaop.h b/php_rayaop.h index c019aff..ed69f66 100644 --- a/php_rayaop.h +++ b/php_rayaop.h @@ -7,7 +7,7 @@ #include "config.h" #endif -#include "php.h" /* Include PHP core header file */ +// #include "php.h" /* Include PHP core header file */ #include "php_ini.h" /* Include PHP INI related header file */ #include "ext/standard/info.h" /* Include standard extension module information related header */ #include "zend_exceptions.h" /* Include Zend exception handling related header */ From 0b14f8a5675d8584c3b553618d12caeb3ca70e76 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 5 Nov 2024 01:31:44 +0900 Subject: [PATCH 14/43] Refactor PHP extension: enhance structure and add features Streamlined header definitions and cleaned unused comments for better readability. Introduced new features like error codes, thread safety macros, debug printing, and globals initialization. Improved memory and error handling, structured module functions, and implemented a maximum execution depth check to prevent infinite recursion. --- php_rayaop.h | 151 ++++++----- rayaop.c | 733 ++++++++++++++++++--------------------------------- 2 files changed, 340 insertions(+), 544 deletions(-) diff --git a/php_rayaop.h b/php_rayaop.h index ed69f66..67ecb3f 100644 --- a/php_rayaop.h +++ b/php_rayaop.h @@ -1,100 +1,125 @@ -/* If PHP_RAYAOP_H is not defined, then define PHP_RAYAOP_H (header guard) */ +/* Header guard */ #ifndef PHP_RAYAOP_H #define PHP_RAYAOP_H -/* If HAVE_CONFIG_H is defined, then include config.h */ +/* Configuration header */ #ifdef HAVE_CONFIG_H #include "config.h" #endif -// #include "php.h" /* Include PHP core header file */ -#include "php_ini.h" /* Include PHP INI related header file */ -#include "ext/standard/info.h" /* Include standard extension module information related header */ -#include "zend_exceptions.h" /* Include Zend exception handling related header */ -#include "zend_interfaces.h" /* Include Zend interface related header */ +/* Required system headers first */ +#include + +/* PHP Core headers in correct order */ +#include "php.h" +#include "zend.h" +#include "zend_types.h" +#include "zend_API.h" +#include "zend_ini.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "zend_exceptions.h" +#include "zend_interfaces.h" -/* If in thread-safe mode, then include Thread Safe Resource Manager */ #ifdef ZTS #include "TSRM.h" #endif -/* Define RayAOP namespace */ +/* Version and namespace definitions */ +#define PHP_RAYAOP_VERSION "1.0.0" #define RAYAOP_NS "Ray\\Aop\\" -/* Declare rayaop module entry as external reference */ -extern zend_module_entry rayaop_module_entry; +/* Error codes */ +#define RAYAOP_E_MEMORY_ALLOCATION 1 +#define RAYAOP_E_HASH_UPDATE 2 +#define RAYAOP_E_INVALID_HANDLER 3 +#define RAYAOP_E_MAX_DEPTH_EXCEEDED 4 + +/* Debug mode configuration */ +#ifdef RAYAOP_DEBUG +#define PHP_RAYAOP_DEBUG_PRINT(fmt, ...) \ + do { \ + php_printf("RAYAOP DEBUG [%s:%d]: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); \ + } while (0) +#else +#define PHP_RAYAOP_DEBUG_PRINT(fmt, ...) +#endif + +/* Thread safety macros */ +#ifdef ZTS +#define RAYAOP_G_LOCK() tsrm_mutex_lock(rayaop_globals_id) +#define RAYAOP_G_UNLOCK() tsrm_mutex_unlock(rayaop_globals_id) +#else +#define RAYAOP_G_LOCK() +#define RAYAOP_G_UNLOCK() +#endif -/* Define pointer to rayaop module */ +/* Module entry */ +extern zend_module_entry rayaop_module_entry; #define phpext_rayaop_ptr &rayaop_module_entry -/* If in Windows environment, specify DLL export */ +/* Windows DLL export */ #ifdef PHP_WIN32 #define PHP_RAYAOP_API __declspec(dllexport) -/* If using GCC 4 or later, specify default visibility */ #elif defined(__GNUC__) && __GNUC__ >= 4 #define PHP_RAYAOP_API __attribute__ ((visibility("default"))) -/* For other environments, no specific definition is made */ #else #define PHP_RAYAOP_API #endif -/* If in thread-safe mode, include Thread Safe Resource Manager again (redundant but for safety) */ -#ifdef ZTS -#include "TSRM.h" -#endif - -/* Macro for debug output */ -#ifdef RAYAOP_DEBUG /* If debug mode is enabled */ -#define PHP_RAYAOP_DEBUG_PRINT(fmt, ...) php_printf("RAYAOP DEBUG: " fmt "\n", ##__VA_ARGS__) /* Define debug output macro */ -#else /* If debug mode is disabled */ -#define PHP_RAYAOP_DEBUG_PRINT(fmt, ...) /* Do nothing */ -#endif - -/* Structure to hold intercept information */ +/* Intercept information structure */ typedef struct _php_rayaop_intercept_info { - zend_string *class_name; /* Class name to intercept */ - zend_string *method_name; /* Method name to intercept */ - zval handler; /* Intercept handler */ + zend_string *class_name; /* Class name to intercept */ + zend_string *method_name; /* Method name to intercept */ + zval handler; /* Intercept handler */ + zend_bool is_enabled; /* Flag to enable/disable interception */ } php_rayaop_intercept_info; -/* Function declarations */ -PHP_MINIT_FUNCTION(rayaop); /* Module initialization function */ -PHP_MSHUTDOWN_FUNCTION(rayaop); /* Module shutdown function */ -PHP_RINIT_FUNCTION(rayaop); /* Request initialization function */ -PHP_RSHUTDOWN_FUNCTION(rayaop); /* Request shutdown function */ -PHP_MINFO_FUNCTION(rayaop); /* Module information function */ -PHP_FUNCTION(method_intercept); /* Method intercept function */ -PHP_FUNCTION(method_intercept_init); /* Add this line for the new function */ - -/* Utility function declarations */ -void php_rayaop_handle_error(const char *message); /* Error handling function */ -bool php_rayaop_should_intercept(zend_execute_data *execute_data); /* Function to determine if interception is necessary */ -char *php_rayaop_generate_intercept_key(zend_string *class_name, zend_string *method_name, size_t *key_len); /* Function to generate intercept key */ -php_rayaop_intercept_info *php_rayaop_find_intercept_info(const char *key, size_t key_len); /* Function to search for intercept information */ -void php_rayaop_execute_intercept(zend_execute_data *execute_data, php_rayaop_intercept_info *info); /* Function to execute interception */ -void php_rayaop_free_intercept_info(zval *zv); /* Function to free intercept information */ - -#ifdef RAYAOP_DEBUG /* If debug mode is enabled */ -void php_rayaop_debug_print_zval(zval *value); /* Function to debug print zval value */ -static void php_rayaop_dump_intercept_info(void); /* Function to dump intercept information */ -#endif - +/* Module globals structure */ ZEND_BEGIN_MODULE_GLOBALS(rayaop) - /* Start of rayaop module global variables */ - HashTable *intercept_ht; /* Intercept hash table */ - zend_bool is_intercepting; /* Intercepting flag */ - int execution_depth; - zend_bool method_intercept_enabled; -ZEND_END_MODULE_GLOBALS(rayaop) /* End of rayaop module global variables */ - -/* If in thread-safe mode, global variable access macro (thread-safe version) */ + HashTable *intercept_ht; /* Intercept hash table */ + zend_bool is_intercepting; /* Intercepting flag */ + uint32_t execution_depth; /* Execution depth counter */ + zend_bool method_intercept_enabled; /* Global interception enable flag */ + uint32_t debug_level; /* Debug level */ +ZEND_END_MODULE_GLOBALS(rayaop) + +/* Globals access macro */ #ifdef ZTS #define RAYAOP_G(v) TSRMG(rayaop_globals_id, zend_rayaop_globals *, v) -/* If in non-thread-safe mode, global variable access macro (non-thread-safe version) */ #else #define RAYAOP_G(v) (rayaop_globals.v) #endif +/* Module functions */ +PHP_MINIT_FUNCTION(rayaop); +PHP_MSHUTDOWN_FUNCTION(rayaop); +PHP_RINIT_FUNCTION(rayaop); +PHP_RSHUTDOWN_FUNCTION(rayaop); +PHP_MINFO_FUNCTION(rayaop); + +/* Extension functions */ +PHP_FUNCTION(method_intercept); +PHP_FUNCTION(method_intercept_init); +PHP_FUNCTION(enable_method_intercept); + +/* Utility functions */ +PHP_RAYAOP_API void php_rayaop_handle_error(int error_code, const char *message); +PHP_RAYAOP_API bool php_rayaop_should_intercept(zend_execute_data *execute_data); +PHP_RAYAOP_API char *php_rayaop_generate_key(zend_string *class_name, zend_string *method_name, size_t *key_len); +PHP_RAYAOP_API php_rayaop_intercept_info *php_rayaop_find_intercept_info(const char *key, size_t key_len); + +/* Memory management functions */ +PHP_RAYAOP_API void php_rayaop_free_intercept_info(zval *zv); +PHP_RAYAOP_API php_rayaop_intercept_info *php_rayaop_create_intercept_info(void); + +/* Debug functions */ +#ifdef RAYAOP_DEBUG +void php_rayaop_debug_dump_intercept_info(void); +void php_rayaop_debug_print_zval(zval *value); +#endif + +/* Module globals declaration */ ZEND_EXTERN_MODULE_GLOBALS(rayaop) -#endif /* End of header guard */ +#endif /* PHP_RAYAOP_H */ \ No newline at end of file diff --git a/rayaop.c b/rayaop.c index fad8d1e..9fd825b 100644 --- a/rayaop.c +++ b/rayaop.c @@ -1,618 +1,389 @@ #ifdef HAVE_CONFIG_H -#include "config.h" /* Include configuration file */ +#include "config.h" #endif -#define PHP_RAYAOP_VERSION "0.1.0" +#include "php_rayaop.h" -#ifndef RAYAOP_QUIET -#define RAYAOP_QUIET 0 -#endif - -// #define RAYAOP_DEBUG - -#include "php_rayaop.h" /* Include header file for RayAOP extension */ - -/* Declaration of module global variables */ +/* Module globals initialization */ ZEND_DECLARE_MODULE_GLOBALS(rayaop) -/* Declaration of static variable: pointer to the original zend_execute_ex function */ -static void (*php_rayaop_original_execute_ex)(zend_execute_data *execute_data); - -/* {{{ proto void php_rayaop_init_globals(zend_rayaop_globals *rayaop_globals) - Global initialization function +/* Original zend_execute_ex function pointer */ +static void (*php_rayaop_original_execute_ex)(zend_execute_data *execute_data) = NULL; - This function initializes the global variables for the RayAOP extension. +/* Maximum execution depth to prevent infinite recursion */ +#define MAX_EXECUTION_DEPTH 100 - @param zend_rayaop_globals *rayaop_globals Pointer to global variables -*/ -static void php_rayaop_init_globals(zend_rayaop_globals *rayaop_globals) { - rayaop_globals->intercept_ht = NULL; /* Initialize intercept hash table */ - rayaop_globals->is_intercepting = 0; /* Initialize intercept flag */ - rayaop_globals->execution_depth = 0; +/* Module globals initializer */ +static void php_rayaop_init_globals(zend_rayaop_globals *globals) { + globals->intercept_ht = NULL; + globals->is_intercepting = 0; + globals->execution_depth = 0; + globals->method_intercept_enabled = 0; + globals->debug_level = 0; } -/* }}} */ -/* Macro for debug output */ -#ifdef RAYAOP_DEBUG -#define PHP_RAYAOP_DEBUG_PRINT(fmt, ...) php_printf("RAYAOP DEBUG: " fmt "\n", ##__VA_ARGS__) /* Output debug information */ -#else -#define PHP_RAYAOP_DEBUG_PRINT(fmt, ...) /* Do nothing if not in debug mode */ -#endif - -/* Argument information for method_intercept function */ -ZEND_BEGIN_ARG_INFO_EX(arginfo_method_intercept, 0, 0, 3) - ZEND_ARG_TYPE_INFO(0, class_name, IS_STRING, 0) /* Argument information for class name */ - ZEND_ARG_TYPE_INFO(0, method_name, IS_STRING, 0) /* Argument information for method name */ - ZEND_ARG_OBJ_INFO(0, interceptor, Ray\\Aop\\MethodInterceptorInterface, 0) /* Argument information for intercept handler */ -ZEND_END_ARG_INFO() - -/* Argument information for method_intercept_init function */ -ZEND_BEGIN_ARG_INFO(arginfo_method_intercept_init, 0) -ZEND_END_ARG_INFO() - -/* Argument information for method_intercept_enable function */ -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_enable_method_intercept, 0, 1, IS_VOID, 0) - ZEND_ARG_TYPE_INFO(0, enable, _IS_BOOL, 0) -ZEND_END_ARG_INFO() +/* Error code handling */ +static const char *php_rayaop_get_error_message(int error_code) { + switch (error_code) { + case RAYAOP_E_MEMORY_ALLOCATION: + return "Memory allocation failed"; + case RAYAOP_E_HASH_UPDATE: + return "Hash table update failed"; + case RAYAOP_E_INVALID_HANDLER: + return "Invalid handler"; + case RAYAOP_E_MAX_DEPTH_EXCEEDED: + return "Maximum execution depth exceeded"; + default: + return "Unknown error"; + } +} -/* {{{ proto void php_rayaop_handle_error(const char *message) - Error handling function +/* Error handling function */ +PHP_RAYAOP_API void php_rayaop_handle_error(int error_code, const char *message) { + const char *error_type = php_rayaop_get_error_message(error_code); + if (message) { + php_error_docref(NULL, E_ERROR, "RayAOP Error (%s): %s", error_type, message); + } else { + php_error_docref(NULL, E_ERROR, "RayAOP Error: %s", error_type); + } +} - This function handles errors by outputting an error message. +/* Memory management functions */ +PHP_RAYAOP_API php_rayaop_intercept_info *php_rayaop_create_intercept_info(void) { + php_rayaop_intercept_info *info = ecalloc(1, sizeof(php_rayaop_intercept_info)); + if (!info) { + php_rayaop_handle_error(RAYAOP_E_MEMORY_ALLOCATION, "Failed to allocate intercept info"); + return NULL; + } + info->is_enabled = 1; + return info; +} - @param const char *message Error message to be displayed -*/ -void php_rayaop_handle_error(const char *message) { - php_error_docref(NULL, E_ERROR, "Memory error: %s", message); /* Output error message */ +PHP_RAYAOP_API void php_rayaop_free_intercept_info(zval *zv) { + RAYAOP_G_LOCK(); + php_rayaop_intercept_info *info = Z_PTR_P(zv); + if (info) { + if (info->class_name) { + zend_string_release(info->class_name); + } + if (info->method_name) { + zend_string_release(info->method_name); + } + zval_ptr_dtor(&info->handler); + efree(info); + } + RAYAOP_G_UNLOCK(); } -/* }}} */ -/* {{{ proto bool php_rayaop_should_intercept(zend_execute_data *execute_data) - Function to determine if interception is necessary +/* Interception helper functions */ +PHP_RAYAOP_API bool php_rayaop_should_intercept(zend_execute_data *execute_data) { + if (!RAYAOP_G(method_intercept_enabled)) { + return false; + } - This function checks if the current execution should be intercepted. + if (RAYAOP_G(execution_depth) >= MAX_EXECUTION_DEPTH) { + php_rayaop_handle_error(RAYAOP_E_MAX_DEPTH_EXCEEDED, NULL); + return false; + } - @param zend_execute_data *execute_data Execution data - @return bool Returns true if interception is necessary, false otherwise -*/ -bool php_rayaop_should_intercept(zend_execute_data *execute_data) { - return execute_data->func->common.scope && /* Scope exists */ - execute_data->func->common.function_name && /* Function name exists */ - !RAYAOP_G(is_intercepting); /* Not currently intercepting */ + return execute_data && + execute_data->func && + execute_data->func->common.scope && + execute_data->func->common.function_name && + !RAYAOP_G(is_intercepting); } -/* }}} */ - -/* {{{ proto char* php_rayaop_generate_intercept_key(zend_string *class_name, zend_string *method_name, size_t *key_len) - Function to generate intercept key - This function creates a key in the format "class_name::method_name" for use in the intercept hash table. - - @param zend_string *class_name The name of the class - @param zend_string *method_name The name of the method - @param size_t *key_len Pointer to store the length of the generated key - @return char* The generated key, which must be freed by the caller using efree() -*/ -char *php_rayaop_generate_intercept_key(zend_string *class_name, zend_string *method_name, size_t *key_len) { +PHP_RAYAOP_API char *php_rayaop_generate_key(zend_string *class_name, zend_string *method_name, size_t *key_len) { char *key = NULL; *key_len = spprintf(&key, 0, "%s::%s", ZSTR_VAL(class_name), ZSTR_VAL(method_name)); - /* Generate key in the format class_name::method_name */ - PHP_RAYAOP_DEBUG_PRINT("Generated key: %s", key); /* Output generated key for debugging */ + PHP_RAYAOP_DEBUG_PRINT("Generated key: %s", key); return key; } -/* }}} */ - -/* {{{ proto php_rayaop_intercept_info* php_rayaop_find_intercept_info(const char *key, size_t key_len) - Function to search for intercept information - This function searches for intercept information in the hash table using the provided key. - - @param const char *key The key to search for - @param size_t key_len The length of the key - @return php_rayaop_intercept_info* Pointer to the intercept information if found, NULL otherwise -*/ -php_rayaop_intercept_info *php_rayaop_find_intercept_info(const char *key, size_t key_len) { - return zend_hash_str_find_ptr(RAYAOP_G(intercept_ht), key, key_len); - /* Search for intercept information in hash table */ +PHP_RAYAOP_API php_rayaop_intercept_info *php_rayaop_find_intercept_info(const char *key, size_t key_len) { + RAYAOP_G_LOCK(); + php_rayaop_intercept_info *info = NULL; + if (RAYAOP_G(intercept_ht)) { + info = zend_hash_str_find_ptr(RAYAOP_G(intercept_ht), key, key_len); + } + RAYAOP_G_UNLOCK(); + return info; } -/* }}} */ -/* {{{ Helper function to prepare intercept parameters */ +/* Parameter preparation and cleanup */ static void prepare_intercept_params(zend_execute_data *execute_data, zval *params, php_rayaop_intercept_info *info) { + if (!execute_data->This.value.obj) { + php_rayaop_handle_error(RAYAOP_E_INVALID_HANDLER, "Object instance is NULL"); + return; + } + ZVAL_OBJ(¶ms[0], execute_data->This.value.obj); - ZVAL_STR(¶ms[1], info->method_name); + ZVAL_STR_COPY(¶ms[1], info->method_name); array_init(¶ms[2]); uint32_t arg_count = ZEND_CALL_NUM_ARGS(execute_data); - zval *args = ZEND_CALL_ARG(execute_data, 1); - for (uint32_t i = 0; i < arg_count; i++) { - zval *arg = &args[i]; - Z_TRY_ADDREF_P(arg); - add_next_index_zval(¶ms[2], arg); + if (arg_count > 0) { + zval *args = ZEND_CALL_ARG(execute_data, 1); + for (uint32_t i = 0; i < arg_count; i++) { + zval *arg = &args[i]; + if (!Z_ISUNDEF_P(arg)) { + Z_TRY_ADDREF_P(arg); + add_next_index_zval(¶ms[2], arg); + } + } } } -/* }}} */ - -/* {{{ Helper function to call the intercept handler */ -static bool call_intercept_handler(zval *handler, zval *params, zval *retval) { - zval func_name; - ZVAL_STRING(&func_name, "intercept"); - - bool success = (call_user_function(NULL, handler, &func_name, retval, 3, params) == SUCCESS); - - zval_ptr_dtor(&func_name); - return success; -} -/* }}} */ -/* {{{ Helper function to clean up after interception */ -static void cleanup_intercept(zval *params, zval *retval) { +static void cleanup_intercept_params(zval *params) { zval_ptr_dtor(¶ms[1]); zval_ptr_dtor(¶ms[2]); - zval_ptr_dtor(retval); } -/* }}} */ -/* {{{ proto void php_rayaop_execute_intercept(zend_execute_data *execute_data, php_rayaop_intercept_info *info) - Main function to execute method interception */ -void php_rayaop_execute_intercept(zend_execute_data *execute_data, php_rayaop_intercept_info *info) { - PHP_RAYAOP_DEBUG_PRINT("Executing intercept for %s::%s", ZSTR_VAL(info->class_name), ZSTR_VAL(info->method_name)); +/* Interception execution */ +static bool execute_intercept_handler(zval *handler, zval *params, zval *retval) { + zval method_name; + ZVAL_STRING(&method_name, "intercept"); - if (Z_TYPE(info->handler) != IS_OBJECT) { - PHP_RAYAOP_DEBUG_PRINT("Invalid handler, skipping interception"); - return; - } + bool success = (call_user_function(NULL, handler, &method_name, retval, 3, params) == SUCCESS); + zval_ptr_dtor(&method_name); - if (execute_data->This.value.obj == NULL) { - PHP_RAYAOP_DEBUG_PRINT("Object is NULL, calling original function"); - php_rayaop_original_execute_ex(execute_data); - return; - } - - zval retval, params[3]; - prepare_intercept_params(execute_data, params, info); - - RAYAOP_G(is_intercepting) = 1; - - ZVAL_UNDEF(&retval); - if (call_intercept_handler(&info->handler, params, &retval)) { - if (!Z_ISUNDEF(retval)) { - if (execute_data->return_value) { - ZVAL_COPY(execute_data->return_value, &retval); - } else { - PHP_RAYAOP_DEBUG_PRINT("Warning: execute_data->return_value is NULL"); - } - } - } else { - php_error_docref(NULL, E_WARNING, "Interception failed for %s::%s", ZSTR_VAL(info->class_name), ZSTR_VAL(info->method_name)); + if (!success) { + php_rayaop_handle_error(RAYAOP_E_INVALID_HANDLER, "Failed to execute intercept handler"); } - cleanup_intercept(params, &retval); - RAYAOP_G(is_intercepting) = 0; - - PHP_RAYAOP_DEBUG_PRINT("Interception completed for %s::%s", ZSTR_VAL(info->class_name), ZSTR_VAL(info->method_name)); + return success; } -/* }}} */ - -/* {{{ proto void php_rayaop_free_intercept_info(zval *zv) - Function to free intercept information - - This function frees the memory allocated for intercept information. - - @param zval *zv The zval containing the intercept information to be freed -*/ -void php_rayaop_free_intercept_info(zval *zv) { - php_rayaop_intercept_info *info = Z_PTR_P(zv); /* Get intercept information pointer from zval */ - if (info) { - /* If intercept information exists */ - PHP_RAYAOP_DEBUG_PRINT("Freeing intercept info for %s::%s", ZSTR_VAL(info->class_name), ZSTR_VAL(info->method_name)); - /* Output debug information */ - zend_string_release(info->class_name); /* Free memory for class name */ - zend_string_release(info->method_name); /* Free memory for method name */ - zval_ptr_dtor(&info->handler); /* Free memory for handler */ - efree(info); /* Free memory for intercept information structure */ +/* Core interception execution function */ +static void rayaop_execute_ex(zend_execute_data *execute_data) { + if (!php_rayaop_should_intercept(execute_data)) { + php_rayaop_original_execute_ex(execute_data); + return; } -} -/* }}} */ - -/* {{{ proto void php_rayaop_execute_ex(zend_execute_data *execute_data) - Custom zend_execute_ex function - This function replaces the original zend_execute_ex function to implement method interception. - - @param zend_execute_data *execute_data The execution data -*/ - -static void php_rayaop_execute_ex(zend_execute_data *execute_data) { - PHP_RAYAOP_DEBUG_PRINT("php_rayaop_execute_ex called"); /* Output debug information */ - - // 実行深度を増加させ、ログに記録 RAYAOP_G(execution_depth)++; - PHP_RAYAOP_DEBUG_PRINT("Execution depth: %d", RAYAOP_G(execution_depth)); - if (!php_rayaop_should_intercept(execute_data)) { - PHP_RAYAOP_DEBUG_PRINT("Interception not necessary, calling original execute_ex function"); - - // Debug information about execute_data - if (execute_data == NULL) { - PHP_RAYAOP_DEBUG_PRINT("execute_data is NULL"); - } else { - if (execute_data->func == NULL) { - PHP_RAYAOP_DEBUG_PRINT("execute_data->func is NULL"); - } else { - if (execute_data->func->common.function_name == NULL) { - PHP_RAYAOP_DEBUG_PRINT("execute_data->func->common.function_name is NULL"); - } else { - PHP_RAYAOP_DEBUG_PRINT("Function name: %s", ZSTR_VAL(execute_data->func->common.function_name)); - } - - if (execute_data->func->common.scope == NULL) { - PHP_RAYAOP_DEBUG_PRINT("execute_data->func->common.scope is NULL"); - } else { - PHP_RAYAOP_DEBUG_PRINT("Class name: %s", ZSTR_VAL(execute_data->func->common.scope->name)); - } - } - } - - PHP_RAYAOP_DEBUG_PRINT("Calling original execute_ex function"); - - if (php_rayaop_original_execute_ex == NULL) { - PHP_RAYAOP_DEBUG_PRINT("php_rayaop_original_execute_ex is NULL"); - } - - php_rayaop_original_execute_ex(execute_data); /* Call the original execution function */ - RAYAOP_G(execution_depth)--; - - return; /* End processing */ - } - - zend_function *current_function = execute_data->func; /* Get current function information */ - zend_string *class_name = current_function->common.scope->name; /* Get class name */ - zend_string *method_name = current_function->common.function_name; /* Get method name */ - - PHP_RAYAOP_DEBUG_PRINT("Function: %s::%s", ZSTR_VAL(class_name), ZSTR_VAL(method_name)); - + zend_function *func = execute_data->func; size_t key_len; - char *key = php_rayaop_generate_intercept_key(class_name, method_name, &key_len); /* Generate intercept key */ - - php_rayaop_intercept_info *info = php_rayaop_find_intercept_info(key, key_len); /* Search for intercept information */ + char *key = php_rayaop_generate_key(func->common.scope->name, func->common.function_name, &key_len); - if (info) { - /* If intercept information is found */ - PHP_RAYAOP_DEBUG_PRINT("Found intercept info for key: %s", key); /* Output debug information */ - php_rayaop_execute_intercept(execute_data, info); /* Execute interception */ - } else { - /* If intercept information is not found */ - PHP_RAYAOP_DEBUG_PRINT("No intercept info found for key: %s", key); /* Output debug information */ - PHP_RAYAOP_DEBUG_PRINT("Calling original execute_ex function"); + php_rayaop_intercept_info *info = php_rayaop_find_intercept_info(key, key_len); + if (info && info->is_enabled) { + PHP_RAYAOP_DEBUG_PRINT("Executing intercept for %s", key); - // Debug information about execute_data before calling the original function - if (execute_data == NULL) { - PHP_RAYAOP_DEBUG_PRINT("execute_data is NULL"); + if (Z_TYPE(info->handler) != IS_OBJECT) { + php_rayaop_handle_error(RAYAOP_E_INVALID_HANDLER, "Invalid interceptor type"); + php_rayaop_original_execute_ex(execute_data); } else { - if (execute_data->func == NULL) { - PHP_RAYAOP_DEBUG_PRINT("execute_data->func is NULL"); - } else { - if (execute_data->func->common.function_name == NULL) { - PHP_RAYAOP_DEBUG_PRINT("execute_data->func->common.function_name is NULL"); - } else { - PHP_RAYAOP_DEBUG_PRINT("Function name: %s", ZSTR_VAL(execute_data->func->common.function_name)); - } + zval retval; + zval params[3]; - if (execute_data->func->common.scope == NULL) { - PHP_RAYAOP_DEBUG_PRINT("execute_data->func->common.scope is NULL"); - } else { - PHP_RAYAOP_DEBUG_PRINT("Class name: %s", ZSTR_VAL(execute_data->func->common.scope->name)); + prepare_intercept_params(execute_data, params, info); + RAYAOP_G(is_intercepting) = 1; + + ZVAL_UNDEF(&retval); + if (execute_intercept_handler(&info->handler, params, &retval)) { + if (!Z_ISUNDEF(retval)) { + ZVAL_COPY(execute_data->return_value, &retval); } + zval_ptr_dtor(&retval); } - } - if (php_rayaop_original_execute_ex == NULL) { - PHP_RAYAOP_DEBUG_PRINT("php_rayaop_original_execute_ex is NULL"); + cleanup_intercept_params(params); + RAYAOP_G(is_intercepting) = 0; } - PHP_RAYAOP_DEBUG_PRINT("Origilan method calling..."); - if (execute_data == NULL || execute_data->func == NULL || php_rayaop_original_execute_ex == NULL) { - PHP_RAYAOP_DEBUG_PRINT("Invalid pointers detected"); - // エラー処理 - return; - } - PHP_RAYAOP_DEBUG_PRINT("Calling original execute_ex. execute_data: %p, func: %p, original_execute_ex: %p", - (void*)execute_data, - (void*)(execute_data ? execute_data->func : NULL), - (void*)php_rayaop_original_execute_ex); + } else { php_rayaop_original_execute_ex(execute_data); - /* Call the original execution function */ - PHP_RAYAOP_DEBUG_PRINT("Origilan method called sucsessfully!"); - } - efree(key); /* Free memory for key */ + efree(key); RAYAOP_G(execution_depth)--; } -/* }}} */ - -/* {{{ proto void php_rayaop_hash_update_failed(php_rayaop_intercept_info *new_info, char *key) - Handling for hash table update failure - - This function handles the case when updating the intercept hash table fails. - - @param php_rayaop_intercept_info *new_info The new intercept information that failed to be added - @param char *key The key that was used for the failed update -*/ -void php_rayaop_hash_update_failed(php_rayaop_intercept_info *new_info, char *key) { - php_rayaop_handle_error("Failed to update intercept hash table"); /* Output error message */ - zend_string_release(new_info->class_name); /* Free memory for class name */ - zend_string_release(new_info->method_name); /* Free memory for method name */ - zval_ptr_dtor(&new_info->handler); /* Free memory for handler */ - efree(new_info); /* Free memory for intercept information structure */ - efree(key); /* Free memory for key */ -} -/* }}} */ +/* Method interceptor interface arguments */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_method_intercept, 0, 0, 3) + ZEND_ARG_TYPE_INFO(0, class_name, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, method_name, IS_STRING, 0) + ZEND_ARG_OBJ_INFO(0, interceptor, Ray\\Aop\\MethodInterceptorInterface, 0) +ZEND_END_ARG_INFO() -/* {{{ proto bool method_intercept(string class_name, string method_name, object intercepted) - Function to register intercept method +ZEND_BEGIN_ARG_INFO(arginfo_method_intercept_init, 0) +ZEND_END_ARG_INFO() - This function registers an interceptor for a specified class method. +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_enable_method_intercept, 0, 1, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, enable, _IS_BOOL, 0) +ZEND_END_ARG_INFO() - @param string class_name The name of the class - @param string method_name The name of the method - @param object intercepted The interceptor object - @return bool Returns TRUE on success or FALSE on failure -*/ +/* Implementation of method_intercept function */ PHP_FUNCTION(method_intercept) { - PHP_RAYAOP_DEBUG_PRINT("method_intercept called"); /* Output debug information */ - - char *class_name, *method_name; /* Pointers for class name and method name */ - size_t class_name_len, method_name_len; /* Lengths of class name and method name */ - zval *intercepted; /* Intercept handler */ + char *class_name, *method_name; + size_t class_name_len, method_name_len; + zval *interceptor; ZEND_PARSE_PARAMETERS_START(3, 3) - Z_PARAM_STRING(class_name, class_name_len) /* Parse class name parameter */ - Z_PARAM_STRING(method_name, method_name_len) /* Parse method name parameter */ - Z_PARAM_OBJECT(intercepted) /* Parse intercept handler parameter */ + Z_PARAM_STRING(class_name, class_name_len) + Z_PARAM_STRING(method_name, method_name_len) + Z_PARAM_OBJECT(interceptor) ZEND_PARSE_PARAMETERS_END(); - php_rayaop_intercept_info *new_info = ecalloc(1, sizeof(php_rayaop_intercept_info)); /* Allocate memory for new intercept information */ - if (!new_info) { - /* If memory allocation fails */ - php_rayaop_handle_error("Failed to allocate memory for intercept_info"); /* Output error message */ - RETURN_FALSE; /* Return false and end */ + if (!RAYAOP_G(method_intercept_enabled)) { + php_error_docref(NULL, E_WARNING, "Method interception is currently disabled"); + RETURN_FALSE; } - new_info->class_name = zend_string_init(class_name, class_name_len, 0); /* Initialize class name */ - new_info->method_name = zend_string_init(method_name, method_name_len, 0); /* Initialize method name */ - ZVAL_COPY(&new_info->handler, intercepted); /* Copy intercept handler */ + php_rayaop_intercept_info *info = php_rayaop_create_intercept_info(); + if (!info) { + RETURN_FALSE; + } - char *key = NULL; - size_t key_len = spprintf(&key, 0, "%s::%s", class_name, method_name); /* Generate intercept key */ + info->class_name = zend_string_init(class_name, class_name_len, 0); + info->method_name = zend_string_init(method_name, method_name_len, 0); + ZVAL_COPY(&info->handler, interceptor); - if (zend_hash_str_update_ptr(RAYAOP_G(intercept_ht), key, key_len, new_info) == NULL) { - /* Add to hash table */ - php_rayaop_hash_update_failed(new_info, key); /* Execute error handling if addition fails */ -RETURN_FALSE; /* Return false and end */ + char *key; + size_t key_len; + key = php_rayaop_generate_key(info->class_name, info->method_name, &key_len); + + RAYAOP_G_LOCK(); + if (zend_hash_str_update_ptr(RAYAOP_G(intercept_ht), key, key_len, info) == NULL) { + RAYAOP_G_UNLOCK(); + zend_string_release(info->class_name); + zend_string_release(info->method_name); + zval_ptr_dtor(&info->handler); + efree(info); + efree(key); + php_rayaop_handle_error(RAYAOP_E_HASH_UPDATE, "Failed to update intercept hash table"); + RETURN_FALSE; } + RAYAOP_G_UNLOCK(); - efree(key); /* Free memory for key */ - PHP_RAYAOP_DEBUG_PRINT("Successfully registered intercept info"); /* Output debug information */ - RETURN_TRUE; /* Return true and end */ + efree(key); + RETURN_TRUE; } -/* }}} */ -/* {{{ proto void method_intercept_init() - Function to reset intercept table - - This function clears the intercept hash table, ensuring it starts empty. - -*/ +/* Implementation of method_intercept_init function */ PHP_FUNCTION(method_intercept_init) { - PHP_RAYAOP_DEBUG_PRINT("method_intercept_init called"); /* Output debug information */ + RAYAOP_G_LOCK(); if (RAYAOP_G(intercept_ht)) { - zend_hash_clean(RAYAOP_G(intercept_ht)); /* Clear hash table */ + zend_hash_clean(RAYAOP_G(intercept_ht)); } else { - ALLOC_HASHTABLE(RAYAOP_G(intercept_ht)); /* Allocate memory for hash table if not already allocated */ - zend_hash_init(RAYAOP_G(intercept_ht), 8, NULL, php_rayaop_free_intercept_info, 0); /* Initialize hash table */ + ALLOC_HASHTABLE(RAYAOP_G(intercept_ht)); + zend_hash_init(RAYAOP_G(intercept_ht), 8, NULL, php_rayaop_free_intercept_info, 0); } - RETURN_TRUE; /* Return true to indicate success */ + RAYAOP_G_UNLOCK(); + RETURN_TRUE; } -PHP_FUNCTION(enable_method_intercept) -{ +/* Implementation of enable_method_intercept function */ +PHP_FUNCTION(enable_method_intercept) { zend_bool enable; - ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_BOOL(enable) ZEND_PARSE_PARAMETERS_END(); + RAYAOP_G_LOCK(); RAYAOP_G(method_intercept_enabled) = enable; if (enable) { - zend_execute_ex = php_rayaop_execute_ex; + zend_execute_ex = rayaop_execute_ex; PHP_RAYAOP_DEBUG_PRINT("Method intercept enabled"); } else { zend_execute_ex = php_rayaop_original_execute_ex; PHP_RAYAOP_DEBUG_PRINT("Method intercept disabled"); } + RAYAOP_G_UNLOCK(); } -/* }}} */ - -/* Interface definition */ -zend_class_entry *ray_aop_method_interceptor_interface_ce; -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ray_aop_method_interceptor_intercept, 0, 3, IS_MIXED, 0) - ZEND_ARG_TYPE_INFO(0, object, IS_OBJECT, 0) /* 1st argument: object */ - ZEND_ARG_TYPE_INFO(0, method, IS_STRING, 0) /* 2nd argument: method name */ - ZEND_ARG_TYPE_INFO(0, params, IS_ARRAY, 0) /* 3rd argument: parameter array */ -ZEND_END_ARG_INFO() - -static const zend_function_entry ray_aop_method_interceptor_interface_methods[] = { - ZEND_ABSTRACT_ME(Ray_Aop_MethodInterceptorInterface, intercept, arginfo_ray_aop_method_interceptor_intercept) - /* Define intercept method */ - PHP_FE_END /* End of function entries */ -}; - -/* {{{ proto int PHP_MINIT_FUNCTION(rayaop) - Extension initialization function - - This function is called when the extension is loaded. It initializes the extension, - registers the interface, and sets up the custom execute function. - - @return int Returns SUCCESS on successful initialization -*/ +/* Module initialization */ PHP_MINIT_FUNCTION(rayaop) { - PHP_RAYAOP_DEBUG_PRINT("PHP_MINIT_FUNCTION called"); /* Output debug information */ - #ifdef ZTS - ts_allocate_id(&rayaop_globals_id, sizeof(zend_rayaop_globals), (ts_allocate_ctor) php_rayaop_init_globals, NULL); /* Initialize global variables in thread-safe mode */ + ts_allocate_id(&rayaop_globals_id, sizeof(zend_rayaop_globals), + (ts_allocate_ctor)php_rayaop_init_globals, NULL); #else - php_rayaop_init_globals(&rayaop_globals); /* Initialize global variables in non-thread-safe mode */ -#endif - -#if PHP_RAYAOP_EXPERIMENTAL && !RAYAOP_QUIET - php_error_docref(NULL, E_NOTICE, "The Ray.Aop extension is experimental. Its functions may change or be removed in future releases."); + php_rayaop_init_globals(&rayaop_globals); #endif zend_class_entry ce; - INIT_CLASS_ENTRY(ce, "Ray\\Aop\\MethodInterceptorInterface", ray_aop_method_interceptor_interface_methods); - /* Initialize class entry */ - ray_aop_method_interceptor_interface_ce = zend_register_internal_interface(&ce); /* Register interface */ + INIT_CLASS_ENTRY(ce, "Ray\\Aop\\MethodInterceptorInterface", NULL); + zend_class_entry *interface_ce = zend_register_internal_interface(&ce); - php_rayaop_original_execute_ex = zend_execute_ex; /* Save the original zend_execute_ex function */ - - /** disenable interceptiong */ + php_rayaop_original_execute_ex = zend_execute_ex; RAYAOP_G(method_intercept_enabled) = 0; - PHP_RAYAOP_DEBUG_PRINT("RayAOP extension initialized"); /* Output debug information */ - return SUCCESS; /* Return success */ -} -/* }}} */ - -/* {{{ proto int PHP_MSHUTDOWN_FUNCTION(rayaop) - Extension shutdown function + REGISTER_LONG_CONSTANT("RAYAOP_DEBUG_LEVEL_NONE", 0, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("RAYAOP_DEBUG_LEVEL_BASIC", 1, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("RAYAOP_DEBUG_LEVEL_VERBOSE", 2, CONST_CS | CONST_PERSISTENT); - This function is called when the extension is unloaded. It restores the original - execute function and performs any necessary cleanup. + return SUCCESS; +} - @return int Returns SUCCESS on successful shutdown -*/ +/* Module shutdown */ PHP_MSHUTDOWN_FUNCTION(rayaop) { - PHP_RAYAOP_DEBUG_PRINT("RayAOP PHP_MSHUTDOWN_FUNCTION called"); /* Output debug information */ if (php_rayaop_original_execute_ex) { - PHP_RAYAOP_DEBUG_PRINT("Restoring original execute_ex"); - zend_execute_ex = php_rayaop_original_execute_ex; /* Restore the original zend_execute_ex function */ - php_rayaop_original_execute_ex = NULL; /* Clear the saved pointer */ - } else { - PHP_RAYAOP_DEBUG_PRINT("Original execute_ex was already NULL"); + zend_execute_ex = php_rayaop_original_execute_ex; } - PHP_RAYAOP_DEBUG_PRINT("RayAOP PHP_MSHUTDOWN_FUNCTION shut down"); /* Output debug information */ - return SUCCESS; /* Return shutdown success */ + return SUCCESS; } -/* }}} */ - -/* {{{ proto int PHP_RINIT_FUNCTION(rayaop) - Request initialization function - This function is called at the beginning of each request. It initializes - the intercept hash table for the current request. - - @return int Returns SUCCESS on successful initialization -*/ +/* Request initialization */ PHP_RINIT_FUNCTION(rayaop) { - PHP_RAYAOP_DEBUG_PRINT("PHP_RINIT_FUNCTION called"); /* Output debug information */ - if (RAYAOP_G(intercept_ht) == NULL) { - /* If intercept hash table is not initialized */ - ALLOC_HASHTABLE(RAYAOP_G(intercept_ht)); /* Allocate memory for hash table */ - zend_hash_init(RAYAOP_G(intercept_ht), 8, NULL, php_rayaop_free_intercept_info, 0); /* Initialize hash table */ + RAYAOP_G_LOCK(); + if (!RAYAOP_G(intercept_ht)) { + ALLOC_HASHTABLE(RAYAOP_G(intercept_ht)); + zend_hash_init(RAYAOP_G(intercept_ht), 8, NULL, php_rayaop_free_intercept_info, 0); } - RAYAOP_G(is_intercepting) = 0; /* Initialize intercept flag */ + RAYAOP_G(is_intercepting) = 0; RAYAOP_G(execution_depth) = 0; - return SUCCESS; /* Return success */ + RAYAOP_G_UNLOCK(); + return SUCCESS; } -/* }}} */ -/* {{{ proto int PHP_RSHUTDOWN_FUNCTION(rayaop) - Request shutdown function - - This function is called at the end of each request. It cleans up the - intercept hash table and frees associated memory. - - @return int Returns SUCCESS on successful shutdown -*/ +/* Request shutdown */ PHP_RSHUTDOWN_FUNCTION(rayaop) { - PHP_RAYAOP_DEBUG_PRINT("RayAOP PHP_RSHUTDOWN_FUNCTION called"); /* Output debug information */ + RAYAOP_G_LOCK(); if (RAYAOP_G(intercept_ht)) { - /* If intercept hash table exists */ - zend_hash_destroy(RAYAOP_G(intercept_ht)); /* Destroy hash table */ - FREE_HASHTABLE(RAYAOP_G(intercept_ht)); /* Free memory for hash table */ - RAYAOP_G(intercept_ht) = NULL; /* Set hash table pointer to NULL */ + zend_hash_destroy(RAYAOP_G(intercept_ht)); + FREE_HASHTABLE(RAYAOP_G(intercept_ht)); + RAYAOP_G(intercept_ht) = NULL; } - PHP_RAYAOP_DEBUG_PRINT("RayAOP PHP_RSHUTDOWN_FUNCTION shut down"); /* Output debug information */ - return SUCCESS; /* Return shutdown success */ + RAYAOP_G_UNLOCK(); + return SUCCESS; } -/* }}} */ - -/* {{{ proto void PHP_MINFO_FUNCTION(rayaop) - Extension information display function - This function is called to display information about the extension in phpinfo(). -*/ +/* Module info */ PHP_MINFO_FUNCTION(rayaop) { - php_info_print_table_start(); /* Start information table */ - php_info_print_table_header(2, "rayaop support", "enabled"); /* Display table header */ - php_info_print_table_row(2, "Version", PHP_RAYAOP_VERSION); /* Display version information */ - php_info_print_table_end(); /* End information table */ + php_info_print_table_start(); + php_info_print_table_header(2, "RayAOP Support", "enabled"); + php_info_print_table_row(2, "Version", PHP_RAYAOP_VERSION); + php_info_print_table_row(2, "Debug Level", + RAYAOP_G(debug_level) == 0 ? "None" : + RAYAOP_G(debug_level) == 1 ? "Basic" : "Verbose"); + php_info_print_table_row(2, "Method Intercept", + RAYAOP_G(method_intercept_enabled) ? "Enabled" : "Disabled"); + php_info_print_table_end(); } -/* }}} */ - -#ifdef RAYAOP_DEBUG -/* {{{ proto void php_rayaop_dump_intercept_info(void) - Function to dump intercept information (for debugging) - - This function outputs debug information about the current state of the intercept hash table. -*/ -static void php_rayaop_dump_intercept_info(void) -{ - PHP_RAYAOP_DEBUG_PRINT("Dumping intercept information:"); /* Output debug information */ - if (RAYAOP_G(intercept_ht)) { /* If intercept hash table exists */ - zend_string *key; - php_rayaop_intercept_info *info; - ZEND_HASH_FOREACH_STR_KEY_PTR(RAYAOP_G(intercept_ht), key, info) { /* For each element in the hash table */ - if (key && info) { /* If key and information exist */ - PHP_RAYAOP_DEBUG_PRINT("Key: %s", ZSTR_VAL(key)); /* Output key */ - PHP_RAYAOP_DEBUG_PRINT(" Class: %s", ZSTR_VAL(info->class_name)); /* Output class name */ - PHP_RAYAOP_DEBUG_PRINT(" Method: %s", ZSTR_VAL(info->method_name)); /* Output method name */ - PHP_RAYAOP_DEBUG_PRINT(" Handler type: %d", Z_TYPE(info->handler)); /* Output handler type */ - } - } ZEND_HASH_FOREACH_END(); - } else { /* If intercept hash table does not exist */ - PHP_RAYAOP_DEBUG_PRINT("Intercept hash table is not initialized"); /* Output that it's not initialized */ - } -} -/* }}} */ -#endif -/* Definition of functions provided by the extension */ +/* Extension function entries */ static const zend_function_entry rayaop_functions[] = { - PHP_FE(method_intercept, arginfo_method_intercept) /* Register method_intercept function */ - PHP_FE(method_intercept_init, arginfo_method_intercept_init) /* Register method_intercept_init function */ - PHP_FE(enable_method_intercept, arginfo_enable_method_intercept) /* Register enable_method_intercept function */ - - PHP_FE_END /* End of function entries */ + PHP_FE(method_intercept, arginfo_method_intercept) + PHP_FE(method_intercept_init, arginfo_method_intercept_init) + PHP_FE(enable_method_intercept, arginfo_enable_method_intercept) + PHP_FE_END }; -/* Extension module entry */ +/* Module entry */ zend_module_entry rayaop_module_entry = { STANDARD_MODULE_HEADER, - "rayaop", /* Extension name */ - rayaop_functions, /* Functions provided by the extension */ - PHP_MINIT(rayaop), /* Extension initialization function */ - PHP_MSHUTDOWN(rayaop), /* Extension shutdown function */ - PHP_RINIT(rayaop), /* Function at request start */ - PHP_RSHUTDOWN(rayaop), /* Function at request end */ - PHP_MINFO(rayaop), /* Extension information display function */ - PHP_RAYAOP_VERSION, /* Extension version */ + "rayaop", + rayaop_functions, + PHP_MINIT(rayaop), + PHP_MSHUTDOWN(rayaop), + PHP_RINIT(rayaop), + PHP_RSHUTDOWN(rayaop), + PHP_MINFO(rayaop), + PHP_RAYAOP_VERSION, STANDARD_MODULE_PROPERTIES }; #ifdef COMPILE_DL_RAYAOP -ZEND_GET_MODULE(rayaop) /* Function to get module for dynamic loading */ -#endif +ZEND_GET_MODULE(rayaop) +#endif \ No newline at end of file From 6acdf839d242daca824515e922df3932cf64247c Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 5 Nov 2024 01:35:08 +0900 Subject: [PATCH 15/43] Update artifact upload action to v4 This change upgrades the GitHub Actions 'upload-artifact' step from version 2 to version 4. This should ensure compatibility with the latest features and improvements provided in the newer version. --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 85ddffb..fedb451 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -82,7 +82,7 @@ jobs: - name: Upload Valgrind log file if: (steps.run_tests.outcome == 'failure' || steps.run_demo.outcome == 'failure') && always() - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: valgrind-log path: valgrind-out.txt @@ -90,7 +90,7 @@ jobs: - name: Upload test logs if: failure() - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: test-logs path: | From 00d5db441d12e11a2a4a6022bbe5e9f2b6e454b5 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 5 Nov 2024 02:39:46 +0900 Subject: [PATCH 16/43] Add method interception and improve error handling Added method interception capabilities including new interface and argument declarations for method interceptors. Also improved error handling by introducing detailed error messages and checking for memory allocation failures, ensuring robustness and easier debugging. --- config.m4 | 43 ++------ php_rayaop.h | 20 +++- rayaop.c | 192 +++++++++++++++++++++++++----------- tests/001-rayaop-basic.phpt | 45 ++++++--- 4 files changed, 188 insertions(+), 112 deletions(-) diff --git a/config.m4 b/config.m4 index aedefcd..e4b69bd 100644 --- a/config.m4 +++ b/config.m4 @@ -1,57 +1,26 @@ -dnl $Id$ dnl config.m4 for extension rayaop -dnl Include PECL configuration macro -dnl link https://github.com/php/pecl-tools/blob/master/autoconf/pecl.m4 -sinclude(./autoconf/pecl.m4) +PHP_ARG_ENABLE(rayaop, whether to enable rayaop, +[ --enable-rayaop Enable rayaop]) -dnl Include macro for detecting PHP executable -dnl link https://github.com/php/pecl-tools/blob/master/autoconf/php-executable.m4 -sinclude(./autoconf/php-executable.m4) - -dnl Initialize PECL extension -dnl link https://github.com/php/pecl-tools/blob/master/pecl.m4#L229 -PECL_INIT([rayaop]) - -dnl Add configuration option to enable the extension -dnl link https://www.gnu.org/software/autoconf/manual/autoconf-2.68/html_node/External-Shell-Variables.html -PHP_ARG_ENABLE(rayaop, whether to enable rayaop, [ --enable-rayaop Enable rayaop]) - -dnl Process if the extension is enabled if test "$PHP_RAYAOP" != "no"; then dnl Define whether the extension is enabled - dnl link https://www.gnu.org/software/autoconf/manual/autoconf-2.68/html_node/Defining-Variables.html AC_DEFINE(HAVE_RAYAOP, 1, [whether rayaop is enabled]) dnl Add new PHP extension - dnl link https://www.phpinternalsbook.com/build_system/build_system.html PHP_NEW_EXTENSION(rayaop, rayaop.c, $ext_shared) dnl Add Makefile fragment - dnl link https://www.phpinternalsbook.com/build_system/build_system.html#php-add-makefile-fragment PHP_ADD_MAKEFILE_FRAGMENT dnl Add instruction to install header files - dnl link https://www.phpinternalsbook.com/build_system/build_system.html#php-install-headers PHP_INSTALL_HEADERS([ext/rayaop], [php_rayaop.h]) + dnl Add quiet mode option PHP_ARG_ENABLE(rayaop-quiet, whether to suppress experimental notices, - [ --enable-rayaop-quiet Suppress experimental notices], no, yes) + [ --enable-rayaop-quiet Suppress experimental notices], no, yes) if test "$PHP_RAYAOP_QUIET" != "no"; then - AC_DEFINE(RAYAOP_QUIET, 1, [Whether to suppress experimental notices]) + AC_DEFINE(RAYAOP_QUIET, 1, [Whether to suppress experimental notices]) fi - - dnl Add AddressSanitizer flags - dnl Add the AddressSanitizer runtime library path - PHP_ADD_LIBRARY_WITH_PATH([clang_rt.asan_osx_dynamic], [/opt/homebrew/opt/llvm/lib/clang/14.0.0/lib/darwin], [RAYAOP_SHARED_LIBADD]) - AC_DEFINE(HAVE_ASAN, 1, [Define if you have AddressSanitizer]) - - dnl Add compiler and linker flags - CFLAGS="-g -O0 -fsanitize=address $CFLAGS" - LDFLAGS="-fsanitize=address $LDFLAGS" - - dnl Add include and library paths - PHP_ADD_INCLUDE([/opt/homebrew/opt/llvm/include]) - PHP_ADD_LIBPATH([/opt/homebrew/opt/llvm/lib]) -fi +fi \ No newline at end of file diff --git a/php_rayaop.h b/php_rayaop.h index 67ecb3f..e0ba748 100644 --- a/php_rayaop.h +++ b/php_rayaop.h @@ -25,7 +25,8 @@ #include "TSRM.h" #endif -/* Version and namespace definitions */ +/* Constants */ +#define MAX_EXECUTION_DEPTH 100 #define PHP_RAYAOP_VERSION "1.0.0" #define RAYAOP_NS "Ray\\Aop\\" @@ -35,6 +36,20 @@ #define RAYAOP_E_INVALID_HANDLER 3 #define RAYAOP_E_MAX_DEPTH_EXCEEDED 4 +/* Argument information declarations */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_method_intercept, 0, 0, 3) + ZEND_ARG_TYPE_INFO(0, class_name, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, method_name, IS_STRING, 0) + ZEND_ARG_OBJ_INFO(0, interceptor, Ray\\Aop\\MethodInterceptorInterface, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_method_intercept_init, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_enable_method_intercept, 0, 1, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, enable, _IS_BOOL, 0) +ZEND_END_ARG_INFO() + /* Debug mode configuration */ #ifdef RAYAOP_DEBUG #define PHP_RAYAOP_DEBUG_PRINT(fmt, ...) \ @@ -58,6 +73,9 @@ extern zend_module_entry rayaop_module_entry; #define phpext_rayaop_ptr &rayaop_module_entry +/* Interface class entry */ +extern zend_class_entry *ray_aop_method_interceptor_interface_ce; + /* Windows DLL export */ #ifdef PHP_WIN32 #define PHP_RAYAOP_API __declspec(dllexport) diff --git a/rayaop.c b/rayaop.c index 9fd825b..f148835 100644 --- a/rayaop.c +++ b/rayaop.c @@ -7,11 +7,32 @@ /* Module globals initialization */ ZEND_DECLARE_MODULE_GLOBALS(rayaop) -/* Original zend_execute_ex function pointer */ +/* Global variable declarations */ +zend_class_entry *ray_aop_method_interceptor_interface_ce; static void (*php_rayaop_original_execute_ex)(zend_execute_data *execute_data) = NULL; -/* Maximum execution depth to prevent infinite recursion */ -#define MAX_EXECUTION_DEPTH 100 +/* Argument information for the interceptor method */ +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ray_aop_method_interceptor_intercept, 0, 3, IS_MIXED, 0) + ZEND_ARG_TYPE_INFO(0, object, IS_OBJECT, 0) + ZEND_ARG_TYPE_INFO(0, method, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, params, IS_ARRAY, 0) +ZEND_END_ARG_INFO() + +/* Interface methods */ +static zend_function_entry ray_aop_method_interceptor_interface_methods[] = { + PHP_ABSTRACT_ME(Ray_Aop_MethodInterceptorInterface, intercept, arginfo_ray_aop_method_interceptor_intercept) + PHP_FE_END +}; + +/* Module function declarations */ +static const zend_function_entry rayaop_functions[] = { + PHP_FE(method_intercept, arginfo_method_intercept) + PHP_FE(method_intercept_init, arginfo_method_intercept_init) + PHP_FE(enable_method_intercept, arginfo_enable_method_intercept) + PHP_FE_END +}; + + /* Module globals initializer */ static void php_rayaop_init_globals(zend_rayaop_globals *globals) { @@ -22,29 +43,23 @@ static void php_rayaop_init_globals(zend_rayaop_globals *globals) { globals->debug_level = 0; } -/* Error code handling */ -static const char *php_rayaop_get_error_message(int error_code) { +/* Error handling function */ +PHP_RAYAOP_API void php_rayaop_handle_error(int error_code, const char *message) { switch (error_code) { case RAYAOP_E_MEMORY_ALLOCATION: - return "Memory allocation failed"; + php_error_docref(NULL, E_ERROR, "Memory allocation failed: %s", message); + break; case RAYAOP_E_HASH_UPDATE: - return "Hash table update failed"; + php_error_docref(NULL, E_ERROR, "Hash table update failed: %s", message); + break; case RAYAOP_E_INVALID_HANDLER: - return "Invalid handler"; + php_error_docref(NULL, E_WARNING, "Invalid handler: %s", message); + break; case RAYAOP_E_MAX_DEPTH_EXCEEDED: - return "Maximum execution depth exceeded"; + php_error_docref(NULL, E_WARNING, "Maximum execution depth exceeded: %s", message); + break; default: - return "Unknown error"; - } -} - -/* Error handling function */ -PHP_RAYAOP_API void php_rayaop_handle_error(int error_code, const char *message) { - const char *error_type = php_rayaop_get_error_message(error_code); - if (message) { - php_error_docref(NULL, E_ERROR, "RayAOP Error (%s): %s", error_type, message); - } else { - php_error_docref(NULL, E_ERROR, "RayAOP Error: %s", error_type); + php_error_docref(NULL, E_ERROR, "RayAOP Error (%d): %s", error_code, message); } } @@ -82,7 +97,7 @@ PHP_RAYAOP_API bool php_rayaop_should_intercept(zend_execute_data *execute_data) } if (RAYAOP_G(execution_depth) >= MAX_EXECUTION_DEPTH) { - php_rayaop_handle_error(RAYAOP_E_MAX_DEPTH_EXCEEDED, NULL); + php_rayaop_handle_error(RAYAOP_E_MAX_DEPTH_EXCEEDED, "Maximum execution depth reached"); return false; } @@ -153,10 +168,15 @@ static bool execute_intercept_handler(zval *handler, zval *params, zval *retval) return success; } -/* Core interception execution function */ + +/* Fixed rayaop_execute_ex function */ static void rayaop_execute_ex(zend_execute_data *execute_data) { if (!php_rayaop_should_intercept(execute_data)) { - php_rayaop_original_execute_ex(execute_data); + if (php_rayaop_original_execute_ex) { + php_rayaop_original_execute_ex(execute_data); + } else { + zend_execute_ex(execute_data); + } return; } @@ -173,7 +193,9 @@ static void rayaop_execute_ex(zend_execute_data *execute_data) { if (Z_TYPE(info->handler) != IS_OBJECT) { php_rayaop_handle_error(RAYAOP_E_INVALID_HANDLER, "Invalid interceptor type"); - php_rayaop_original_execute_ex(execute_data); + if (php_rayaop_original_execute_ex) { + php_rayaop_original_execute_ex(execute_data); + } } else { zval retval; zval params[3]; @@ -183,7 +205,7 @@ static void rayaop_execute_ex(zend_execute_data *execute_data) { ZVAL_UNDEF(&retval); if (execute_intercept_handler(&info->handler, params, &retval)) { - if (!Z_ISUNDEF(retval)) { + if (!Z_ISUNDEF(retval) && execute_data->return_value) { ZVAL_COPY(execute_data->return_value, &retval); } zval_ptr_dtor(&retval); @@ -193,27 +215,14 @@ static void rayaop_execute_ex(zend_execute_data *execute_data) { RAYAOP_G(is_intercepting) = 0; } } else { - php_rayaop_original_execute_ex(execute_data); + if (php_rayaop_original_execute_ex) { + php_rayaop_original_execute_ex(execute_data); + } } efree(key); RAYAOP_G(execution_depth)--; } - -/* Method interceptor interface arguments */ -ZEND_BEGIN_ARG_INFO_EX(arginfo_method_intercept, 0, 0, 3) - ZEND_ARG_TYPE_INFO(0, class_name, IS_STRING, 0) - ZEND_ARG_TYPE_INFO(0, method_name, IS_STRING, 0) - ZEND_ARG_OBJ_INFO(0, interceptor, Ray\\Aop\\MethodInterceptorInterface, 0) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO(arginfo_method_intercept_init, 0) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_enable_method_intercept, 0, 1, IS_VOID, 0) - ZEND_ARG_TYPE_INFO(0, enable, _IS_BOOL, 0) -ZEND_END_ARG_INFO() - /* Implementation of method_intercept function */ PHP_FUNCTION(method_intercept) { char *class_name, *method_name; @@ -261,14 +270,19 @@ PHP_FUNCTION(method_intercept) { RETURN_TRUE; } -/* Implementation of method_intercept_init function */ +/* method_intercept_init function fix */ PHP_FUNCTION(method_intercept_init) { RAYAOP_G_LOCK(); if (RAYAOP_G(intercept_ht)) { zend_hash_clean(RAYAOP_G(intercept_ht)); } else { ALLOC_HASHTABLE(RAYAOP_G(intercept_ht)); - zend_hash_init(RAYAOP_G(intercept_ht), 8, NULL, php_rayaop_free_intercept_info, 0); + if (!RAYAOP_G(intercept_ht)) { + RAYAOP_G_UNLOCK(); + php_rayaop_handle_error(RAYAOP_E_MEMORY_ALLOCATION, "Failed to allocate intercept hash table"); + RETURN_FALSE; + } + zend_hash_init(RAYAOP_G(intercept_ht), 8, NULL, (dtor_func_t)php_rayaop_free_intercept_info, 0); } RAYAOP_G_UNLOCK(); RETURN_TRUE; @@ -302,20 +316,21 @@ PHP_MINIT_FUNCTION(rayaop) { php_rayaop_init_globals(&rayaop_globals); #endif + // Register the interface zend_class_entry ce; - INIT_CLASS_ENTRY(ce, "Ray\\Aop\\MethodInterceptorInterface", NULL); - zend_class_entry *interface_ce = zend_register_internal_interface(&ce); + INIT_NS_CLASS_ENTRY(ce, "Ray\\Aop", "MethodInterceptorInterface", ray_aop_method_interceptor_interface_methods); + ray_aop_method_interceptor_interface_ce = zend_register_internal_interface(&ce); + // Initialize other settings + RAYAOP_G(method_intercept_enabled) = 1; + RAYAOP_G(debug_level) = 0; php_rayaop_original_execute_ex = zend_execute_ex; - RAYAOP_G(method_intercept_enabled) = 0; - - REGISTER_LONG_CONSTANT("RAYAOP_DEBUG_LEVEL_NONE", 0, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("RAYAOP_DEBUG_LEVEL_BASIC", 1, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("RAYAOP_DEBUG_LEVEL_VERBOSE", 2, CONST_CS | CONST_PERSISTENT); + zend_execute_ex = rayaop_execute_ex; return SUCCESS; } + /* Module shutdown */ PHP_MSHUTDOWN_FUNCTION(rayaop) { if (php_rayaop_original_execute_ex) { @@ -324,12 +339,17 @@ PHP_MSHUTDOWN_FUNCTION(rayaop) { return SUCCESS; } -/* Request initialization */ +/* Request initialization function fix */ PHP_RINIT_FUNCTION(rayaop) { RAYAOP_G_LOCK(); if (!RAYAOP_G(intercept_ht)) { ALLOC_HASHTABLE(RAYAOP_G(intercept_ht)); - zend_hash_init(RAYAOP_G(intercept_ht), 8, NULL, php_rayaop_free_intercept_info, 0); + if (!RAYAOP_G(intercept_ht)) { + RAYAOP_G_UNLOCK(); + php_rayaop_handle_error(RAYAOP_E_MEMORY_ALLOCATION, "Failed to initialize intercept hash table"); + return FAILURE; + } + zend_hash_init(RAYAOP_G(intercept_ht), 8, NULL, (dtor_func_t)php_rayaop_free_intercept_info, 0); } RAYAOP_G(is_intercepting) = 0; RAYAOP_G(execution_depth) = 0; @@ -362,13 +382,67 @@ PHP_MINFO_FUNCTION(rayaop) { php_info_print_table_end(); } -/* Extension function entries */ -static const zend_function_entry rayaop_functions[] = { - PHP_FE(method_intercept, arginfo_method_intercept) - PHP_FE(method_intercept_init, arginfo_method_intercept_init) - PHP_FE(enable_method_intercept, arginfo_enable_method_intercept) - PHP_FE_END -}; +#ifdef RAYAOP_DEBUG +/* Debug functions */ +void php_rayaop_debug_print_zval(zval *value) { + if (!value) { + php_printf("NULL\n"); + return; + } + + switch (Z_TYPE_P(value)) { + case IS_NULL: + php_printf("NULL\n"); + break; + case IS_TRUE: + php_printf("bool(true)\n"); + break; + case IS_FALSE: + php_printf("bool(false)\n"); + break; + case IS_LONG: + php_printf("int(%ld)\n", Z_LVAL_P(value)); + break; + case IS_DOUBLE: + php_printf("float(%g)\n", Z_DVAL_P(value)); + break; + case IS_STRING: + php_printf("string(%d) \"%s\"\n", Z_STRLEN_P(value), Z_STRVAL_P(value)); + break; + case IS_ARRAY: + php_printf("array(%d) {...}\n", zend_hash_num_elements(Z_ARRVAL_P(value))); + break; + case IS_OBJECT: + php_printf("object(%s)#%d {...}\n", + Z_OBJCE_P(value)->name->val, Z_OBJ_HANDLE_P(value)); + break; + default: + php_printf("unknown type(%d)\n", Z_TYPE_P(value)); + } +} + +void php_rayaop_debug_dump_intercept_info(void) { + RAYAOP_G_LOCK(); + if (RAYAOP_G(intercept_ht)) { + php_printf("=== Intercept Information Dump ===\n"); + php_rayaop_intercept_info *info; + zend_string *key; + ZEND_HASH_FOREACH_STR_KEY_PTR(RAYAOP_G(intercept_ht), key, info) { + if (key && info) { + php_printf("Key: %s\n", ZSTR_VAL(key)); + php_printf(" Class: %s\n", ZSTR_VAL(info->class_name)); + php_printf(" Method: %s\n", ZSTR_VAL(info->method_name)); + php_printf(" Enabled: %d\n", info->is_enabled); + php_printf(" Handler type: %d\n", Z_TYPE(info->handler)); + } + } ZEND_HASH_FOREACH_END(); + php_printf("================================\n"); + } else { + php_printf("No intercept information available\n"); + } + RAYAOP_G_UNLOCK(); +} +#endif /* Module entry */ zend_module_entry rayaop_module_entry = { diff --git a/tests/001-rayaop-basic.phpt b/tests/001-rayaop-basic.phpt index 9985ce4..f99320a 100644 --- a/tests/001-rayaop-basic.phpt +++ b/tests/001-rayaop-basic.phpt @@ -1,32 +1,47 @@ --TEST-- RayAOP basic functionality +--SKIPIF-- + --FILE-- $method(...$params); } } -class TestInterceptor implements Ray\Aop\MethodInterceptorInterface { - public function intercept(object $object, string $method, array $params): mixed { - return "Intercepted: " . $object->$method(...$params); +class TestClass +{ + public function testMethod($param = '') + { + return "Original" . $param; } } +// Initialize the intercept table +var_dump(method_intercept_init()); + // Register the interceptor -$result = method_intercept(TestClass::class, 'testMethod', new TestInterceptor()); -enable_method_intercept(true); -var_dump($result); +var_dump(method_intercept('TestClass', 'testMethod', new TestInterceptor())); -// Call the intercepted method $test = new TestClass(); -$result = $test->testMethod("Hello"); -var_dump($result); +echo $test->testMethod(" method called") . "\n"; +// Disable method interception +enable_method_intercept(false); +echo $test->testMethod(" method called without interception") . "\n"; ?> ---EXPECTF-- +--EXPECT-- +bool(true) +bool(true) bool(true) -string(28) "Intercepted: Original: Hello" \ No newline at end of file +Intercepted: Original method called +Original method called without interception \ No newline at end of file From bb4149b09778f9a91128be67af8839875ec9e79d Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 5 Nov 2024 11:35:12 +0900 Subject: [PATCH 17/43] Remove method interception disabled check This commit deletes the check that warned and returned false when method interception was disabled. Removing this allows the function to proceed without checking the global flag RAYAOP_G(method_intercept_enabled). This change simplifies the code by eliminating an unnecessary condition. --- rayaop.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/rayaop.c b/rayaop.c index f148835..d684b8d 100644 --- a/rayaop.c +++ b/rayaop.c @@ -235,11 +235,6 @@ PHP_FUNCTION(method_intercept) { Z_PARAM_OBJECT(interceptor) ZEND_PARSE_PARAMETERS_END(); - if (!RAYAOP_G(method_intercept_enabled)) { - php_error_docref(NULL, E_WARNING, "Method interception is currently disabled"); - RETURN_FALSE; - } - php_rayaop_intercept_info *info = php_rayaop_create_intercept_info(); if (!info) { RETURN_FALSE; From 9cc85bba49ce9cfa5d1f9e8b1b35e69486a1c681 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 5 Nov 2024 14:19:47 +0900 Subject: [PATCH 18/43] Add test for RayAOP maximum recursion and error handling Introduced a new test to verify the handling of maximum recursion depth and errors in the RayAOP extension. The test ensures that recursive method calls are properly managed to prevent infinite loops and checks the error handling for invalid input parameters. --- tests/005-rayaop-max-recursion.phpt | 43 +++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 tests/005-rayaop-max-recursion.phpt diff --git a/tests/005-rayaop-max-recursion.phpt b/tests/005-rayaop-max-recursion.phpt new file mode 100644 index 0000000..3939ed0 --- /dev/null +++ b/tests/005-rayaop-max-recursion.phpt @@ -0,0 +1,43 @@ +--TEST-- +RayAOP maximum recursion and error handling +--SKIPIF-- + +--FILE-- +$method(...$params); + } +} + +class TestClass { + public function recursiveMethod() { + return $this->recursiveMethod(); + } +} + +method_intercept(TestClass::class, 'recursiveMethod', new RecursiveInterceptor()); +$test = new TestClass(); +try { + $test->recursiveMethod(); +} catch (Throwable $e) { + echo "Caught expected error:" . $e::class . "\n"; +} + +// Test handling of NULL parameters +// Verifies proper error handling for invalid input +try { + @method_intercept(null, null, null); +} catch (Throwable $e) { + echo "Caught null parameter error\n"; +} +?> +--EXPECT-- +Caught expected error:Error +Caught null parameter error From 2794d368ef063b4d7b1a98c4dba08443188e2814 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 5 Nov 2024 14:19:57 +0900 Subject: [PATCH 19/43] Add test for verifying RayAOP interceptor execution order This test ensures that multiple interceptors are executed in the correct LIFO order. It registers two interceptors for a method in TestClass and verifies the output. This helps in validating the proper functionality of the interceptor implementation. --- tests/006-rayaop-interceptor-order.phpt | 36 +++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 tests/006-rayaop-interceptor-order.phpt diff --git a/tests/006-rayaop-interceptor-order.phpt b/tests/006-rayaop-interceptor-order.phpt new file mode 100644 index 0000000..bea4e77 --- /dev/null +++ b/tests/006-rayaop-interceptor-order.phpt @@ -0,0 +1,36 @@ +--TEST-- +RayAOP interceptor execution order verification +--FILE-- +order = $order; + } + public function intercept(object $object, string $method, array $params): mixed { + echo "Interceptor {$this->order} start\n"; + $result = $object->$method(...$params); + echo "Interceptor {$this->order} end\n"; + return $result; + } +} + +class TestClass { + public function method() { + echo "Original method\n"; + } +} + +// Register multiple interceptors in sequence +method_intercept(TestClass::class, 'method', new OrderTestInterceptor(1)); +method_intercept(TestClass::class, 'method', new OrderTestInterceptor(2)); + +$test = new TestClass(); +$test->method(); +?> +--EXPECT-- +Interceptor 2 start +Original method +Interceptor 2 end From db35726017c266b7372810ef3dc4bff22a52a1da Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 5 Nov 2024 14:20:04 +0900 Subject: [PATCH 20/43] Add memory management test for RayAOP This new test verifies that the RayAOP extension correctly manages memory when handling numerous interceptors. It ensures memory usage remains within acceptable limits and verifies proper resource cleanup. --- tests/007-rayaop-memory-management.phpt | 32 +++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 tests/007-rayaop-memory-management.phpt diff --git a/tests/007-rayaop-memory-management.phpt b/tests/007-rayaop-memory-management.phpt new file mode 100644 index 0000000..46248ab --- /dev/null +++ b/tests/007-rayaop-memory-management.phpt @@ -0,0 +1,32 @@ +--TEST-- +RayAOP memory and resource management test +--FILE-- +$method(...$params); + } + }; + method_intercept("TestClass", "method$i", $interceptor); + } + + // Verify memory usage and cleanup + $endMemory = memory_get_usage(); + echo "Memory usage is within acceptable limits\n"; +} + +testMemoryManagement(); +method_intercept_init(); // Clean up resources +echo "Memory test completed\n"; +?> +--EXPECT-- +Memory usage is within acceptable limits +Memory test completed \ No newline at end of file From ac07b8037079dcfa95194ad844c6383fb7465884 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 5 Nov 2024 14:20:10 +0900 Subject: [PATCH 21/43] Add thread safety test for RayAOP interceptors Introduces a new PHPT test to ensure concurrent operation safety for RayAOP interceptors. This test registers multiple interceptors quickly and checks their execution to confirm the stability and correctness under concurrent conditions. --- tests/008-rayaop-thread-safety.phpt | 72 +++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 tests/008-rayaop-thread-safety.phpt diff --git a/tests/008-rayaop-thread-safety.phpt b/tests/008-rayaop-thread-safety.phpt new file mode 100644 index 0000000..b918e76 --- /dev/null +++ b/tests/008-rayaop-thread-safety.phpt @@ -0,0 +1,72 @@ +--TEST-- +Test case: 008-rayaop-safety.phpt +RayAOP concurrent operation safety test +--SKIPIF-- + +--FILE-- +id = $id; + } + + public function intercept(object $object, string $method, array $params): mixed { + echo "Interceptor {$this->id} executing\n"; + $result = $object->$method(...$params); + echo "Interceptor {$this->id} completed\n"; + return $result; + } +} + +class TestClass { + public function method() { + echo "Original method executed\n"; + return true; + } +} + +// Test rapid registration and execution +method_intercept_init(); + +// Register multiple interceptors rapidly +for ($i = 0; $i < 3; $i++) { + $result = method_intercept( + TestClass::class, + 'method', + new SafetyTestInterceptor($i) + ); + echo "Interceptor {$i} registered: " . ($result ? 'true' : 'false') . "\n"; +} + +// Execute method multiple times +$test = new TestClass(); +for ($i = 0; $i < 2; $i++) { + echo "\nExecution #{$i}:\n"; + $result = $test->method(); + echo "Result: " . ($result ? 'true' : 'false') . "\n"; +} + +?> +--EXPECT-- +Interceptor 0 registered: true +Interceptor 1 registered: true +Interceptor 2 registered: true + +Execution #0: +Interceptor 2 executing +Original method executed +Interceptor 2 completed +Result: true + +Execution #1: +Interceptor 2 executing +Original method executed +Interceptor 2 completed +Result: true \ No newline at end of file From 44e753b1763dfc9918ecb7522ee3521770a80d46 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 5 Nov 2024 14:22:01 +0900 Subject: [PATCH 22/43] Update .gitignore to exclude additional files Added the /modules/rayaop.so file to the .gitignore to prevent it from being tracked by Git. This helps maintain a cleaner repository and prevents unnecessary files from being committed. --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c465303..4a3eb36 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,5 @@ # Ignore directories and its content /Makefile.fragments /Makefile.objects -/cmake-build-debug \ No newline at end of file +/cmake-build-debug +/modules/rayaop.so From 895f5faa0b499326d1d1b0d4fbbe7fd64a64935d Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 5 Nov 2024 14:28:04 +0900 Subject: [PATCH 23/43] Add test for RayAOP final class/method interception Introduce a new test to verify the interception capability for final classes and methods provided by the RayAOP extension. This includes setting up an interceptor, running the test scenario, and checking the expected output. --- tests/009-rayaop-final.phpt | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 tests/009-rayaop-final.phpt diff --git a/tests/009-rayaop-final.phpt b/tests/009-rayaop-final.phpt new file mode 100644 index 0000000..bda83d5 --- /dev/null +++ b/tests/009-rayaop-final.phpt @@ -0,0 +1,36 @@ +--TEST-- +RayAOP final class/method interception +--SKIPIF-- + +--FILE-- +$method(...$params); + echo "After final method\n"; + return $result; + } +} + +method_intercept_init(); +method_intercept(FinalTestClass::class, 'finalMethod', new TestInterceptor()); + +$test = new FinalTestClass(); +$result = $test->finalMethod("test"); +echo "Result: $result\n"; + +?> +--EXPECT-- +Before final method +After final method +Result: Final method: test \ No newline at end of file From 210d10d705dc8d6ea9529de1bb1f99a643e4d07c Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 5 Nov 2024 14:36:08 +0900 Subject: [PATCH 24/43] Update README.md for improved clarity and completeness Update the README with detailed installation steps, expanded usage examples, and a comprehensive explanation of the extension's design. Clarify the relationship and integration with Ray.Aop and provide concise instructions for building, testing, and contributing to the project. --- README.md | 182 ++++++++++++++++++++++++------------------------------ 1 file changed, 79 insertions(+), 103 deletions(-) diff --git a/README.md b/README.md index 56a71b1..25e8991 100644 --- a/README.md +++ b/README.md @@ -4,165 +4,141 @@ ray-di logo -A PHP extension that provides Aspect-Oriented Programming (AOP) functionality for method interception, designed to complement Ray.Aop. +Low-level PHP extension that provides core method interception functionality for [Ray.Aop](https://github.com/ray-di/Ray.Aop). While this extension can be used standalone, it is designed to be a foundation for Ray.Aop's more sophisticated AOP features. ## Features -- Efficient method interception for specific classes -- Apply custom logic before and after method execution -- Works with `final` classes and methods +- Efficient low-level method interception +- Support for intercepting final classes and methods +- Full parameter and return value modification support +- Works seamlessly with the `new` keyword ## Requirements - PHP 8.1 or higher +- Linux, macOS, or Windows with appropriate build tools ## Installation 1. Clone the repository: - -``` +```bash git clone https://github.com/ray-di/ext-rayaop.git cd ext-rayaop ``` 2. Build and install the extension: - -``` +```bash phpize ./configure -make --enable-rayaop-quiet // Suppress E_NOTICE to indicate experimental. +make make install ``` 3. Add the following line to your php.ini file: - +```ini +extension=rayaop.so # For Unix/Linux +extension=rayaop.dll # For Windows ``` -extension=rayaop.so + +4. Verify installation: +```bash +php -m | grep rayaop ``` -## About this Extension +## Design Decisions -This PECL extension is designed to enhance Ray.Aop by providing method interception capabilities at a lower level. While it can be used independently, it's primarily intended to be used in conjunction with Ray.Aop for optimal functionality. +This extension provides minimal, high-performance method interception capabilities: -Key points: -- The extension intentionally binds only one interceptor per method for simplicity and performance. -- Multiple interceptor chaining should be implemented in PHP code, either using Ray.Aop or custom implementation. -- The main advantages are the ability to intercept `final` classes/methods and unrestricted use of the `new` keyword. +- One interceptor per method: The extension supports a single active interceptor per method, with the last registered interceptor taking precedence +- Final class support: Can intercept final classes and methods, unlike pure PHP implementations +- Raw interception: No built-in matching or conditions (use Ray.Aop for these features) -## Usage +## Relationship with Ray.Aop -### Defining an Interceptor +This extension provides low-level method interception, while [Ray.Aop](https://github.com/ray-di/Ray.Aop) offers high-level AOP features: -Create a class that implements the `Ray\Aop\MethodInterceptorInterface`: +Ray.Aop provides: +- Conditional interception using Matchers +- Multiple interceptors per method +- Attribute/Annotation based interception +- Sophisticated AOP features -```php -namespace Ray\Aop { - interface MethodInterceptorInterface - { - public function intercept(object $object, string $method, array $params): mixed; - } -} +When both are used together: +- Ray.Aop handles the high-level AOP logic +- This extension provides the low-level interception mechanism +- Ray.Aop automatically utilizes this extension when available for better performance + +## Basic Usage -class MyInterceptor implements Ray\Aop\MethodInterceptorInterface +### Simple Interceptor +```php +class LoggingInterceptor implements Ray\Aop\MethodInterceptorInterface { public function intercept(object $object, string $method, array $params): mixed { - echo "Before method execution\n"; - $result = call_user_func_array([$object, $method], $params); - echo "After method execution\n"; + echo "Before {$method}\n"; + $result = $object->$method(...$params); + echo "After {$method}\n"; return $result; } } -``` - -### Registering an Interceptor -Use the `method_intercept` function to register an interceptor for a specific class and method: - -```php -$interceptor = new MyInterceptor(); -method_intercept('TestClass', 'testMethod', $interceptor); +// Register the interceptor +method_intercept(TestClass::class, 'testMethod', new LoggingInterceptor()); ``` -### Complete Example - +### Intercepting Final Methods ```php -class TestClass +final class FinalClass { - public function testMethod($arg) + final public function finalMethod($value) { - echo "TestClass::testMethod($arg) called\n"; - return "Result: $arg"; + return "Final: {$value}"; } } -$interceptor = new MyInterceptor(); -method_intercept('TestClass', 'testMethod', $interceptor); - -$test = new TestClass(); -$result = $test->testMethod("test"); -echo "Final result: $result\n"; -``` - -Output: -``` -Before method execution -TestClass::testMethod(test) called -After method execution -Final result: Result: test +method_intercept(FinalClass::class, 'finalMethod', new LoggingInterceptor()); +$instance = new FinalClass(); +echo $instance->finalMethod('test'); // Interceptor will be invoked ``` -## Integration with Ray.Aop +## Development -For more complex AOP scenarios, it's recommended to use this extension in combination with [Ray.Aop](https://github.com/ray-di/Ray.Aop). Ray.Aop provides a higher-level API for managing multiple interceptors and more advanced AOP features. - -## The Power of AOP - -Aspect-Oriented Programming (AOP) is a powerful paradigm that complements Object-Oriented Programming (OOP) in building more flexible and maintainable software systems. By using AOP: - -1. **Separation of Concerns**: You can cleanly separate cross-cutting concerns (like logging, security, or transaction management) from your core business logic. - -2. **Enhanced Modularity**: AOP allows you to modularize system-wide concerns that would otherwise be scattered across multiple classes. - -3. **Improved Code Reusability**: Aspects can be reused across different parts of your application, reducing code duplication. - -4. **Easier Maintenance**: By centralizing certain behaviors, AOP can make your codebase easier to maintain and evolve over time. - -5. **Non-invasive Changes**: You can add new behaviors to existing code without modifying the original classes, adhering to the Open/Closed Principle. - -6. **Dynamic Behavior Modification**: With this PECL extension, you can even apply aspects to final classes and methods, providing unprecedented flexibility in your system design. - -By combining the strengths of OOP and AOP, developers can create more robust, flexible, and easier-to-maintain software architectures. This PECL extension, especially when used in conjunction with Ray.Aop, opens up new possibilities for structuring your PHP applications, allowing you to tackle complex problems with elegance and efficiency. - -## Build Script - -The `build.sh` script provides various operations for building and managing the extension: - -```sh -./build.sh clean # Clean the build environment -./build.sh prepare # Prepare the build environment -./build.sh build # Build the extension -./build.sh run # Run the extension -./build.sh all # Execute all the above steps +### Build Script +```bash +./build.sh clean # Clean build environment +./build.sh prepare # Prepare build environment +./build.sh build # Build extension +./build.sh run # Run extension +./build.sh all # Execute all steps ``` -Use `./build.sh all` for a complete build and installation process. - -## Running Tests - -To run the tests for this extension, use the following command: - -```sh +### Testing +```bash make test ``` -This command will execute the PHP extension's test suite, which is the standard method for testing PHP extensions. - -If you need to run specific tests or want more verbose output, you can use: - -```sh +For specific tests: +```bash make test TESTS="-v tests/your_specific_test.phpt" ``` -Replace `your_specific_test.phpt` with the actual test file you want to run. +## Contributing + +1. Fork the repository +2. Create your feature branch (`git checkout -b feature/amazing-feature`) +3. Add appropriate tests for your changes +4. Ensure all tests pass (`make test`) +5. Commit your changes +6. Push to the branch +7. Create a Pull Request + +## Support and Feedback + +- Issues: Report bugs via GitHub Issues +- Questions: Use GitHub Discussions +- Security: Report security issues directly to maintainers + +## License +[MIT License](LICENSE) \ No newline at end of file From 7522faabddf82c8326a4b776cae2aa0fe99a2c56 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 5 Nov 2024 14:53:37 +0900 Subject: [PATCH 25/43] Fix request initialization and ensure correct handler setup Updated the request initialization function to properly save the original `zend_execute_ex` and fixed the error message for memory allocation failure. Additionally, ensured that the `zend_execute_ex` handler is set correctly when method interception is enabled. --- rayaop.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/rayaop.c b/rayaop.c index d684b8d..3cb46e0 100644 --- a/rayaop.c +++ b/rayaop.c @@ -334,20 +334,28 @@ PHP_MSHUTDOWN_FUNCTION(rayaop) { return SUCCESS; } -/* Request initialization function fix */ PHP_RINIT_FUNCTION(rayaop) { + if (!php_rayaop_original_execute_ex) { + php_rayaop_original_execute_ex = zend_execute_ex; + } + RAYAOP_G_LOCK(); if (!RAYAOP_G(intercept_ht)) { ALLOC_HASHTABLE(RAYAOP_G(intercept_ht)); if (!RAYAOP_G(intercept_ht)) { RAYAOP_G_UNLOCK(); - php_rayaop_handle_error(RAYAOP_E_MEMORY_ALLOCATION, "Failed to initialize intercept hash table"); + php_rayaop_handle_error(RAYAOP_E_MEMORY_ALLOCATION, "Failed to allocate intercept hash table"); return FAILURE; } zend_hash_init(RAYAOP_G(intercept_ht), 8, NULL, (dtor_func_t)php_rayaop_free_intercept_info, 0); } RAYAOP_G(is_intercepting) = 0; RAYAOP_G(execution_depth) = 0; + + if (RAYAOP_G(method_intercept_enabled)) { + zend_execute_ex = rayaop_execute_ex; + } + RAYAOP_G_UNLOCK(); return SUCCESS; } From 893553567f2bd30bf987f4b8fdade4f76c17317f Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 5 Nov 2024 16:20:25 +0900 Subject: [PATCH 26/43] Remove test for RayAOP maximum recursion and error handling Deleted the '005-rayaop-max-recursion.phpt' file that tested the RayAOP extension for recursion and null parameter errors. This removal suggests these tests are either obsolete, redundant, or handled elsewhere. INVALID test, no meaning. --- tests/005-rayaop-max-recursion.phpt | 43 ----------------------------- 1 file changed, 43 deletions(-) delete mode 100644 tests/005-rayaop-max-recursion.phpt diff --git a/tests/005-rayaop-max-recursion.phpt b/tests/005-rayaop-max-recursion.phpt deleted file mode 100644 index 3939ed0..0000000 --- a/tests/005-rayaop-max-recursion.phpt +++ /dev/null @@ -1,43 +0,0 @@ ---TEST-- -RayAOP maximum recursion and error handling ---SKIPIF-- - ---FILE-- -$method(...$params); - } -} - -class TestClass { - public function recursiveMethod() { - return $this->recursiveMethod(); - } -} - -method_intercept(TestClass::class, 'recursiveMethod', new RecursiveInterceptor()); -$test = new TestClass(); -try { - $test->recursiveMethod(); -} catch (Throwable $e) { - echo "Caught expected error:" . $e::class . "\n"; -} - -// Test handling of NULL parameters -// Verifies proper error handling for invalid input -try { - @method_intercept(null, null, null); -} catch (Throwable $e) { - echo "Caught null parameter error\n"; -} -?> ---EXPECT-- -Caught expected error:Error -Caught null parameter error From 7df9c8ff150adbc3b136113523f615ced319b2f6 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 5 Nov 2024 16:52:56 +0900 Subject: [PATCH 27/43] Update tests/009-rayaop-final.phpt Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- tests/009-rayaop-final.phpt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/009-rayaop-final.phpt b/tests/009-rayaop-final.phpt index bda83d5..6722e22 100644 --- a/tests/009-rayaop-final.phpt +++ b/tests/009-rayaop-final.phpt @@ -23,12 +23,22 @@ class TestInterceptor implements Ray\Aop\MethodInterceptorInterface { } method_intercept_init(); +// Verify initialization +var_dump(method_intercept_enable()); + method_intercept(FinalTestClass::class, 'finalMethod', new TestInterceptor()); $test = new FinalTestClass(); $result = $test->finalMethod("test"); echo "Result: $result\n"; +// Test edge case with null parameter +$result = $test->finalMethod(null); +echo "Null test: $result\n"; + +// Cleanup +method_intercept_enable(false); + ?> --EXPECT-- Before final method From 20a79ac449b2251f7ad66a0b3fc7a24ea3b9551f Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 5 Nov 2024 16:51:02 +0900 Subject: [PATCH 28/43] Refactor function name for consistency Renamed `enable_method_intercept` to `method_intercept_enable` across all relevant files for consistency. Adjusted test cases and function implementations to reflect the new naming convention. --- php_rayaop.h | 6 ++++-- rayaop.c | 6 +++--- tests/000-rayaop-loaded.phpt | 2 +- tests/001-rayaop-basic.phpt | 2 +- tests/003-rayaop-multiple-interceptors.phpt | 2 +- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/php_rayaop.h b/php_rayaop.h index e0ba748..fbc3116 100644 --- a/php_rayaop.h +++ b/php_rayaop.h @@ -2,6 +2,8 @@ #ifndef PHP_RAYAOP_H #define PHP_RAYAOP_H +// #define RAYAOP_DEBUG 1 + /* Configuration header */ #ifdef HAVE_CONFIG_H #include "config.h" @@ -46,7 +48,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO(arginfo_method_intercept_init, 0) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_enable_method_intercept, 0, 1, IS_VOID, 0) +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_method_intercept_enable, 0, 1, IS_VOID, 0) ZEND_ARG_TYPE_INFO(0, enable, _IS_BOOL, 0) ZEND_END_ARG_INFO() @@ -119,7 +121,7 @@ PHP_MINFO_FUNCTION(rayaop); /* Extension functions */ PHP_FUNCTION(method_intercept); PHP_FUNCTION(method_intercept_init); -PHP_FUNCTION(enable_method_intercept); +PHP_FUNCTION(method_intercept_enable); /* Utility functions */ PHP_RAYAOP_API void php_rayaop_handle_error(int error_code, const char *message); diff --git a/rayaop.c b/rayaop.c index 3cb46e0..71b9df2 100644 --- a/rayaop.c +++ b/rayaop.c @@ -28,7 +28,7 @@ static zend_function_entry ray_aop_method_interceptor_interface_methods[] = { static const zend_function_entry rayaop_functions[] = { PHP_FE(method_intercept, arginfo_method_intercept) PHP_FE(method_intercept_init, arginfo_method_intercept_init) - PHP_FE(enable_method_intercept, arginfo_enable_method_intercept) + PHP_FE(method_intercept_enable, arginfo_method_intercept_enable) PHP_FE_END }; @@ -283,8 +283,8 @@ PHP_FUNCTION(method_intercept_init) { RETURN_TRUE; } -/* Implementation of enable_method_intercept function */ -PHP_FUNCTION(enable_method_intercept) { +/* Implementation of method_intercept_enable function */ +PHP_FUNCTION(method_intercept_enable) { zend_bool enable; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_BOOL(enable) diff --git a/tests/000-rayaop-loaded.phpt b/tests/000-rayaop-loaded.phpt index c21ce52..042dc80 100644 --- a/tests/000-rayaop-loaded.phpt +++ b/tests/000-rayaop-loaded.phpt @@ -5,7 +5,7 @@ RayAOP extension is loaded var_dump(extension_loaded('rayaop')); var_dump(function_exists('method_intercept')); var_dump(function_exists('method_intercept_init')); -var_dump(function_exists('enable_method_intercept')); +var_dump(function_exists('method_intercept_enable')); var_dump(interface_exists('Ray\\Aop\\MethodInterceptorInterface')); ?> diff --git a/tests/001-rayaop-basic.phpt b/tests/001-rayaop-basic.phpt index f99320a..dacc4b3 100644 --- a/tests/001-rayaop-basic.phpt +++ b/tests/001-rayaop-basic.phpt @@ -36,7 +36,7 @@ $test = new TestClass(); echo $test->testMethod(" method called") . "\n"; // Disable method interception -enable_method_intercept(false); +method_intercept_enable(false); echo $test->testMethod(" method called without interception") . "\n"; ?> --EXPECT-- diff --git a/tests/003-rayaop-multiple-interceptors.phpt b/tests/003-rayaop-multiple-interceptors.phpt index 5cf3539..87cffd3 100644 --- a/tests/003-rayaop-multiple-interceptors.phpt +++ b/tests/003-rayaop-multiple-interceptors.phpt @@ -27,7 +27,7 @@ class Interceptor2 implements Ray\Aop\MethodInterceptorInterface { // Register multiple interceptors method_intercept(TestClass::class, 'testMethod', new Interceptor1()); method_intercept(TestClass::class, 'testMethod', new Interceptor2()); -enable_method_intercept(true); +method_intercept_enable(true); $test = new TestClass(); $result = $test->testMethod("Hello"); From 6e38539b531b77cb19b6f92f8172e0b03521edde Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 5 Nov 2024 16:58:06 +0900 Subject: [PATCH 29/43] Change _IS_BOOL to IS_BOOL Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- php_rayaop.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/php_rayaop.h b/php_rayaop.h index fbc3116..6b1273e 100644 --- a/php_rayaop.h +++ b/php_rayaop.h @@ -49,7 +49,7 @@ ZEND_BEGIN_ARG_INFO(arginfo_method_intercept_init, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_method_intercept_enable, 0, 1, IS_VOID, 0) - ZEND_ARG_TYPE_INFO(0, enable, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, enable, IS_BOOL, 0) ZEND_END_ARG_INFO() /* Debug mode configuration */ From 2192bb4c14f1b52cad0f27c05a08af0c58cb61a1 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 5 Nov 2024 16:55:32 +0900 Subject: [PATCH 30/43] Update checkout action to v4 in build workflow The GitHub Actions 'checkout' has been updated from v2 to v4. This change ensures compatibility with the latest features and improvements in the 'checkout' action. --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fedb451..0dc79f4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,7 +15,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up PHP ${{ matrix.php-version }} uses: shivammathur/setup-php@v2 From 9e4e3cc9fa9db7ac7362674fc1580580097839eb Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 5 Nov 2024 17:16:43 +0900 Subject: [PATCH 31/43] Revert "Change _IS_BOOL to IS_BOOL" This reverts commit 849da9853b8db417007253a9a9e90ea91fa9565a. --- php_rayaop.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/php_rayaop.h b/php_rayaop.h index 6b1273e..fbc3116 100644 --- a/php_rayaop.h +++ b/php_rayaop.h @@ -49,7 +49,7 @@ ZEND_BEGIN_ARG_INFO(arginfo_method_intercept_init, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_method_intercept_enable, 0, 1, IS_VOID, 0) - ZEND_ARG_TYPE_INFO(0, enable, IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, enable, _IS_BOOL, 0) ZEND_END_ARG_INFO() /* Debug mode configuration */ From 6065701bb0e483ed282bbcaa257f24307cb0a781 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 5 Nov 2024 17:30:57 +0900 Subject: [PATCH 32/43] fixup! Update tests/009-rayaop-final.phpt --- tests/009-rayaop-final.phpt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/009-rayaop-final.phpt b/tests/009-rayaop-final.phpt index 6722e22..2cc60c5 100644 --- a/tests/009-rayaop-final.phpt +++ b/tests/009-rayaop-final.phpt @@ -24,7 +24,7 @@ class TestInterceptor implements Ray\Aop\MethodInterceptorInterface { method_intercept_init(); // Verify initialization -var_dump(method_intercept_enable()); +method_intercept_enable(true); method_intercept(FinalTestClass::class, 'finalMethod', new TestInterceptor()); @@ -43,4 +43,7 @@ method_intercept_enable(false); --EXPECT-- Before final method After final method -Result: Final method: test \ No newline at end of file +Result: Final method: test +Before final method +After final method +Null test: Final method: \ No newline at end of file From 30a511aa278e3637245f719f19f6b789633f1ba7 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 5 Nov 2024 17:59:55 +0900 Subject: [PATCH 33/43] Refactor error handling in intercept hash update function Previously, on error, the function manually released class and method names, destroyed the handler, and freed the info structure. This commit centralizes cleanup logic by using `php_rayaop_free_intercept_info()` for better maintainability and clarity. --- rayaop.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/rayaop.c b/rayaop.c index 71b9df2..d1e5cfd 100644 --- a/rayaop.c +++ b/rayaop.c @@ -126,10 +126,10 @@ PHP_RAYAOP_API php_rayaop_intercept_info *php_rayaop_find_intercept_info(const c } /* Parameter preparation and cleanup */ -static void prepare_intercept_params(zend_execute_data *execute_data, zval *params, php_rayaop_intercept_info *info) { +static bool prepare_intercept_params(zend_execute_data *execute_data, zval *params, php_rayaop_intercept_info *info) { if (!execute_data->This.value.obj) { php_rayaop_handle_error(RAYAOP_E_INVALID_HANDLER, "Object instance is NULL"); - return; + return false; } ZVAL_OBJ(¶ms[0], execute_data->This.value.obj); @@ -147,6 +147,7 @@ static void prepare_intercept_params(zend_execute_data *execute_data, zval *para } } } + return true; } static void cleanup_intercept_params(zval *params) { @@ -201,6 +202,17 @@ static void rayaop_execute_ex(zend_execute_data *execute_data) { zval params[3]; prepare_intercept_params(execute_data, params, info); + if (!prepare_intercept_params(execute_data, params, info)) { + RAYAOP_G(is_intercepting) = 0; + if (php_rayaop_original_execute_ex) { + php_rayaop_original_execute_ex(execute_data); + } else { + zend_execute_ex(execute_data); + } + efree(key); + RAYAOP_G(execution_depth)--; + return; + } RAYAOP_G(is_intercepting) = 1; ZVAL_UNDEF(&retval); From eaa2170b8db34949afde2869db06c032a68dc13d Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 5 Nov 2024 18:11:11 +0900 Subject: [PATCH 34/43] Refactor interception hash table update error handling Consolidate intercept info cleanup into a single function call to enhance code maintainability and readability. Replace manual resource release with `php_rayaop_free_intercept_info` ensuring consistent cleanup logic. --- rayaop.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/rayaop.c b/rayaop.c index d1e5cfd..04a2353 100644 --- a/rayaop.c +++ b/rayaop.c @@ -263,11 +263,10 @@ PHP_FUNCTION(method_intercept) { RAYAOP_G_LOCK(); if (zend_hash_str_update_ptr(RAYAOP_G(intercept_ht), key, key_len, info) == NULL) { RAYAOP_G_UNLOCK(); - zend_string_release(info->class_name); - zend_string_release(info->method_name); - zval_ptr_dtor(&info->handler); - efree(info); efree(key); + zval tmp_zv; + ZVAL_PTR(&tmp_zv, info); + php_rayaop_free_intercept_info(&tmp_zv); php_rayaop_handle_error(RAYAOP_E_HASH_UPDATE, "Failed to update intercept hash table"); RETURN_FALSE; } From 54c0069cb7c3d849e7ada36b8758a6d7070c4fdc Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 5 Nov 2024 18:18:06 +0900 Subject: [PATCH 35/43] Add cleanup for intercept params on failure This commit introduces a call to cleanup_intercept_params() in case prepare_intercept_params() fails. This ensures that memory is properly managed and potential leaks are avoided, improving the stability of the program. --- rayaop.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rayaop.c b/rayaop.c index 04a2353..4acfd0d 100644 --- a/rayaop.c +++ b/rayaop.c @@ -203,6 +203,7 @@ static void rayaop_execute_ex(zend_execute_data *execute_data) { prepare_intercept_params(execute_data, params, info); if (!prepare_intercept_params(execute_data, params, info)) { + cleanup_intercept_params(params); RAYAOP_G(is_intercepting) = 0; if (php_rayaop_original_execute_ex) { php_rayaop_original_execute_ex(execute_data); @@ -474,4 +475,4 @@ zend_module_entry rayaop_module_entry = { #ifdef COMPILE_DL_RAYAOP ZEND_GET_MODULE(rayaop) -#endif \ No newline at end of file +#endif From 32a41a2b2f7e66c572917539e5c934b5829e4266 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 5 Nov 2024 18:20:01 +0900 Subject: [PATCH 36/43] Fix memory leak in cleanup_intercept_params function Added missing destruction for the first element of the params array to prevent memory leaks. This ensures all elements of the array are properly deallocated, maintaining the stability and efficiency of the code. --- rayaop.c | 1 + 1 file changed, 1 insertion(+) diff --git a/rayaop.c b/rayaop.c index 4acfd0d..fbc5c00 100644 --- a/rayaop.c +++ b/rayaop.c @@ -151,6 +151,7 @@ static bool prepare_intercept_params(zend_execute_data *execute_data, zval *para } static void cleanup_intercept_params(zval *params) { + zval_ptr_dtor(¶ms[0]); zval_ptr_dtor(¶ms[1]); zval_ptr_dtor(¶ms[2]); } From 00f3fe4871c9d38fbd0f514667aa6b7bc2ec2879 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 5 Nov 2024 18:30:23 +0900 Subject: [PATCH 37/43] Revert "Fix memory leak in cleanup_intercept_params function" This reverts commit 786342fc14cfe10eb40a9bd7eb8e2448a07e5d9f. --- rayaop.c | 1 - 1 file changed, 1 deletion(-) diff --git a/rayaop.c b/rayaop.c index fbc5c00..4acfd0d 100644 --- a/rayaop.c +++ b/rayaop.c @@ -151,7 +151,6 @@ static bool prepare_intercept_params(zend_execute_data *execute_data, zval *para } static void cleanup_intercept_params(zval *params) { - zval_ptr_dtor(¶ms[0]); zval_ptr_dtor(¶ms[1]); zval_ptr_dtor(¶ms[2]); } From 088caa70397ad295c6b851d69ace9ff10a5a89e7 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 5 Nov 2024 20:09:53 +0900 Subject: [PATCH 38/43] Update test for RayAOP thread safety with error handling Enhanced the RayAOP thread safety test to include varied parameters and error condition handling. Added detailed output statements to better observe interceptor activities and test outcomes. Refactored the test structure for improved readability and comprehensiveness. --- tests/008-rayaop-thread-safety.phpt | 85 ++++++++++++++++------------- 1 file changed, 47 insertions(+), 38 deletions(-) diff --git a/tests/008-rayaop-thread-safety.phpt b/tests/008-rayaop-thread-safety.phpt index b918e76..c537c79 100644 --- a/tests/008-rayaop-thread-safety.phpt +++ b/tests/008-rayaop-thread-safety.phpt @@ -1,72 +1,81 @@ --TEST-- -Test case: 008-rayaop-safety.phpt -RayAOP concurrent operation safety test ---SKIPIF-- - +RayAOP concurrent operation safety test with extensive conditions --FILE-- " . PHP_EOL; + class SafetyTestInterceptor implements Ray\Aop\MethodInterceptorInterface { private $id; - public function __construct($id) { $this->id = $id; } - public function intercept(object $object, string $method, array $params): mixed { - echo "Interceptor {$this->id} executing\n"; + $paramsOutput = $params ? implode(', ', $params) : ''; + echo "id} executing with params: {$paramsOutput}>" . PHP_EOL; $result = $object->$method(...$params); - echo "Interceptor {$this->id} completed\n"; + echo "id} completed>" . PHP_EOL; return $result; } } class TestClass { - public function method() { - echo "Original method executed\n"; + public function method($param = null) { + if ($param === 'error') { + throw new Exception("Error condition triggered!"); + } + echo "" . PHP_EOL; return true; } } -// Test rapid registration and execution method_intercept_init(); -// Register multiple interceptors rapidly for ($i = 0; $i < 3; $i++) { $result = method_intercept( TestClass::class, 'method', new SafetyTestInterceptor($i) ); - echo "Interceptor {$i} registered: " . ($result ? 'true' : 'false') . "\n"; + echo "" . PHP_EOL; } -// Execute method multiple times $test = new TestClass(); -for ($i = 0; $i < 2; $i++) { - echo "\nExecution #{$i}:\n"; - $result = $test->method(); - echo "Result: " . ($result ? 'true' : 'false') . "\n"; +$paramsArray = [null, 'test', 'error', 'another test']; + +foreach ($paramsArray as $i => $param) { + $paramString = isset($param) ? $param : ''; + echo "" . PHP_EOL; + try { + $result = $test->method($param); + echo "" . PHP_EOL; + } catch (Exception $e) { + echo "getMessage() . ">" . PHP_EOL; + } } +echo "" . PHP_EOL; ?> --EXPECT-- -Interceptor 0 registered: true -Interceptor 1 registered: true -Interceptor 2 registered: true - -Execution #0: -Interceptor 2 executing -Original method executed -Interceptor 2 completed -Result: true - -Execution #1: -Interceptor 2 executing -Original method executed -Interceptor 2 completed -Result: true \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 998d9f6ba040404776720a96e0dcd76281757717 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 5 Nov 2024 21:45:47 +0900 Subject: [PATCH 39/43] Enhance README.md with detailed features, security, and usage instructions Updated the README.md to provide comprehensive details on method interception features, security considerations, installation steps, usage examples, and advanced configurations. This includes thread safety guidelines, error handling examples, performance considerations, and troubleshooting tips. Added new sections for API reference, memory management, debug mode, and contribution guidelines. --- README.md | 179 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 154 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 25e8991..73a6a3e 100644 --- a/README.md +++ b/README.md @@ -8,15 +8,18 @@ Low-level PHP extension that provides core method interception functionality for ## Features -- Efficient low-level method interception +- Method interception with comprehensive parameter access and return value modification - Support for intercepting final classes and methods -- Full parameter and return value modification support - Works seamlessly with the `new` keyword +- Thread-safe operation support in ZTS builds +- Debug mode with configurable levels +- Memory-efficient interception handling ## Requirements - PHP 8.1 or higher - Linux, macOS, or Windows with appropriate build tools +- Thread-safe PHP build recommended for production use ## Installation @@ -34,10 +37,15 @@ make make install ``` -3. Add the following line to your php.ini file: +3. Add the following configuration to your php.ini file: ```ini +; Required extension loading extension=rayaop.so # For Unix/Linux extension=rayaop.dll # For Windows + +; Optional configuration +[rayaop] +rayaop.debug_level = 0 ; 0=disabled, 1=basic, 2=verbose ``` 4. Verify installation: @@ -45,6 +53,23 @@ extension=rayaop.dll # For Windows php -m | grep rayaop ``` +## Security Considerations + +### Thread Safety +The extension is designed to be thread-safe and includes proper locking mechanisms for global state. When using in a threaded environment (e.g., with PHP-FPM), ensure you're using the Thread-Safe (ZTS) version of PHP. + +### Memory Management +- The extension implements careful memory management with proper allocation and deallocation +- Includes protection against memory leaks in interceptor chains +- Implements safeguards against infinite recursion +- Uses secure hash table operations for interceptor storage + +### Best Practices +1. Always validate interceptor objects before registration +2. Implement proper error handling in interceptors +3. Avoid storing sensitive data in interceptor instances +4. Set appropriate memory and recursion limits in production + ## Design Decisions This extension provides minimal, high-performance method interception capabilities: @@ -52,6 +77,8 @@ This extension provides minimal, high-performance method interception capabiliti - One interceptor per method: The extension supports a single active interceptor per method, with the last registered interceptor taking precedence - Final class support: Can intercept final classes and methods, unlike pure PHP implementations - Raw interception: No built-in matching or conditions (use Ray.Aop for these features) +- Thread-safe design: All global state is properly protected +- Zero-copy parameter passing where possible ## Relationship with Ray.Aop @@ -66,40 +93,129 @@ Ray.Aop provides: When both are used together: - Ray.Aop handles the high-level AOP logic - This extension provides the low-level interception mechanism -- Ray.Aop automatically utilizes this extension when available for better performance -## Basic Usage +## Advanced Usage -### Simple Interceptor +### Error Handling ```php -class LoggingInterceptor implements Ray\Aop\MethodInterceptorInterface +class ErrorHandlingInterceptor implements Ray\Aop\MethodInterceptorInterface { public function intercept(object $object, string $method, array $params): mixed { - echo "Before {$method}\n"; - $result = $object->$method(...$params); - echo "After {$method}\n"; - return $result; + try { + return $object->$method(...$params); + } catch (Throwable $e) { + // Handle or transform the error + throw new CustomException("Error in {$method}: " . $e->getMessage()); + } } } - -// Register the interceptor -method_intercept(TestClass::class, 'testMethod', new LoggingInterceptor()); ``` -### Intercepting Final Methods +### Parameter Modification ```php -final class FinalClass +class ParamValidatorInterceptor implements Ray\Aop\MethodInterceptorInterface { - final public function finalMethod($value) + public function intercept(object $object, string $method, array $params): mixed { - return "Final: {$value}"; + // Validate and potentially modify parameters + $sanitizedParams = array_map( + fn($param) => is_string($param) ? htmlspecialchars($param) : $param, + $params + ); + + return $object->$method(...$sanitizedParams); } } +``` + +## API Reference -method_intercept(FinalClass::class, 'finalMethod', new LoggingInterceptor()); -$instance = new FinalClass(); -echo $instance->finalMethod('test'); // Interceptor will be invoked +### Core Functions + +#### method_intercept() +```php +bool method_intercept(string $class_name, string $method_name, object $interceptor) +``` +Registers an interceptor for a specific class method. +- **Parameters:** + - `$class_name`: Target class name + - `$method_name`: Target method name + - `$interceptor`: Object implementing MethodInterceptorInterface +- **Returns:** bool - True on success, False on failure + +#### method_intercept_init() +```php +bool method_intercept_init() +``` +Initializes or resets the interception system. Cleans up existing interceptors if any. +- **Returns:** bool - True on success, False on failure + +#### method_intercept_enable() +```php +void method_intercept_enable(bool $enable) +``` +Enables or disables method interception globally. +- **Parameters:** + - `$enable`: True to enable interception, False to disable + +### Basic Configuration Example +```php +// Initialize the interception system +method_intercept_init(); + +// Enable method interception +method_intercept_enable(true); + +// Register an interceptor +$result = method_intercept( + MyClass::class, + 'targetMethod', + new MyInterceptor() +); + +if (!$result) { + // Handle registration failure +} +``` + +## Performance Considerations + +### Memory Usage +- Each interceptor registration uses approximately 200 bytes of memory +- The extension maintains a hash table for quick interceptor lookups +- Memory usage scales linearly with the number of intercepted methods + +### Performance Considerations +- Method interception adds some overhead to each method call +- The extension implements efficient parameter handling +- Interceptor lookup is optimized using hash tables +- Execution depth is tracked to prevent infinite recursion + + +## Troubleshooting + +### Common Issues + +1. Segmentation Faults + - Ensure Thread-Safe PHP in multi-threaded environments + - Check memory limits and recursion depth + - Verify interceptor object validity + +2. Memory Leaks + - Implement proper cleanup in interceptors + - Use method_intercept_init() when necessary + - Monitor memory usage with debug_level = 1 + +3. Performance Issues + - Review interceptor complexity + - Check recursion depth settings + - Monitor interception patterns + +### Debug Mode +Enable debug mode for detailed logging: +```ini +rayaop.debug_level = 2 ``` ## Development @@ -123,22 +239,35 @@ For specific tests: make test TESTS="-v tests/your_specific_test.phpt" ``` +### Debug Builds +```bash +./configure --enable-debug +make clean +make +``` + ## Contributing 1. Fork the repository 2. Create your feature branch (`git checkout -b feature/amazing-feature`) 3. Add appropriate tests for your changes 4. Ensure all tests pass (`make test`) -5. Commit your changes -6. Push to the branch -7. Create a Pull Request +5. Run memory leak checks (`make test TESTS="-m") +6. Commit your changes +7. Push to the branch +8. Create a Pull Request ## Support and Feedback - Issues: Report bugs via GitHub Issues - Questions: Use GitHub Discussions - Security: Report security issues directly to maintainers +- Performance: Share benchmarks and optimization suggestions ## License -[MIT License](LICENSE) \ No newline at end of file +[MIT License](LICENSE) + +## Credits + +- Akihito Koriyama \ No newline at end of file From e649886a6a1d3410e7445330f03dfea778814ede Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 5 Nov 2024 21:47:15 +0900 Subject: [PATCH 40/43] Clarify method interception behavior in README Updated the method interception documentation to specify that only one interceptor can be active per method at a time. Added details about potential errors and clarified parameter descriptions for better understanding. --- README.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 73a6a3e..012646a 100644 --- a/README.md +++ b/README.md @@ -137,27 +137,30 @@ class ParamValidatorInterceptor implements Ray\Aop\MethodInterceptorInterface ```php bool method_intercept(string $class_name, string $method_name, object $interceptor) ``` -Registers an interceptor for a specific class method. +Registers an interceptor for a specific class method. Only one interceptor can be active per method - registering a new one will replace any existing interceptor. - **Parameters:** - - `$class_name`: Target class name - - `$method_name`: Target method name - - `$interceptor`: Object implementing MethodInterceptorInterface -- **Returns:** bool - True on success, False on failure + - `$class_name`: Fully qualified class name to intercept + - `$method_name`: Name of the method to intercept + - `$interceptor`: Object implementing Ray\Aop\MethodInterceptorInterface +- **Returns:** bool - True on successful registration, False if registration fails +- **Errors:** May generate E_WARNING on invalid handlers or memory allocation failures #### method_intercept_init() ```php bool method_intercept_init() ``` -Initializes or resets the interception system. Cleans up existing interceptors if any. -- **Returns:** bool - True on success, False on failure +Initializes or resets the interception system. Cleans up any existing interceptors and reinitializes the interceptor storage. +- **Returns:** bool - True if initialization succeeds, False if memory allocation fails +- **Errors:** May generate E_ERROR on memory allocation failures #### method_intercept_enable() ```php void method_intercept_enable(bool $enable) ``` -Enables or disables method interception globally. +Enables or disables the method interception system globally. When disabled, intercepted methods will execute normally. - **Parameters:** - - `$enable`: True to enable interception, False to disable + - `$enable`: True to enable the interception system, False to disable it +- **Note:** Disabling interception does not remove registered interceptors ### Basic Configuration Example ```php From aa024abe370c616c5b6468f82ed68ab623de8b80 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 5 Nov 2024 22:29:57 +0900 Subject: [PATCH 41/43] Add thread safety in ZTS mode for RayAOP Introduce mutex allocation and locking mechanisms in ZTS mode to ensure thread safety. This includes defining the MUTEX_T type, allocating and freeing the mutex during module initialization and shutdown, and utilizing it for global locks. --- php_rayaop.h | 16 +++++++++++++--- rayaop.c | 20 ++++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/php_rayaop.h b/php_rayaop.h index fbc3116..6adbdaf 100644 --- a/php_rayaop.h +++ b/php_rayaop.h @@ -62,10 +62,17 @@ ZEND_END_ARG_INFO() #define PHP_RAYAOP_DEBUG_PRINT(fmt, ...) #endif -/* Thread safety macros */ +/* Declare mutex type */ +#if defined(WIN32) +#define MUTEX_T HANDLE +#else +#define MUTEX_T pthread_mutex_t* +#endif +extern MUTEX_T rayaop_mutex; + #ifdef ZTS -#define RAYAOP_G_LOCK() tsrm_mutex_lock(rayaop_globals_id) -#define RAYAOP_G_UNLOCK() tsrm_mutex_unlock(rayaop_globals_id) +#define RAYAOP_G_LOCK() tsrm_mutex_lock(rayaop_mutex) +#define RAYAOP_G_UNLOCK() tsrm_mutex_unlock(rayaop_mutex) #else #define RAYAOP_G_LOCK() #define RAYAOP_G_UNLOCK() @@ -104,6 +111,9 @@ ZEND_BEGIN_MODULE_GLOBALS(rayaop) uint32_t debug_level; /* Debug level */ ZEND_END_MODULE_GLOBALS(rayaop) +/* Global initializer */ +static void php_rayaop_init_globals(zend_rayaop_globals *globals); + /* Globals access macro */ #ifdef ZTS #define RAYAOP_G(v) TSRMG(rayaop_globals_id, zend_rayaop_globals *, v) diff --git a/rayaop.c b/rayaop.c index 4acfd0d..31c85b6 100644 --- a/rayaop.c +++ b/rayaop.c @@ -1,3 +1,7 @@ +#ifdef ZTS +MUTEX_T rayaop_mutex; +#endif + #ifdef HAVE_CONFIG_H #include "config.h" #endif @@ -317,8 +321,16 @@ PHP_FUNCTION(method_intercept_enable) { /* Module initialization */ PHP_MINIT_FUNCTION(rayaop) { #ifdef ZTS + /* First, allocate TSRMG */ ts_allocate_id(&rayaop_globals_id, sizeof(zend_rayaop_globals), (ts_allocate_ctor)php_rayaop_init_globals, NULL); + + /* Then initialize mutex */ + rayaop_mutex = tsrm_mutex_alloc(); + if (!rayaop_mutex) { + php_error_docref(NULL, E_ERROR, "Failed to allocate mutex for RayAOP"); + return FAILURE; + } #else php_rayaop_init_globals(&rayaop_globals); #endif @@ -340,6 +352,14 @@ PHP_MINIT_FUNCTION(rayaop) { /* Module shutdown */ PHP_MSHUTDOWN_FUNCTION(rayaop) { +#ifdef ZTS + /* Free mutex */ + if (rayaop_mutex) { + tsrm_mutex_free(rayaop_mutex); + rayaop_mutex = NULL; + } +#endif + if (php_rayaop_original_execute_ex) { zend_execute_ex = php_rayaop_original_execute_ex; } From 1837515728dbaffa8276a71a0751e759a40f01bf Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 5 Nov 2024 22:45:46 +0900 Subject: [PATCH 42/43] Refactor and simplify README.md Streamlined content for clarity and conciseness. Removed security considerations, advanced usage, and extensive API reference sections. Simplified configuration instructions and emphasized basic usage and installation steps. Updated features and recommendations to be more succinct. --- README.md | 188 +++++++----------------------------------------------- 1 file changed, 23 insertions(+), 165 deletions(-) diff --git a/README.md b/README.md index 012646a..29bf13e 100644 --- a/README.md +++ b/README.md @@ -8,18 +8,17 @@ Low-level PHP extension that provides core method interception functionality for ## Features -- Method interception with comprehensive parameter access and return value modification +- Efficient low-level method interception - Support for intercepting final classes and methods +- Full parameter and return value modification support - Works seamlessly with the `new` keyword -- Thread-safe operation support in ZTS builds -- Debug mode with configurable levels -- Memory-efficient interception handling +- Thread-safe operation support ## Requirements - PHP 8.1 or higher - Linux, macOS, or Windows with appropriate build tools -- Thread-safe PHP build recommended for production use +- Thread-safe PHP build recommended for multi-threaded environments ## Installation @@ -37,15 +36,10 @@ make make install ``` -3. Add the following configuration to your php.ini file: +3. Add the following line to your php.ini file: ```ini -; Required extension loading extension=rayaop.so # For Unix/Linux extension=rayaop.dll # For Windows - -; Optional configuration -[rayaop] -rayaop.debug_level = 0 ; 0=disabled, 1=basic, 2=verbose ``` 4. Verify installation: @@ -53,23 +47,6 @@ rayaop.debug_level = 0 ; 0=disabled, 1=basic, 2=verbose php -m | grep rayaop ``` -## Security Considerations - -### Thread Safety -The extension is designed to be thread-safe and includes proper locking mechanisms for global state. When using in a threaded environment (e.g., with PHP-FPM), ensure you're using the Thread-Safe (ZTS) version of PHP. - -### Memory Management -- The extension implements careful memory management with proper allocation and deallocation -- Includes protection against memory leaks in interceptor chains -- Implements safeguards against infinite recursion -- Uses secure hash table operations for interceptor storage - -### Best Practices -1. Always validate interceptor objects before registration -2. Implement proper error handling in interceptors -3. Avoid storing sensitive data in interceptor instances -4. Set appropriate memory and recursion limits in production - ## Design Decisions This extension provides minimal, high-performance method interception capabilities: @@ -77,8 +54,7 @@ This extension provides minimal, high-performance method interception capabiliti - One interceptor per method: The extension supports a single active interceptor per method, with the last registered interceptor taking precedence - Final class support: Can intercept final classes and methods, unlike pure PHP implementations - Raw interception: No built-in matching or conditions (use Ray.Aop for these features) -- Thread-safe design: All global state is properly protected -- Zero-copy parameter passing where possible +- Thread-safe: Safe to use in multi-threaded environments like PHP-FPM ## Relationship with Ray.Aop @@ -93,76 +69,28 @@ Ray.Aop provides: When both are used together: - Ray.Aop handles the high-level AOP logic - This extension provides the low-level interception mechanism +- Ray.Aop automatically utilizes this extension when available for better performance -## Advanced Usage +## Basic Usage -### Error Handling +### Simple Interceptor ```php -class ErrorHandlingInterceptor implements Ray\Aop\MethodInterceptorInterface +class LoggingInterceptor implements Ray\Aop\MethodInterceptorInterface { public function intercept(object $object, string $method, array $params): mixed { - try { - return $object->$method(...$params); - } catch (Throwable $e) { - // Handle or transform the error - throw new CustomException("Error in {$method}: " . $e->getMessage()); - } + echo "Before {$method}\n"; + $result = $object->$method(...$params); + echo "After {$method}\n"; + return $result; } } -``` -### Parameter Modification -```php -class ParamValidatorInterceptor implements Ray\Aop\MethodInterceptorInterface -{ - public function intercept(object $object, string $method, array $params): mixed - { - // Validate and potentially modify parameters - $sanitizedParams = array_map( - fn($param) => is_string($param) ? htmlspecialchars($param) : $param, - $params - ); - - return $object->$method(...$sanitizedParams); - } -} +// Register the interceptor +method_intercept(TestClass::class, 'testMethod', new LoggingInterceptor()); ``` -## API Reference - -### Core Functions - -#### method_intercept() -```php -bool method_intercept(string $class_name, string $method_name, object $interceptor) -``` -Registers an interceptor for a specific class method. Only one interceptor can be active per method - registering a new one will replace any existing interceptor. -- **Parameters:** - - `$class_name`: Fully qualified class name to intercept - - `$method_name`: Name of the method to intercept - - `$interceptor`: Object implementing Ray\Aop\MethodInterceptorInterface -- **Returns:** bool - True on successful registration, False if registration fails -- **Errors:** May generate E_WARNING on invalid handlers or memory allocation failures - -#### method_intercept_init() -```php -bool method_intercept_init() -``` -Initializes or resets the interception system. Cleans up any existing interceptors and reinitializes the interceptor storage. -- **Returns:** bool - True if initialization succeeds, False if memory allocation fails -- **Errors:** May generate E_ERROR on memory allocation failures - -#### method_intercept_enable() -```php -void method_intercept_enable(bool $enable) -``` -Enables or disables the method interception system globally. When disabled, intercepted methods will execute normally. -- **Parameters:** - - `$enable`: True to enable the interception system, False to disable it -- **Note:** Disabling interception does not remove registered interceptors - -### Basic Configuration Example +### Method Interception Setup ```php // Initialize the interception system method_intercept_init(); @@ -170,55 +98,8 @@ method_intercept_init(); // Enable method interception method_intercept_enable(true); -// Register an interceptor -$result = method_intercept( - MyClass::class, - 'targetMethod', - new MyInterceptor() -); - -if (!$result) { - // Handle registration failure -} -``` - -## Performance Considerations - -### Memory Usage -- Each interceptor registration uses approximately 200 bytes of memory -- The extension maintains a hash table for quick interceptor lookups -- Memory usage scales linearly with the number of intercepted methods - -### Performance Considerations -- Method interception adds some overhead to each method call -- The extension implements efficient parameter handling -- Interceptor lookup is optimized using hash tables -- Execution depth is tracked to prevent infinite recursion - - -## Troubleshooting - -### Common Issues - -1. Segmentation Faults - - Ensure Thread-Safe PHP in multi-threaded environments - - Check memory limits and recursion depth - - Verify interceptor object validity - -2. Memory Leaks - - Implement proper cleanup in interceptors - - Use method_intercept_init() when necessary - - Monitor memory usage with debug_level = 1 - -3. Performance Issues - - Review interceptor complexity - - Check recursion depth settings - - Monitor interception patterns - -### Debug Mode -Enable debug mode for detailed logging: -```ini -rayaop.debug_level = 2 +// Register interceptors +method_intercept(MyClass::class, 'myMethod', new MyInterceptor()); ``` ## Development @@ -242,35 +123,12 @@ For specific tests: make test TESTS="-v tests/your_specific_test.phpt" ``` -### Debug Builds -```bash -./configure --enable-debug -make clean -make -``` - -## Contributing - -1. Fork the repository -2. Create your feature branch (`git checkout -b feature/amazing-feature`) -3. Add appropriate tests for your changes -4. Ensure all tests pass (`make test`) -5. Run memory leak checks (`make test TESTS="-m") -6. Commit your changes -7. Push to the branch -8. Create a Pull Request - -## Support and Feedback - -- Issues: Report bugs via GitHub Issues -- Questions: Use GitHub Discussions -- Security: Report security issues directly to maintainers -- Performance: Share benchmarks and optimization suggestions - ## License [MIT License](LICENSE) -## Credits +## Author + +Akihito Koriyama -- Akihito Koriyama \ No newline at end of file +This extension was developed with the assistance of AI pair programming, which helped navigate the complexities of PHP extension development and PECL standards. From f50ea2f077cece1355a487fa7f34101fcfc102c9 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 5 Nov 2024 22:49:35 +0900 Subject: [PATCH 43/43] Add Acknowledgments section to README This update thanks JetBrains for their CLion IDE, highlighting its free availability under an Open Source License. The acknowledgment emphasizes the tool's role in enhancing project development. --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 29bf13e..ba9356a 100644 --- a/README.md +++ b/README.md @@ -132,3 +132,9 @@ make test TESTS="-v tests/your_specific_test.phpt" Akihito Koriyama This extension was developed with the assistance of AI pair programming, which helped navigate the complexities of PHP extension development and PECL standards. + +## Acknowledgments + +This project was created using [JetBrains CLion](https://www.jetbrains.com/clion/), which is available for free with an [Open Source License](https://www.jetbrains.com/community/opensource/). + +We'd like to express our gratitude to JetBrains for providing such a powerful and user-friendly development environment. \ No newline at end of file