diff --git a/agent/fw_drupal8.c b/agent/fw_drupal8.c index 1ed5d2fcb..5b2e3031e 100644 --- a/agent/fw_drupal8.c +++ b/agent/fw_drupal8.c @@ -605,6 +605,94 @@ NR_PHP_WRAPPER(nr_drupal94_invoke_all_with_clean) { NR_PHP_WRAPPER_END #endif // OAPI +static bool nr_validate_key_val_arr(nr_php_string_hash_key_t* key, zval* val) { + if (NULL == key || 0 == ZEND_STRING_LEN(key) + || 0 == nr_php_is_zval_valid_array(val) + || 0 == zend_hash_num_elements(Z_ARRVAL_P(val))) { + return true; + } else { + return false; + } +} + +/* + * Purpose: Instrument Drupal Attribute Hooks for Drupal 11.1+ + * + * Params: 1. A zval pointer to the moduleHandler instance in use by Drupal. + * + * Return: bool + * + */ +static bool nr_drupal_hook_attribute_instrument(zval* module_handler) { + zval* hook_implementation_map = NULL; + + nr_php_string_hash_key_t* hook_key = NULL; + zval* hook_val = NULL; + nr_php_string_hash_key_t* class_key = NULL; + zval* class_val = NULL; + nr_php_string_hash_key_t* method_key = NULL; + zval* module_val = NULL; + + char* hookpath = NULL; + + hook_implementation_map = nr_php_get_zval_object_property( + module_handler, "hookImplementationsMap"); + + if (!nr_php_is_zval_valid_array(hook_implementation_map)) { + nrl_warning(NRL_FRAMEWORK, + "hookImplementationsMap property not a valid array"); + return false; + } + + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(hook_implementation_map), hook_key, + hook_val) { + if (nr_validate_key_val_arr(hook_key, hook_val)) { + nrl_warning(NRL_FRAMEWORK, + "hookImplementationsMap[hook]: invalid key or value"); + return false; + } + + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(hook_val), class_key, class_val) { + if (nr_validate_key_val_arr(class_key, class_val)) { + nrl_warning(NRL_FRAMEWORK, + "hookImplementationsMap[class]: invalid key or value"); + return false; + } + + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(class_val), method_key, + module_val) { + if (NULL == method_key + || 0 == nr_php_is_zval_valid_string(module_val)) { + nrl_warning(NRL_FRAMEWORK, + "hookImplementationsMap[method]: invalid key or value"); + return false; + } + + if (0 + == nr_stricmp(ZEND_STRING_VALUE(class_key), + "Drupal\\Core\\Extension\\ProceduralCall")) { + hookpath = nr_formatf("%s", ZEND_STRING_VALUE(method_key)); + } else { + hookpath = nr_formatf("%s::%s", ZEND_STRING_VALUE(class_key), + ZEND_STRING_VALUE(method_key)); + } + + nr_php_wrap_user_function_drupal( + hookpath, nr_strlen(hookpath), Z_STRVAL_P(module_val), + Z_STRLEN_P(module_val), ZEND_STRING_VALUE(hook_key), + ZEND_STRING_LEN(hook_key)); + + nr_free(hookpath); + } + ZEND_HASH_FOREACH_END(); + } + ZEND_HASH_FOREACH_END(); + } + ZEND_HASH_FOREACH_END(); + + return true; +} + /* * Purpose : Wrap the invoke() method of the module handler instance in use. */ @@ -632,6 +720,9 @@ NR_PHP_WRAPPER(nr_drupal8_module_handler) { ce = Z_OBJCE_P(*retval_ptr); + if (nr_drupal_hook_attribute_instrument(*retval_ptr)) { + NR_PHP_WRAPPER_LEAVE; + } nr_drupal8_add_method_callback(ce, NR_PSTR("getimplementations"), nr_drupal8_post_get_implementations TSRMLS_CC); nr_drupal8_add_method_callback(ce, NR_PSTR("implementshook"), diff --git a/tests/integration/frameworks/drupal/mock_module_handler_empty_array.php b/tests/integration/frameworks/drupal/mock_module_handler_empty_array.php new file mode 100644 index 000000000..0cf5db0a8 --- /dev/null +++ b/tests/integration/frameworks/drupal/mock_module_handler_empty_array.php @@ -0,0 +1,34 @@ + array('classname' => array('methodname' => 'modulename')), + 'hookname_b' => array(), + 'hookname_c' => array('classname_c' => array('methodname_c' => 'modulename_c')), + ); + + // to avoid editor warnings + public function invokeAllWith($hook_str, $callback) + { + return null; + } + + // for debugging purposes + public function dump() + { + var_dump($this->hookImplementationsMap); + } + } +} diff --git a/tests/integration/frameworks/drupal/mock_module_handler_empty_key.php b/tests/integration/frameworks/drupal/mock_module_handler_empty_key.php new file mode 100644 index 000000000..3f057711e --- /dev/null +++ b/tests/integration/frameworks/drupal/mock_module_handler_empty_key.php @@ -0,0 +1,34 @@ + array('classname' => array('methodname' => 'modulename')), + 'hookname_b' => array('' => array('methodname_b' => 'modulename_b')), + 'hookname_c' => array('classname_c' => array('methodname_c' => 'modulename_c')), + ); + + // to avoid editor warnings + public function invokeAllWith($hook_str, $callback) + { + return null; + } + + // for debugging purposes + public function dump() + { + var_dump($this->hookImplementationsMap); + } + } +} diff --git a/tests/integration/frameworks/drupal/mock_module_handler_invalid_key.php b/tests/integration/frameworks/drupal/mock_module_handler_invalid_key.php new file mode 100644 index 000000000..faffb7ded --- /dev/null +++ b/tests/integration/frameworks/drupal/mock_module_handler_invalid_key.php @@ -0,0 +1,34 @@ + array('classname' => array('methodname' => 'modulename')), + 1 => array('classname_b' => array('methodname_b' => 'modulename_b')), + 'hookname_c' => array('classname_c', array('methodname_c', 'modulename_c')), + ); + + // to avoid editor warnings + public function invokeAllWith($hook_str, $callback) + { + return null; + } + + // for debugging purposes + public function dump() + { + var_dump($this->hookImplementationsMap); + } + } +} diff --git a/tests/integration/frameworks/drupal/mock_module_handler_invalid_map.php b/tests/integration/frameworks/drupal/mock_module_handler_invalid_map.php new file mode 100644 index 000000000..ac61d62bf --- /dev/null +++ b/tests/integration/frameworks/drupal/mock_module_handler_invalid_map.php @@ -0,0 +1,30 @@ +hookImplementationsMap); + } + } +} diff --git a/tests/integration/frameworks/drupal/mock_module_handler_invalid_module.php b/tests/integration/frameworks/drupal/mock_module_handler_invalid_module.php new file mode 100644 index 000000000..d6fa753aa --- /dev/null +++ b/tests/integration/frameworks/drupal/mock_module_handler_invalid_module.php @@ -0,0 +1,34 @@ + array('classname' => array('methodname' => 'modulename')), + 'hookname_b' => array('classname_b' => array('methodname_b' => array(1, 2, 3))), + 'hookname_c' => array('classname_c' => array('methodname_c' => 'modulename_c')), + ); + + // to avoid editor warnings + public function invokeAllWith($hook_str, $callback) + { + return null; + } + + // for debugging purposes + public function dump() + { + var_dump($this->hookImplementationsMap); + } + } +} diff --git a/tests/integration/frameworks/drupal/mock_module_handler_invalid_value.php b/tests/integration/frameworks/drupal/mock_module_handler_invalid_value.php new file mode 100644 index 000000000..cdec24a98 --- /dev/null +++ b/tests/integration/frameworks/drupal/mock_module_handler_invalid_value.php @@ -0,0 +1,34 @@ + array('classname' => array('methodname' => 'modulename')), + 'hookname_b' => array('classname_b' => 'just a string'), + 'hookname_c' => array('classname_c' => array('methodname_c' => 'modulename_c')), + ); + + // to avoid editor warnings + public function invokeAllWith($hook_str, $callback) + { + return null; + } + + // for debugging purposes + public function dump() + { + var_dump($this->hookImplementationsMap); + } + } +} diff --git a/tests/integration/frameworks/drupal/mock_module_handler_valid.php b/tests/integration/frameworks/drupal/mock_module_handler_valid.php new file mode 100644 index 000000000..a17bb4d2b --- /dev/null +++ b/tests/integration/frameworks/drupal/mock_module_handler_valid.php @@ -0,0 +1,34 @@ + array('classname' => array('methodname' => 'modulename')), + 'hookname_b' => array('classname_b' => array('methodname_b' => 'modulename_b')), + 'hookname_c' => array('classname_c' => array('methodname_c' => 'modulename_c')), + ); + + // to avoid editor warnings + public function invokeAllWith($hook_str, $callback) + { + return null; + } + + // for debugging purposes + public function dump() + { + var_dump($this->hookImplementationsMap); + } + } +} diff --git a/tests/integration/frameworks/drupal/test_hook_implementations_map_empty_array.php b/tests/integration/frameworks/drupal/test_hook_implementations_map_empty_array.php new file mode 100644 index 000000000..997dd7b0c --- /dev/null +++ b/tests/integration/frameworks/drupal/test_hook_implementations_map_empty_array.php @@ -0,0 +1,42 @@ += 7.4 required\n"); +} +*/ + +/*INI +newrelic.framework = drupal8 +*/ + +/*EXPECT_TRACED_ERRORS null */ + +/*EXPECT_ERROR_EVENTS null */ + +/*EXPECT +*/ + +require_once __DIR__ . '/mock_module_handler_empty_array.php'; + +// This specific API is needed for us to instrument the ModuleHandler +class Drupal +{ + public function moduleHandler() + { + return new Drupal\Core\Extension\ModuleHandler(); + } +} + +// Create module handler +$drupal = new Drupal(); +$handler = $drupal->moduleHandler(); diff --git a/tests/integration/frameworks/drupal/test_hook_implementations_map_empty_key.php b/tests/integration/frameworks/drupal/test_hook_implementations_map_empty_key.php new file mode 100644 index 000000000..1ee7a2400 --- /dev/null +++ b/tests/integration/frameworks/drupal/test_hook_implementations_map_empty_key.php @@ -0,0 +1,42 @@ += 7.4 required\n"); +} +*/ + +/*INI +newrelic.framework = drupal8 +*/ + +/*EXPECT_TRACED_ERRORS null */ + +/*EXPECT_ERROR_EVENTS null */ + +/*EXPECT +*/ + +require_once __DIR__ . '/mock_module_handler_empty_key.php'; + +// This specific API is needed for us to instrument the ModuleHandler +class Drupal +{ + public function moduleHandler() + { + return new Drupal\Core\Extension\ModuleHandler(); + } +} + +// Create module handler +$drupal = new Drupal(); +$handler = $drupal->moduleHandler(); diff --git a/tests/integration/frameworks/drupal/test_hook_implementations_map_invalid_key.php b/tests/integration/frameworks/drupal/test_hook_implementations_map_invalid_key.php new file mode 100644 index 000000000..ac60f928f --- /dev/null +++ b/tests/integration/frameworks/drupal/test_hook_implementations_map_invalid_key.php @@ -0,0 +1,42 @@ += 7.4 required\n"); +} +*/ + +/*INI +newrelic.framework = drupal8 +*/ + +/*EXPECT_TRACED_ERRORS null */ + +/*EXPECT_ERROR_EVENTS null */ + +/*EXPECT +*/ + +require_once __DIR__ . '/mock_module_handler_invalid_key.php'; + +// This specific API is needed for us to instrument the ModuleHandler +class Drupal +{ + public function moduleHandler() + { + return new Drupal\Core\Extension\ModuleHandler(); + } +} + +// Create module handler +$drupal = new Drupal(); +$handler = $drupal->moduleHandler(); diff --git a/tests/integration/frameworks/drupal/test_hook_implementations_map_invalid_map.php b/tests/integration/frameworks/drupal/test_hook_implementations_map_invalid_map.php new file mode 100644 index 000000000..a55a2306b --- /dev/null +++ b/tests/integration/frameworks/drupal/test_hook_implementations_map_invalid_map.php @@ -0,0 +1,42 @@ += 7.4 required\n"); +} +*/ + +/*INI +newrelic.framework = drupal8 +*/ + +/*EXPECT_TRACED_ERRORS null */ + +/*EXPECT_ERROR_EVENTS null */ + +/*EXPECT +*/ + +require_once __DIR__ . '/mock_module_handler_invalid_map.php'; + +// This specific API is needed for us to instrument the ModuleHandler +class Drupal +{ + public function moduleHandler() + { + return new Drupal\Core\Extension\ModuleHandler(); + } +} + +// Create module handler +$drupal = new Drupal(); +$handler = $drupal->moduleHandler(); diff --git a/tests/integration/frameworks/drupal/test_hook_implementations_map_invalid_module.php b/tests/integration/frameworks/drupal/test_hook_implementations_map_invalid_module.php new file mode 100644 index 000000000..3a9b9c529 --- /dev/null +++ b/tests/integration/frameworks/drupal/test_hook_implementations_map_invalid_module.php @@ -0,0 +1,42 @@ += 7.4 required\n"); +} +*/ + +/*INI +newrelic.framework = drupal8 +*/ + +/*EXPECT_TRACED_ERRORS null */ + +/*EXPECT_ERROR_EVENTS null */ + +/*EXPECT +*/ + +require_once __DIR__ . '/mock_module_handler_invalid_module.php'; + +// This specific API is needed for us to instrument the ModuleHandler +class Drupal +{ + public function moduleHandler() + { + return new Drupal\Core\Extension\ModuleHandler(); + } +} + +// Create module handler +$drupal = new Drupal(); +$handler = $drupal->moduleHandler(); diff --git a/tests/integration/frameworks/drupal/test_hook_implementations_map_invalid_value.php b/tests/integration/frameworks/drupal/test_hook_implementations_map_invalid_value.php new file mode 100644 index 000000000..892bc533a --- /dev/null +++ b/tests/integration/frameworks/drupal/test_hook_implementations_map_invalid_value.php @@ -0,0 +1,42 @@ += 7.4 required\n"); +} +*/ + +/*INI +newrelic.framework = drupal8 +*/ + +/*EXPECT_TRACED_ERRORS null */ + +/*EXPECT_ERROR_EVENTS null */ + +/*EXPECT +*/ + +require_once __DIR__ . '/mock_module_handler_invalid_value.php'; + +// This specific API is needed for us to instrument the ModuleHandler +class Drupal +{ + public function moduleHandler() + { + return new Drupal\Core\Extension\ModuleHandler(); + } +} + +// Create module handler +$drupal = new Drupal(); +$handler = $drupal->moduleHandler(); diff --git a/tests/integration/frameworks/drupal/test_hook_implementations_map_valid.php b/tests/integration/frameworks/drupal/test_hook_implementations_map_valid.php new file mode 100644 index 000000000..afe54c69c --- /dev/null +++ b/tests/integration/frameworks/drupal/test_hook_implementations_map_valid.php @@ -0,0 +1,42 @@ += 7.4 required\n"); +} +*/ + +/*INI +newrelic.framework = drupal8 +*/ + +/*EXPECT_TRACED_ERRORS null */ + +/*EXPECT_ERROR_EVENTS null */ + +/*EXPECT +*/ + +require_once __DIR__ . '/mock_module_handler_valid.php'; + +// This specific API is needed for us to instrument the ModuleHandler +class Drupal +{ + public function moduleHandler() + { + return new Drupal\Core\Extension\ModuleHandler(); + } +} + +// Create module handler +$drupal = new Drupal(); +$handler = $drupal->moduleHandler();