Skip to content

Commit d6db53b

Browse files
authored
fix(agent): address the aws-sdk-php versioning change (#1001)
Concerns about future/past proofing to the checking prioritized the non-eval implementation vs using the eval method for checking the version. However, aws-sdk-php internal functions frequently change without notice in the release notes. An extra eval is used to avoid the fatal error that would occur in the VERY unlikely event of Aws/Sdk.php not being found and which addresses the previous concerns while decoupling versioning from internal functions.
1 parent aae7611 commit d6db53b

File tree

1 file changed

+43
-57
lines changed

1 file changed

+43
-57
lines changed

agent/lib_aws_sdk_php.c

Lines changed: 43 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -22,49 +22,40 @@
2222
* In a normal course of events, the following line will always work
2323
* zend_eval_string("Aws\\Sdk::VERSION;", &retval, "Get AWS Version")
2424
* By the time we have detected the existence of the aws-sdk-php and with
25-
* default composer profject settings, it callable even from
25+
* default composer project settings, it is callable even from
2626
* nr_aws_sdk_php_enable which will automatically load the class if it isn't
27-
* loaded yet and then evaluate the string. However, in the rare case that files
27+
* loaded yet and then evaluate the string. In the rare case that files
2828
* are not loaded via autoloader and/or have non-default composer classload
29-
* settings, if the class is not found, PHP 8.2+ will generate a fatal
30-
* unrecoverable uncatchable error error whenever it cannot find a class. While
31-
* calling this from nr_aws_sdk_php_enable would have been great and would allow
32-
* the sdk version value to be set only once, to avoid the very unlikely but not
33-
* impossible fatal error, this will be called from the
34-
* "Aws\\ClientResolver::_apply_user_agent" wrapper which GUARANTEES that
35-
* aws/sdk exists and is already loaded.
36-
*
37-
*
38-
* Additionally given that aws-sdk-php is currently detected from the
39-
* AwsClient.php file, this method will always be called when a client is
40-
* created unlike Sdk::construct which doesn't show with PHP 8.2+.
41-
*
42-
* Using Aws/Sdk::__construct for version is currently nonviable as it is
43-
* unreliable as a version determiner.
44-
* Having separate functionality to extract from Aws/Sdk::__construct
45-
* is both not required and is redundant and causes additional overhead and
46-
* so only one function is needed to extract version.
47-
*
48-
* Aws\\ClientResolver::_apply_user_agent a reliable function as it is
49-
* always called on client initialization since it is key to populating
50-
* the request headers, and it loads Sdk by default.
51-
*
52-
* Concerns about future/past proofing to the checking prioritized the following
53-
* implementation vs using the eval method.
29+
* settings, if the class is not found, PHP 8.2+ will generate an
30+
* error whenever it cannot find a class which must be caught. Calling this
31+
* from nr_aws_sdk_php_enable would allow the sdk version value to be set only
32+
* once. To avoid the VERY unlikely but not impossible fatal error, we need to
33+
* wrap the call in a try/catch block and make it a lambda so that we avoid
34+
* fatal errors.
5435
*/
5536
void nr_lib_aws_sdk_php_handle_version() {
56-
zval* zval_version = NULL;
57-
zend_class_entry* class_entry = NULL;
5837
char* version = NULL;
59-
60-
class_entry = nr_php_find_class("aws\\sdk");
61-
if (NULL != class_entry) {
62-
zval_version = nr_php_get_class_constant(class_entry, "VERSION");
63-
64-
if (nr_php_is_zval_non_empty_string(zval_version)) {
65-
version = Z_STRVAL_P(zval_version);
38+
zval retval;
39+
int result = FAILURE;
40+
41+
result = zend_eval_string(
42+
"(function() {"
43+
" $nr_aws_sdk_version = '';"
44+
" try {"
45+
" $nr_aws_sdk_version = Aws\\Sdk::VERSION;"
46+
" } catch (Throwable $e) {"
47+
" }"
48+
" return $nr_aws_sdk_version;"
49+
"})();",
50+
&retval, "Get nr_aws_sdk_version");
51+
52+
/* See if we got a non-empty/non-null string for version. */
53+
if (SUCCESS == result) {
54+
if (nr_php_is_zval_non_empty_string(&retval)) {
55+
version = Z_STRVAL(retval);
6656
}
6757
}
58+
6859
if (NRINI(vulnerability_management_package_detection_enabled)) {
6960
/* Add php package to transaction */
7061
nr_txn_add_php_package(NRPRG(txn), PHP_PACKAGE_NAME, version);
@@ -73,7 +64,7 @@ void nr_lib_aws_sdk_php_handle_version() {
7364
nr_txn_suggest_package_supportability_metric(NRPRG(txn), PHP_PACKAGE_NAME,
7465
version);
7566

76-
nr_php_zval_free(&zval_version);
67+
zval_dtor(&retval);
7768
}
7869

7970
void nr_lib_aws_sdk_php_add_supportability_service_metric(
@@ -96,13 +87,6 @@ void nr_lib_aws_sdk_php_add_supportability_service_metric(
9687
nrm_force_add(NRPRG(txn) ? NRTXN(unscoped_metrics) : 0, buf, 0);
9788
}
9889

99-
NR_PHP_WRAPPER(nr_create_aws_sdk_version_metrics) {
100-
(void)wraprec;
101-
NR_PHP_WRAPPER_CALL;
102-
nr_lib_aws_sdk_php_handle_version();
103-
}
104-
NR_PHP_WRAPPER_END
105-
10690
/*
10791
* AwsClient::parseClass
10892
* This is called from the base AwsClient class for every client associated
@@ -144,7 +128,7 @@ NR_PHP_WRAPPER_END
144128
* optimizable library. Small overhead incurred when encountering an autoload
145129
* file, but detects aws-sdk-php immediately before any sdk code executes
146130
* (changes needed for this are detailed in the original PR)
147-
* 2. use a file that gets called later and only when AwsClient.php file file is
131+
* 2. use a file that gets called later and only when AwsClient.php file is
148132
* called. It's called later and we'll miss some instrumentation, but if we're
149133
* only ever going to be interested in Client calls anyway, maybe that's ok?
150134
* Doesn't detect Sdk.php (optimized out) so when customers only use that or
@@ -158,22 +142,24 @@ NR_PHP_WRAPPER_END
158142
* to wrap, this will add overhead to every hash map lookup. Currently
159143
* implemented option is 2, use the AwsClient.php as this is our main focus.
160144
* This means until a call to an Aws/AwsClient function,
161-
* all calls including aws\sdk calls are ignored. Version detection will be
162-
* tied to Aws/ClientResolver::_apply_user_agent which is ALWAYS called when
163-
* dealing with aws clients. It will not be computed from
164-
* Aws/Sdk::__constructor which would at best be duplicate info and worst would
165-
* never be ignored until a client is called.
145+
* all calls including aws\sdk calls are ignored.
146+
*
147+
* Version detection will be called directly from Aws\Sdk.php
166148
*/
167149
void nr_aws_sdk_php_enable() {
168-
/* This will be used to extract the version. */
169-
nr_php_wrap_user_function(NR_PSTR("Aws\\ClientResolver::_apply_user_agent"),
170-
nr_create_aws_sdk_version_metrics);
171-
/* Called when initializing all other Clients */
172-
nr_php_wrap_user_function(NR_PSTR("Aws\\AwsClient::parseClass"),
173-
nr_create_aws_service_metric);
174-
150+
/*
151+
* Set the UNKNOWN package first, so it doesn't overwrite what we find with
152+
* nr_lib_aws_sdk_php_handle_version.
153+
*/
175154
if (NRINI(vulnerability_management_package_detection_enabled)) {
176155
nr_txn_add_php_package(NRPRG(txn), PHP_PACKAGE_NAME,
177156
PHP_PACKAGE_VERSION_UNKNOWN);
178157
}
158+
159+
/* Extract the version for aws-sdk 3+ */
160+
nr_lib_aws_sdk_php_handle_version();
161+
162+
/* Called when initializing all Clients */
163+
nr_php_wrap_user_function(NR_PSTR("Aws\\AwsClient::parseClass"),
164+
nr_create_aws_service_metric);
179165
}

0 commit comments

Comments
 (0)