Skip to content

Commit 1099f17

Browse files
feat(agent): Drupal hook attribute instrumentation (#1030)
Adds support for Drupal Attribute Hooks added in Drupal 11.1.
1 parent f6b2914 commit 1099f17

15 files changed

+620
-0
lines changed

agent/fw_drupal8.c

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,95 @@ NR_PHP_WRAPPER(nr_drupal94_invoke_all_with_clean) {
608608
NR_PHP_WRAPPER_END
609609
#endif // OAPI
610610

611+
static bool nr_is_invalid_key_val_arr(nr_php_string_hash_key_t* key,
612+
zval* val) {
613+
if (NULL == key || 0 == ZEND_STRING_LEN(key)
614+
|| 0 == nr_php_is_zval_valid_array(val)
615+
|| 0 == zend_hash_num_elements(Z_ARRVAL_P(val))) {
616+
return true;
617+
} else {
618+
return false;
619+
}
620+
}
621+
622+
/*
623+
* Purpose: Instrument Drupal Attribute Hooks for Drupal 11.1+
624+
*
625+
* Params: 1. A zval pointer to the moduleHandler instance in use by Drupal.
626+
*
627+
* Return: bool
628+
*
629+
*/
630+
static bool nr_drupal_hook_attribute_instrument(zval* module_handler) {
631+
zval* hook_implementation_map = NULL;
632+
633+
nr_php_string_hash_key_t* hook_key = NULL;
634+
zval* hook_val = NULL;
635+
nr_php_string_hash_key_t* class_key = NULL;
636+
zval* class_val = NULL;
637+
nr_php_string_hash_key_t* method_key = NULL;
638+
zval* module_val = NULL;
639+
640+
char* hookpath = NULL;
641+
642+
hook_implementation_map = nr_php_get_zval_object_property(
643+
module_handler, "hookImplementationsMap");
644+
645+
if (!nr_php_is_zval_valid_array(hook_implementation_map)) {
646+
nrl_verbosedebug(NRL_FRAMEWORK,
647+
"hookImplementationsMap property not a valid array");
648+
return false;
649+
}
650+
651+
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(hook_implementation_map), hook_key,
652+
hook_val) {
653+
if (nr_is_invalid_key_val_arr(hook_key, hook_val)) {
654+
nrl_warning(NRL_FRAMEWORK,
655+
"hookImplementationsMap[hook]: invalid key or value");
656+
return false;
657+
}
658+
659+
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(hook_val), class_key, class_val) {
660+
if (nr_is_invalid_key_val_arr(class_key, class_val)) {
661+
nrl_warning(NRL_FRAMEWORK,
662+
"hookImplementationsMap[class]: invalid key or value");
663+
return false;
664+
}
665+
666+
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(class_val), method_key,
667+
module_val) {
668+
if (NULL == method_key
669+
|| 0 == nr_php_is_zval_valid_string(module_val)) {
670+
nrl_warning(NRL_FRAMEWORK,
671+
"hookImplementationsMap[method]: invalid key or value");
672+
return false;
673+
}
674+
675+
if (nr_striendswith(
676+
ZEND_STRING_VALUE(class_key), ZEND_STRING_LEN(class_key),
677+
NR_PSTR("Drupal\\Core\\Extension\\ProceduralCall"))) {
678+
hookpath = nr_formatf("%s", ZEND_STRING_VALUE(method_key));
679+
} else {
680+
hookpath = nr_formatf("%s::%s", ZEND_STRING_VALUE(class_key),
681+
ZEND_STRING_VALUE(method_key));
682+
}
683+
684+
nr_php_wrap_user_function_drupal(
685+
hookpath, nr_strlen(hookpath), Z_STRVAL_P(module_val),
686+
Z_STRLEN_P(module_val), ZEND_STRING_VALUE(hook_key),
687+
ZEND_STRING_LEN(hook_key));
688+
689+
nr_free(hookpath);
690+
}
691+
ZEND_HASH_FOREACH_END();
692+
}
693+
ZEND_HASH_FOREACH_END();
694+
}
695+
ZEND_HASH_FOREACH_END();
696+
697+
return true;
698+
}
699+
611700
/*
612701
* Purpose : Wrap the invoke() method of the module handler instance in use.
613702
*/
@@ -635,6 +724,9 @@ NR_PHP_WRAPPER(nr_drupal8_module_handler) {
635724

636725
ce = Z_OBJCE_P(*retval_ptr);
637726

727+
if (nr_drupal_hook_attribute_instrument(*retval_ptr)) {
728+
NR_PHP_WRAPPER_LEAVE;
729+
}
638730
nr_drupal8_add_method_callback(ce, NR_PSTR("getimplementations"),
639731
nr_drupal8_post_get_implementations TSRMLS_CC);
640732
nr_drupal8_add_method_callback(ce, NR_PSTR("implementshook"),
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
/*
3+
* Copyright 2020 New Relic Corporation. All rights reserved.
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
/* Verify agent behavior when key value is an empty array */
8+
9+
namespace Drupal\Core\Extension {
10+
interface ModuleHandlerInterface
11+
{
12+
public function invokeAllWith($hook_str, $callback);
13+
}
14+
class ModuleHandler implements ModuleHandlerInterface
15+
{
16+
protected array $hookImplementationsMap = array(
17+
'hookname' => array('classname' => array('methodname' => 'modulename')),
18+
'hookname_b' => array(),
19+
'hookname_c' => array('classname_c' => array('methodname_c' => 'modulename_c')),
20+
);
21+
22+
// to avoid editor warnings
23+
public function invokeAllWith($hook_str, $callback)
24+
{
25+
return null;
26+
}
27+
28+
// for debugging purposes
29+
public function dump()
30+
{
31+
var_dump($this->hookImplementationsMap);
32+
}
33+
}
34+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
/*
3+
* Copyright 2020 New Relic Corporation. All rights reserved.
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
/* Verify agent behavior when key is an empty string */
8+
9+
namespace Drupal\Core\Extension {
10+
interface ModuleHandlerInterface
11+
{
12+
public function invokeAllWith($hook_str, $callback);
13+
}
14+
class ModuleHandler implements ModuleHandlerInterface
15+
{
16+
protected array $hookImplementationsMap = array(
17+
'hookname' => array('classname' => array('methodname' => 'modulename')),
18+
'hookname_b' => array('' => array('methodname_b' => 'modulename_b')),
19+
'hookname_c' => array('classname_c' => array('methodname_c' => 'modulename_c')),
20+
);
21+
22+
// to avoid editor warnings
23+
public function invokeAllWith($hook_str, $callback)
24+
{
25+
return null;
26+
}
27+
28+
// for debugging purposes
29+
public function dump()
30+
{
31+
var_dump($this->hookImplementationsMap);
32+
}
33+
}
34+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
/*
3+
* Copyright 2020 New Relic Corporation. All rights reserved.
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
/* Verify agent behavior when map key is not a string */
8+
9+
namespace Drupal\Core\Extension {
10+
interface ModuleHandlerInterface
11+
{
12+
public function invokeAllWith($hook_str, $callback);
13+
}
14+
class ModuleHandler implements ModuleHandlerInterface
15+
{
16+
protected array $hookImplementationsMap = array(
17+
'hookname' => array('classname' => array('methodname' => 'modulename')),
18+
1 => array('classname_b' => array('methodname_b' => 'modulename_b')),
19+
'hookname_c' => array('classname_c', array('methodname_c', 'modulename_c')),
20+
);
21+
22+
// to avoid editor warnings
23+
public function invokeAllWith($hook_str, $callback)
24+
{
25+
return null;
26+
}
27+
28+
// for debugging purposes
29+
public function dump()
30+
{
31+
var_dump($this->hookImplementationsMap);
32+
}
33+
}
34+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
/*
3+
* Copyright 2020 New Relic Corporation. All rights reserved.
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
/* Verify agent behavior when hookImplemementationsMap is not an array */
8+
9+
namespace Drupal\Core\Extension {
10+
interface ModuleHandlerInterface
11+
{
12+
public function invokeAllWith($hook_str, $callback);
13+
}
14+
class ModuleHandler implements ModuleHandlerInterface
15+
{
16+
protected string $hookImplementationsMap = 'just a string';
17+
18+
// to avoid editor warnings
19+
public function invokeAllWith($hook_str, $callback)
20+
{
21+
return null;
22+
}
23+
24+
// for debugging purposes
25+
public function dump()
26+
{
27+
var_dump($this->hookImplementationsMap);
28+
}
29+
}
30+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
/*
3+
* Copyright 2020 New Relic Corporation. All rights reserved.
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
/* Verify agent behavior when module name is not a string */
8+
9+
namespace Drupal\Core\Extension {
10+
interface ModuleHandlerInterface
11+
{
12+
public function invokeAllWith($hook_str, $callback);
13+
}
14+
class ModuleHandler implements ModuleHandlerInterface
15+
{
16+
protected array $hookImplementationsMap = array(
17+
'hookname' => array('classname' => array('methodname' => 'modulename')),
18+
'hookname_b' => array('classname_b' => array('methodname_b' => array(1, 2, 3))),
19+
'hookname_c' => array('classname_c' => array('methodname_c' => 'modulename_c')),
20+
);
21+
22+
// to avoid editor warnings
23+
public function invokeAllWith($hook_str, $callback)
24+
{
25+
return null;
26+
}
27+
28+
// for debugging purposes
29+
public function dump()
30+
{
31+
var_dump($this->hookImplementationsMap);
32+
}
33+
}
34+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
/*
3+
* Copyright 2020 New Relic Corporation. All rights reserved.
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
/* Verify agent behavior when key value is not an array */
8+
9+
namespace Drupal\Core\Extension {
10+
interface ModuleHandlerInterface
11+
{
12+
public function invokeAllWith($hook_str, $callback);
13+
}
14+
class ModuleHandler implements ModuleHandlerInterface
15+
{
16+
protected array $hookImplementationsMap = array(
17+
'hookname' => array('classname' => array('methodname' => 'modulename')),
18+
'hookname_b' => array('classname_b' => 'just a string'),
19+
'hookname_c' => array('classname_c' => array('methodname_c' => 'modulename_c')),
20+
);
21+
22+
// to avoid editor warnings
23+
public function invokeAllWith($hook_str, $callback)
24+
{
25+
return null;
26+
}
27+
28+
// for debugging purposes
29+
public function dump()
30+
{
31+
var_dump($this->hookImplementationsMap);
32+
}
33+
}
34+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
/*
3+
* Copyright 2020 New Relic Corporation. All rights reserved.
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
/* Verify agent behavior on valid hookImplementationsMap */
8+
9+
namespace Drupal\Core\Extension {
10+
interface ModuleHandlerInterface
11+
{
12+
public function invokeAllWith($hook_str, $callback);
13+
}
14+
class ModuleHandler implements ModuleHandlerInterface
15+
{
16+
protected array $hookImplementationsMap = array(
17+
'hookname' => array('classname' => array('methodname' => 'modulename')),
18+
'hookname_b' => array('classname_b' => array('methodname_b' => 'modulename_b')),
19+
'hookname_c' => array('classname_c' => array('methodname_c' => 'modulename_c')),
20+
);
21+
22+
// to avoid editor warnings
23+
public function invokeAllWith($hook_str, $callback)
24+
{
25+
return null;
26+
}
27+
28+
// for debugging purposes
29+
public function dump()
30+
{
31+
var_dump($this->hookImplementationsMap);
32+
}
33+
}
34+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
/*
3+
* Copyright 2020 New Relic Corporation. All rights reserved.
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
/*DESCRIPTION
8+
Verify agent behavior when key value is an empty array
9+
*/
10+
11+
/*SKIPIF
12+
<?php
13+
if (version_compare(PHP_VERSION, '7.4', '<')) {
14+
die("skip: PHP >= 7.4 required\n");
15+
}
16+
*/
17+
18+
/*INI
19+
newrelic.framework = drupal8
20+
*/
21+
22+
/*EXPECT_TRACED_ERRORS null */
23+
24+
/*EXPECT_ERROR_EVENTS null */
25+
26+
/*EXPECT
27+
*/
28+
29+
require_once __DIR__ . '/mock_module_handler_empty_array.php';
30+
31+
// This specific API is needed for us to instrument the ModuleHandler
32+
class Drupal
33+
{
34+
public function moduleHandler()
35+
{
36+
return new Drupal\Core\Extension\ModuleHandler();
37+
}
38+
}
39+
40+
// Create module handler
41+
$drupal = new Drupal();
42+
$handler = $drupal->moduleHandler();

0 commit comments

Comments
 (0)