agent/native/ext/AST_instrumentation.cpp (1,051 lines of code) (raw):
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
extern "C" {
#include <Zend/zend_ast.h>
}
#include "AST_instrumentation.h"
#include "ConfigSnapshot.h"
#include "ConfigManager.h"
#include "log.h"
#include "AST_debug.h"
#include <stdlib.h>
#include <php_version.h>
#include <zend_API.h>
#include <zend_types.h>
#include <zend_language_parser.h>
#include "WordPress_instrumentation.h"
#include "util.h"
#include "util_for_PHP.h"
#include "AST_util.h"
#include "elastic_apm_alloc.h"
#define ELASTIC_APM_CURRENT_LOG_CATEGORY ELASTIC_APM_LOG_CATEGORY_AUTO_INSTRUMENT
static bool g_isOriginalZendAstProcessSet = false;
static zend_ast_process_t g_originalZendAstProcess = NULL;
static bool g_isLoadingAgentPhpCode = false;
void elasticApmBeforeLoadingAgentPhpCode()
{
g_isLoadingAgentPhpCode = true;
}
void elasticApmAfterLoadingAgentPhpCode()
{
g_isLoadingAgentPhpCode = false;
}
bool getStringFromAstZVal( zend_ast* astZval, /* out */ StringView* pResult )
{
char txtOutStreamBuf[ELASTIC_APM_TEXT_OUTPUT_STREAM_ON_STACK_BUFFER_SIZE];
TextOutputStream txtOutStream = ELASTIC_APM_TEXT_OUTPUT_STREAM_FROM_STATIC_BUFFER( txtOutStreamBuf );
if ( astZval == NULL )
{
ELASTIC_APM_LOG_TRACE( "Returning false - astZval == NULL" );
return false;
}
if ( astZval->kind != ZEND_AST_ZVAL )
{
ELASTIC_APM_LOG_TRACE( "Returning false - astZval->kind: %s", streamZendAstKind( astZval->kind, &txtOutStream ) );
return false;
}
zval* zVal = zend_ast_get_zval( astZval );
if ( zVal == NULL )
{
ELASTIC_APM_LOG_TRACE( "Returning false - zVal == NULL" );
return false;
}
int zValType = (int)Z_TYPE_P( zVal );
if ( zValType != IS_STRING )
{
ELASTIC_APM_LOG_TRACE( "Returning false - zValType: %s (%d)", zend_get_type_by_const( zValType ), (int)zValType );
return false;
}
zend_string* zString = Z_STR_P( zVal );
*pResult = zStringToStringView( zString );
ELASTIC_APM_LOG_TRACE( "Returning true - with result string [length: %" PRIu64 "]: %.*s", (UInt64)(pResult->length), (int)(pResult->length), pResult->begin );
return true;
}
bool getAstDeclName( zend_ast_decl* astDecl, /* out */ StringView* name )
{
if ( astDecl->name == NULL )
{
ELASTIC_APM_LOG_TRACE( "Returning false - astAsDecl->name == NULL" );
return false;
}
*name = zStringToStringView( astDecl->name );
ELASTIC_APM_LOG_TRACE( "Returning true - name [length: %" PRIu64 "]: %.*s", (UInt64)(name->length), (int)(name->length), name->begin );
return true;
}
bool getAstFunctionParameters( zend_ast_decl* astDecl, /* out */ ZendAstPtrArrayView* paramsAsts )
{
char txtOutStreamBuf[ELASTIC_APM_TEXT_OUTPUT_STREAM_ON_STACK_BUFFER_SIZE];
TextOutputStream txtOutStream = ELASTIC_APM_TEXT_OUTPUT_STREAM_FROM_STATIC_BUFFER( txtOutStreamBuf );
ELASTIC_APM_ASSERT( astDecl->kind == ZEND_AST_FUNC_DECL || astDecl->kind == ZEND_AST_METHOD, "astDecl->kind: %s", streamZendAstKind( astDecl->kind, &txtOutStream ) );
textOutputStreamRewind( &txtOutStream );
ELASTIC_APM_ASSERT_VALID_PTR( paramsAsts );
// function list of parameters is always child[ 0 ] - see zend_compile_func_decl
zend_ast* astFuncParams = astDecl->child[ 0 ];
if ( ! ( ( astFuncParams->kind == ZEND_AST_PARAM_LIST ) && zend_ast_is_list( astFuncParams ) ) )
{
ELASTIC_APM_LOG_TRACE( "Returning false - zend_ast_is_list( astFuncParams ): %s, astFuncParams->kind: %s"
, boolToString( zend_ast_is_list( astFuncParams ) ), streamZendAstKind( astFuncParams->kind, &txtOutStream ) );
textOutputStreamRewind( &txtOutStream );
return false;
}
zend_ast_list* astFuncParamsAsList = zend_ast_get_list( astFuncParams );
paramsAsts->count = astFuncParamsAsList->children;
paramsAsts->values = &( astFuncParamsAsList->child[ 0 ] );
return true;
}
bool getAstFunctionParameterName( zend_ast_decl* astDecl, unsigned int parameterIndex, /* out */ StringView* name )
{
char txtOutStreamBuf[ELASTIC_APM_TEXT_OUTPUT_STREAM_ON_STACK_BUFFER_SIZE];
TextOutputStream txtOutStream = ELASTIC_APM_TEXT_OUTPUT_STREAM_FROM_STATIC_BUFFER( txtOutStreamBuf );
ZendAstPtrArrayView paramsAsts;
if ( ! getAstFunctionParameters( astDecl, /* out */ ¶msAsts ) )
{
return false;
}
if ( parameterIndex >= paramsAsts.count )
{
ELASTIC_APM_LOG_TRACE( "Returning false - paramsAsts.count: %u, parameterIndex: %u", (unsigned)paramsAsts.count, parameterIndex );
return false;
}
zend_ast* param = paramsAsts.values[ parameterIndex ];
if ( param == NULL )
{
ELASTIC_APM_LOG_TRACE( "Returning false - param == NULL" );
return false;
}
if ( param->kind != ZEND_AST_PARAM )
{
ELASTIC_APM_LOG_TRACE( "Returning false - param->kind: %s", streamZendAstKind( param->kind, &txtOutStream ) );
textOutputStreamRewind( &txtOutStream );
return false;
}
if ( zend_ast_get_num_children( param ) == 0 )
{
ELASTIC_APM_LOG_TRACE( "Returning false - zend_ast_get_num_children( param ): %d", (int)zend_ast_get_num_children( param ) );
return false;
}
// parameter name is in child[ 1 ]
return getStringFromAstZVal( param->child[ 1 ], /* out */ name );
}
zend_string* createZStringForAst( StringView inStr )
{
return zend_string_init( inStr.begin, inStr.length, /* persistent: */ false );
}
bool isZendAstListKind( zend_ast_kind kind )
{
return ((kind >> ZEND_AST_IS_LIST_SHIFT) & 1) != 0;
}
/**
* Max number of children for AST nodes is
* 4 for PHP before 8.0
* 5 for PHP from 8.0 but before 8.4
* 6 for PHP from 8.4
*
* @see ZEND_AST_SPEC_CALL_EX
*
* When adding support for a new PHP version:
* - Make sure g_astNodeMaxChildCount is correct
* - If g_astNodeMaxChildCount changed then update createAstEx()
* - Make sure zendAstKindToString() in AST_debug.cpp includes all the enum cases from enum _zend_ast_kind in <php-src>/Zend/zend_ast.h
* - Increment minor part of PHP version in static_assert below
*/
static_assert(
PHP_VERSION_ID < ELASTIC_APM_BUILD_PHP_VERSION_ID( 8, 5, 0 ),
"Make sure g_astNodeMaxChildCount is correct. See max number of children in enum _zend_ast_kind in <php-src>/Zend/zend_ast.h"
);
static constexpr size_t g_astNodeMaxChildCount =
#if PHP_VERSION_ID < ELASTIC_APM_BUILD_PHP_VERSION_ID( 8, 0, 0 )
4 // PHP before 8.0
#elif PHP_VERSION_ID < ELASTIC_APM_BUILD_PHP_VERSION_ID( 8, 4, 0 )
5 // PHP from 8.0 but before 8.4
#else
6 // PHP from 8.4
#endif
;
static constexpr size_t g_zendKindWithLargestChildNodesCount =
#if PHP_VERSION_ID < ELASTIC_APM_BUILD_PHP_VERSION_ID( 8, 0, 0 )
ZEND_AST_FOR // PHP before 8.0
#else
ZEND_AST_PARAM // PHP from 8.0
#endif
;
static_assert(g_zendKindWithLargestChildNodesCount == (g_astNodeMaxChildCount << ZEND_AST_NUM_CHILDREN_SHIFT));
zend_ast* createAstEx( zend_ast_kind kind, zend_ast_attr attr, ZendAstPtrArrayView children )
{
char txtOutStreamBuf[ELASTIC_APM_TEXT_OUTPUT_STREAM_ON_STACK_BUFFER_SIZE];
TextOutputStream txtOutStream = ELASTIC_APM_TEXT_OUTPUT_STREAM_FROM_STATIC_BUFFER( txtOutStreamBuf );
ELASTIC_APM_ASSERT_LE_UINT64( children.count, g_astNodeMaxChildCount );
ELASTIC_APM_ASSERT( ! isZendAstListKind( kind ), "kind: %s", streamZendAstKind( kind, &txtOutStream ) );
switch( children.count )
{
case 0:
return zend_ast_create_ex( kind, attr );
case 1:
return zend_ast_create_ex( kind, attr, children.values[ 0 ] );
case 2:
return zend_ast_create_ex( kind, attr, children.values[ 0 ], children.values[ 1 ] );
case 3:
return zend_ast_create_ex( kind, attr, children.values[ 0 ], children.values[ 1 ], children.values[ 2 ] );
case 4:
return zend_ast_create_ex( kind, attr, children.values[ 0 ], children.values[ 1 ], children.values[ 2 ], children.values[ 3 ] );
#if PHP_VERSION_ID >= ELASTIC_APM_BUILD_PHP_VERSION_ID( 8, 0, 0 )
case 5:
return zend_ast_create_ex( kind, attr, children.values[ 0 ], children.values[ 1 ], children.values[ 2 ], children.values[ 3 ], children.values[ 4 ] );
#endif
#if PHP_VERSION_ID >= ELASTIC_APM_BUILD_PHP_VERSION_ID( 8, 4, 0 )
case 6:
return zend_ast_create_ex( kind, attr, children.values[ 0 ], children.values[ 1 ], children.values[ 2 ], children.values[ 3 ], children.values[ 4 ], children.values[ 5 ] );
#endif
default: // silence compiler warning
return nullptr;
}
}
ResultCode createAstExCheckChildrenCount( zend_ast_kind kind, zend_ast_attr attr, ZendAstPtrArrayView children, /* out */ zend_ast** pResult )
{
ResultCode resultCode;
if ( children.count > g_astNodeMaxChildCount )
{
ELASTIC_APM_LOG_ERROR( "Number of children is larger than max; children.count: %u, g_astNodeMaxChildCount: %u", (unsigned)children.count, (unsigned)g_astNodeMaxChildCount );
ELASTIC_APM_SET_RESULT_CODE_AND_GOTO_FAILURE_EX( resultFailure );
}
*pResult = createAstEx( kind, attr, children );
resultCode = resultSuccess;
finally:
return resultCode;
failure:
goto finally;
}
zend_ast* createAstWithAttribute( zend_ast_kind kind, zend_ast_attr attr )
{
return createAstEx( kind, attr, ELASTIC_APM_MAKE_EMPTY_ARRAY_VIEW( ZendAstPtrArrayView ) );
}
zend_ast* createAstWithAttributeAndOneChild( zend_ast_kind kind, zend_ast_attr attr, zend_ast* child )
{
return createAstEx( kind, attr, ELASTIC_APM_MAKE_ARRAY_VIEW( ZendAstPtrArrayView, /* count */ 1, &child ) );
}
zend_ast* createAstWithAttributeAndTwoChildren( zend_ast_kind kind, zend_ast_attr attr, zend_ast* child0, zend_ast* child1 )
{
zend_ast* children[] = { child0, child1 };
return createAstEx( kind, attr, ELASTIC_APM_MAKE_ARRAY_VIEW_FROM_STATIC( ZendAstPtrArrayView, children ) );
}
zend_ast* createAstWithAttributeAndThreeChildren( zend_ast_kind kind, zend_ast_attr attr, zend_ast* child0, zend_ast* child1, zend_ast* child2 )
{
zend_ast* children[] = { child0, child1, child2 };
return createAstEx( kind, attr, ELASTIC_APM_MAKE_ARRAY_VIEW_FROM_STATIC( ZendAstPtrArrayView, children ) );
}
zend_ast* createAstWithOneChild( zend_ast_kind kind, zend_ast* child )
{
return createAstWithAttributeAndOneChild( kind, /* attr */ 0, child );
}
zend_ast* createAstWithTwoChildren( zend_ast_kind kind, zend_ast* child0, zend_ast* child1 )
{
return createAstWithAttributeAndTwoChildren( kind, /* attr */ 0, child0, child1 );
}
zend_ast* createAstWithThreeChildren( zend_ast_kind kind, zend_ast* child0, zend_ast* child1, zend_ast* child2 )
{
return createAstWithAttributeAndThreeChildren( kind, /* attr */ 0, child0, child1, child2 );
}
zend_ast* createAstMagicConst( zend_ast_attr attr, uint32_t lineNumber )
{
zend_ast* result = createAstWithAttribute( ZEND_AST_MAGIC_CONST, attr );
result->lineno = lineNumber;
return result;
}
zend_ast* createAstMagicConst__FUNCTION__( uint32_t lineNumber )
{
return createAstMagicConst( T_FUNC_C, lineNumber );
}
zend_ast* createAstMagicConst__CLASS__( uint32_t lineNumber )
{
return createAstMagicConst( T_CLASS_C, lineNumber );
}
zend_ast* createAstZValWithAttribute( zval* zv, zend_ast_attr attr, uint32_t lineNumber )
{
zend_ast* result = zend_ast_create_zval_with_lineno(
zv,
#if PHP_VERSION_ID < ELASTIC_APM_BUILD_PHP_VERSION_ID( 7, 3, 0 ) /* if PHP version before 7.3.0 */
attr,
#endif
lineNumber
);
#if PHP_VERSION_ID >= ELASTIC_APM_BUILD_PHP_VERSION_ID( 7, 3, 0 ) /* if PHP version from 7.3.0 */
result->attr = attr;
#endif
return result;
}
zend_ast* createAstZValStringWithAttribute( StringView inStr, zend_ast_attr attr, uint32_t lineNumber )
{
zend_string* asZString = createZStringForAst( inStr );
zval stringAsZVal;
ZVAL_NEW_STR( &stringAsZVal, asZString );
return createAstZValWithAttribute( &stringAsZVal, attr, lineNumber );
}
zend_ast* createAstZValString( StringView inStr, uint32_t lineNumber )
{
return createAstZValStringWithAttribute( inStr, /* attr */ 0, lineNumber );
}
zend_ast* createAstVar( StringView name, uint32_t lineNumber )
{
// ZEND_AST_VAR (256) (line: 121)
// ZEND_AST_ZVAL (64) (line: 483) [type: string, value: hook_name]
return createAstWithOneChild( /* kind */ ZEND_AST_VAR, createAstZValString( name, lineNumber ) );
}
zend_ast* createAstConst( StringView name, zend_ast_attr nameAstAttr, uint32_t lineNumber )
{
// ZEND_AST_CONST (line: 20, attr: 0, childCount: 1)
// ZEND_AST_ZVAL (line: 20, attr: 1) [type: string, value: null]
zend_ast* nameAst = createAstZValString( name, lineNumber );
nameAst->attr = nameAstAttr;
return createAstWithOneChild( /* kind */ ZEND_AST_CONST, /* child0 */ nameAst );
}
zend_ast* createAstConstNull( uint32_t lineNumber )
{
return createAstConst( ELASTIC_APM_STRING_LITERAL_TO_VIEW( "null" ), ZEND_NAME_NOT_FQ, lineNumber );
}
zend_ast* createAstGlobalConst( StringView name, uint32_t lineNumber )
{
return createAstConst( name, /* nameAstAttr */ 0, lineNumber );
}
/**
* @see zend_ast_create_list_* in zend_ast.h
*/
static size_t g_elasticApmCreateAstListExChildrenCount = 2;
zend_ast* createAstListEx( zend_ast_kind kind, zend_ast_attr attr, ZendAstPtrArrayView children )
{
char txtOutStreamBuf[ELASTIC_APM_TEXT_OUTPUT_STREAM_ON_STACK_BUFFER_SIZE];
TextOutputStream txtOutStream = ELASTIC_APM_TEXT_OUTPUT_STREAM_FROM_STATIC_BUFFER( txtOutStreamBuf );
ELASTIC_APM_ASSERT_LE_UINT64( children.count, g_elasticApmCreateAstListExChildrenCount );
ELASTIC_APM_ASSERT( isZendAstListKind( kind ), "kind: %s", streamZendAstKind( kind, &txtOutStream ) );
zend_ast* result = NULL;
switch( children.count )
{
case 0:
result = zend_ast_create_list( children.count, kind );
break;
case 1:
result = zend_ast_create_list( children.count, kind, children.values[ 0 ] );
break;
case 2:
result = zend_ast_create_list( children.count, kind, children.values[ 0 ], children.values[ 1 ] );
break;
}
zend_ast_list* resultAsList = (zend_ast_list*)result;
resultAsList->attr = attr;
return result;
}
zend_ast* createAstListWithAttribute( zend_ast_kind kind, zend_ast_attr attr, uint32_t lineNumber )
{
zend_ast* result = createAstListEx( kind, attr, ELASTIC_APM_MAKE_EMPTY_ARRAY_VIEW( ZendAstPtrArrayView ) );
((zend_ast_list*)result)->lineno = lineNumber;
return result;
}
zend_ast* createAstList( zend_ast_kind kind, uint32_t lineNumber )
{
return createAstListWithAttribute( kind, /* attr */ 0, lineNumber );
}
void addChildToAstList( zend_ast* child, /* in,out */ zend_ast** pInSrcListOutNewList )
{
ELASTIC_APM_ASSERT_VALID_IN_PTR_TO_PTR( pInSrcListOutNewList );
char txtOutStreamBuf[ELASTIC_APM_TEXT_OUTPUT_STREAM_ON_STACK_BUFFER_SIZE];
TextOutputStream txtOutStream = ELASTIC_APM_TEXT_OUTPUT_STREAM_FROM_STATIC_BUFFER( txtOutStreamBuf );
ELASTIC_APM_ASSERT( zend_ast_is_list( *pInSrcListOutNewList ), "kind: %s", streamZendAstKind( (*pInSrcListOutNewList)->kind, &txtOutStream ) );
zend_ast* newList = zend_ast_list_add( /* in */ *pInSrcListOutNewList, child );
*pInSrcListOutNewList = newList;
}
zend_ast* createAstListWithOneChild( zend_ast_kind kind, zend_ast* child )
{
return createAstListEx( kind, /* attr */ 0, ELASTIC_APM_MAKE_ARRAY_VIEW( ZendAstPtrArrayView, /* count */ 1, &child ) );
}
zend_ast* createAstListWithTwoChildren( zend_ast_kind kind, zend_ast* child0, zend_ast* child1 )
{
zend_ast* children[] = { child0, child1 };
return createAstListEx( kind, /* attr */ 0, ELASTIC_APM_MAKE_ARRAY_VIEW_FROM_STATIC( ZendAstPtrArrayView, children ) );
}
zend_ast* createAstListWithThreeChildren( zend_ast_kind kind, zend_ast* child0, zend_ast* child1, zend_ast* child2 )
{
zend_ast* result = createAstListWithTwoChildren( kind, child0, child1 );
addChildToAstList( child2, /* in,out */ &result );
return result;
}
ResultCode createCapturedArgsAstArray( zend_ast_decl* astDecl, ArgCaptureSpecArrayView argCaptureSpecs, uint32_t lineNumber, /* out */ zend_ast** pResult )
{
// AST for PHP code:
//
// <pre-hook event handler>(..., [$hook_name, &$callback])
// ^^^^^^^^^^^^^^^^^^^^^^^^ - captured args AST array
//
// ZEND_AST_ARRAY (129) (line: 121, attr: 3) <- [$hook_name, /* ref */ &$callback] and 3 == ZEND_ARRAY_SYNTAX_SHORT
// ZEND_AST_ARRAY_ELEM (526) (line: 121, attr: 0)
// ZEND_AST_VAR (256) (line: 121, attr: 0)
// ZEND_AST_ZVAL (64) (line: 121, attr: 0) [type: string, value: hook_name]
// NULL
// ZEND_AST_ARRAY_ELEM (526) (line: 121, attr: 1) <- attr == 1 because callback variable is taken by reference
// ZEND_AST_VAR (256) (line: 121, attr: 0)
// ZEND_AST_ZVAL (64) (line: 121, attr: 0) [type: string, value: callback]
// NULL
ELASTIC_APM_ASSERT_VALID_OUT_PTR_TO_PTR( pResult );
ResultCode resultCode;
zend_ast* result = createAstListWithAttribute( ZEND_AST_ARRAY, /* attr */ ZEND_ARRAY_SYNTAX_SHORT, lineNumber );
ELASTIC_APM_FOR_EACH_INDEX( i, argCaptureSpecs.count )
{
StringView parameterName;
ArgCaptureSpec argCaptureSpec = argCaptureSpecs.values[ i ];
if ( argCaptureSpec == dontCaptureArg )
{
continue;
}
ELASTIC_APM_ASSERT( argCaptureSpec == captureArgByRef || argCaptureSpec == captureArgByValue, "argCaptureSpec: %d, i: %d", argCaptureSpec, (int)i );
// ZEND_AST_ARRAY_ELEM attribute should be 1 when passed by reference and 0 when passed by value
zend_ast_attr arrayElemAttr = argCaptureSpec == captureArgByRef ? 1 : 0;
if ( ! getAstFunctionParameterName( astDecl, /* parameterIndex */ i, /* out */ &( parameterName ) ) )
{
ELASTIC_APM_SET_RESULT_CODE_AND_GOTO_FAILURE();
}
zend_ast* varAst = createAstVar( parameterName, lineNumber );
// Array element value is the first child (i.e., index 0) and array element key is the second child (i.e., index 1)
zend_ast* arrayElement = createAstWithAttributeAndTwoChildren( ZEND_AST_ARRAY_ELEM, arrayElemAttr, /* array element value */ varAst, /* array element key */ NULL );
addChildToAstList( arrayElement, /* in,out */ &result );
}
*pResult = result;
resultCode = resultSuccess;
finally:
return resultCode;
failure:
goto finally;
}
zend_ast* createAstStandaloneFunctionCall( StringView funcName, bool isFullyQualified, zend_ast* astArgList )
{
// AST for PHP code:
//
// \elastic_apm_ast_instrumentation_pre_hook(__CLASS__, __FUNCTION__, [$hook_name, &$callback])
//
// ZEND_AST_CALL (line: 15, attr: 0, childCount: 2)
// ZEND_AST_ZVAL (line: 15, attr: 0) [type: string, value: elastic_apm_ast_instrumentation_pre_hook]
// ZEND_AST_ARG_LIST (line: 15, attr: 0, childCount: 3)
uint32_t lineNumber = zend_ast_get_lineno( astArgList );
zend_ast_attr zValAttr = isFullyQualified ? ZEND_NAME_FQ : ZEND_NAME_NOT_FQ;
return createAstWithTwoChildren( ZEND_AST_CALL, createAstZValStringWithAttribute( funcName, zValAttr, lineNumber ), astArgList );
}
zend_ast* createAstStandaloneFqFunctionCall( StringView funcName, zend_ast* astArgList )
{
return createAstStandaloneFunctionCall( funcName, /* isFullyQualified */ true, astArgList );
}
zend_ast* createAstStandaloneNotFqFunctionCall( StringView funcName, zend_ast* astArgList )
{
return createAstStandaloneFunctionCall( funcName, /* isFullyQualified */ false, astArgList );
}
ResultCode createPreHookAstArgListByCaptureSpec( zend_ast_decl* astDecl, ArgCaptureSpecArrayView argCaptureSpecs, /* out */ zend_ast** pResult )
{
// AST for PHP code:
//
// \elastic_apm_ast_instrumentation_pre_hook(<instrumented class full name>, __FUNCTION__, [$hook_name, &$callback])
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// <instrumented class full name> is __CLASS__ for methods and null for standalone functions
//
// ZEND_AST_ARG_LIST (line: 15, attr: 0, childCount: 3)
// ZEND_AST_MAGIC_CONST (line: 15, attr: __CLASS__)
// ZEND_AST_MAGIC_CONST (line: 15, attr: __FUNCTION__)
// ZEND_AST_ARRAY (line: 15, attr: 3, childCount: 2)
ELASTIC_APM_ASSERT_VALID_OUT_PTR_TO_PTR( pResult );
ResultCode resultCode;
uint32_t lineNumber = astDecl->start_lineno;
zend_ast* capturedArgsAstArray = NULL;
ELASTIC_APM_CALL_IF_FAILED_GOTO( createCapturedArgsAstArray( astDecl, argCaptureSpecs, lineNumber, /* out */ &capturedArgsAstArray ) );
*pResult = createAstListWithThreeChildren(
ZEND_AST_ARG_LIST
, astDecl->kind == ZEND_AST_METHOD ? createAstMagicConst__CLASS__( lineNumber ) : createAstConstNull( lineNumber )
, createAstMagicConst__FUNCTION__( lineNumber )
, capturedArgsAstArray
);
resultCode = resultSuccess;
finally:
ELASTIC_APM_LOG_DEBUG_RESULT_CODE_FUNCTION_EXIT_MSG();
return resultCode;
failure:
goto finally;
}
static StringView g_elastic_apm_ast_instrumentation_pre_hook_funcName = ELASTIC_APM_STRING_LITERAL_TO_VIEW( "elastic_apm_ast_instrumentation_pre_hook" );
/**
* function body is always child[ 2 ]
*
* @see zend_compile_func_decl
*/
static const size_t g_funcDeclBodyChildIndex = 2;
ResultCode insertAstForFunctionPreHook( zend_ast_decl* funcAstDecl, ArgCaptureSpecArrayView argCaptureSpecs )
{
// Before:
//
// function add_filter( $hook_name, $callback, $priority = 10, $accepted_args = 1 ) {
// ////////////////////////////
// // original function body //
// ////////////////////////////
// }
//
// ZEND_AST_FUNC_DECL (name: add_filter, line: 7, flags: 0, attr: 0, childCount: 4)
// ZEND_AST_PARAM_LIST (line: 7, attr: 0, childCount: 4)
// NULL
// ZEND_AST_STMT_LIST (line: 7, attr: 0, childCount: 4) <- original function body
// NULL
//
// After:
//
// function add_filter( $hook_name, $callback, $priority = 10, $accepted_args = 1 ) { /* fold-into-one-line-begin */
// \elastic_apm_ast_instrumentation_pre_hook( /* pre-hook args */ );
// { /* fold-into-one-line-end */
// ////////////////////////////
// // original function body //
// ////////////////////////////
// } }
//
// ZEND_AST_FUNC_DECL (name: add_filter, line: 24, flags: 0, attr: 0, childCount: 4)
// ZEND_AST_PARAM_LIST (line: 24, attr: 0, childCount: 4)
// NULL
// ZEND_AST_STMT_LIST (line: 24, attr: 0, childCount: 2) <- new function body
// ZEND_AST_CALL (line: 24, attr: 0, childCount: 2)
// ZEND_AST_ZVAL (line: 24, attr: 0) [type: string, value: elastic_apm_ast_instrumentation_pre_hook]
// ZEND_AST_ARG_LIST (line: 24, attr: 0, childCount: 3) <- pre-hook args
// ZEND_AST_STMT_LIST (line: 24, attr: 0, childCount: 4) <- original function body
// NULL
ELASTIC_APM_ASSERT_VALID_PTR( funcAstDecl );
ResultCode resultCode;
char txtOutStreamBuf[ELASTIC_APM_TEXT_OUTPUT_STREAM_ON_STACK_BUFFER_SIZE];
TextOutputStream txtOutStream = ELASTIC_APM_TEXT_OUTPUT_STREAM_FROM_STATIC_BUFFER( txtOutStreamBuf );
String dbgCompiledFileName = stringIfNotNullElse( nullableZStringToStringView( CG(compiled_filename) ).begin, "<N/A>" );
ELASTIC_APM_ASSERT( funcAstDecl->kind == ZEND_AST_FUNC_DECL || funcAstDecl->kind == ZEND_AST_METHOD, "funcAstDecl->kind: %s", streamZendAstKind( funcAstDecl->kind, &txtOutStream ) );
textOutputStreamRewind( &txtOutStream );
zend_ast* originalFuncBodyAst = nullptr;
zend_ast* preHookCallAstArgList = nullptr;
StringView dbgFuncName;
if ( ! getAstDeclName( funcAstDecl, /* out */ &dbgFuncName ) )
{
ELASTIC_APM_LOG_ERROR( "Failed to get function name - returning failure" );
ELASTIC_APM_SET_RESULT_CODE_AND_GOTO_FAILURE();
}
ELASTIC_APM_LOG_DEBUG_FUNCTION_ENTRY_MSG( "dbgFuncName: %s, compiled_filename: %s", dbgFuncName.begin, dbgCompiledFileName );
debugDumpAstTreeToLog( (zend_ast*) funcAstDecl, logLevel_debug );
originalFuncBodyAst = funcAstDecl->child[ g_funcDeclBodyChildIndex ];
if ( originalFuncBodyAst == NULL )
{
ELASTIC_APM_LOG_TRACE( "originalFuncBodyAst == NULL" );
ELASTIC_APM_SET_RESULT_CODE_AND_GOTO_FAILURE();
}
if ( originalFuncBodyAst->kind != ZEND_AST_STMT_LIST )
{
ELASTIC_APM_LOG_TRACE( "Expected originalFuncBodyAst->kind to be ZEND_AST_STMT_LIST but it is %s", streamZendAstKind( originalFuncBodyAst->kind, &txtOutStream ) );
textOutputStreamRewind( &txtOutStream );
ELASTIC_APM_SET_RESULT_CODE_AND_GOTO_FAILURE();
}
ELASTIC_APM_CALL_IF_FAILED_GOTO( createPreHookAstArgListByCaptureSpec( funcAstDecl, argCaptureSpecs, /* out */ &preHookCallAstArgList ) );
funcAstDecl->child[ g_funcDeclBodyChildIndex ] = createAstListWithTwoChildren(
ZEND_AST_STMT_LIST
, createAstStandaloneFqFunctionCall( g_elastic_apm_ast_instrumentation_pre_hook_funcName, preHookCallAstArgList )
, originalFuncBodyAst
);
resultCode = resultSuccess;
finally:
ELASTIC_APM_LOG_DEBUG_RESULT_CODE_FUNCTION_EXIT_MSG();
debugDumpAstTreeToLog( (zend_ast*) funcAstDecl, logLevel_debug );
return resultCode;
failure:
goto finally;
}
zend_ast* createDirectCallAstArgList( uint32_t lineNumber, StringView constNameForMethodName )
{
// PHP code:
//
// \elastic_apm_ast_instrumentation_direct_call(\ELASTIC_APM_WORDPRESS_DIRECT_CALL_METHOD_SET_READY_TO_WRAP_FILTER_CALLBACKS);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//
// AST:
//
// ZEND_AST_ARG_LIST (line: 63, attr: 0, childCount: 1)
// ZEND_AST_CONST (line: 63, attr: 0, childCount: 1)
// ZEND_AST_ZVAL (line: 63, attr: 0) [type: string, value: ELASTIC_APM_WORDPRESS_DIRECT_CALL_METHOD_SET_READY_TO_WRAP_FILTER_CALLBACKS]
return createAstListWithOneChild( ZEND_AST_ARG_LIST, createAstGlobalConst( constNameForMethodName, lineNumber ) );
}
static StringView g_elastic_apm_ast_instrumentation_direct_call_funcName = ELASTIC_APM_STRING_LITERAL_TO_VIEW( "elastic_apm_ast_instrumentation_direct_call" );
ResultCode appendDirectCallToInstrumentation( zend_ast_decl** pAstChildSlot, StringView constNameForMethodName )
{
// Before:
//
// function _wp_filter_build_unique_id( $hook_name, $callback, $priority ) {
// // ...
// }
//
// ZEND_AST_FUNC_DECL (name: _wp_filter_build_unique_id, line: 44, flags: 0, attr: 0, childCount: 4) <- original function declaration
//
// After:
//
// { function _wp_filter_build_unique_id($hook_name, $callback, $priority ) { markerForElasticApmTestsFoldAstIntoOneLineBegin();
// // ...
// } } /* fold-into-one-line-begin */
// \elastic_apm_ast_instrumentation_direct_call(/* direct call args */);
// /* fold-into-one-line-end */ }
//
// ZEND_AST_STMT_LIST (line: 44, attr: 0, childCount: 2)
// ZEND_AST_FUNC_DECL (name: _wp_filter_build_unique_id, line: 44, flags: 0, attr: 0, childCount: 4) <- original function declaration
// ZEND_AST_CALL (line: 63, attr: 0, childCount: 2)
// ZEND_AST_ZVAL (line: 63, attr: 0) [type: string, value: elastic_apm_ast_instrumentation_direct_call]
ELASTIC_APM_ASSERT_VALID_IN_PTR_TO_PTR( pAstChildSlot );
ResultCode resultCode;
char txtOutStreamBuf[ELASTIC_APM_TEXT_OUTPUT_STREAM_ON_STACK_BUFFER_SIZE];
TextOutputStream txtOutStream = ELASTIC_APM_TEXT_OUTPUT_STREAM_FROM_STATIC_BUFFER( txtOutStreamBuf );
String dbgCompiledFileName = stringIfNotNullElse( nullableZStringToStringView( CG(compiled_filename) ).begin, "<N/A>" );
zend_ast_decl* appendToAstDecl = *pAstChildSlot;
uint32_t lineNumber = appendToAstDecl->end_lineno;
zend_ast* appendedCallAstArgList = nullptr;
zend_ast* appendedCallAst = nullptr;
ELASTIC_APM_ASSERT( appendToAstDecl->kind == ZEND_AST_FUNC_DECL, "appendToAst->kind: %s", streamZendAstKind( appendToAstDecl->kind, &txtOutStream ) );
textOutputStreamRewind( &txtOutStream );
StringView dbgFuncName;
if ( ! getAstDeclName( appendToAstDecl, /* out */ &dbgFuncName ) )
{
ELASTIC_APM_LOG_ERROR( "Failed to get function name - returning failure" );
ELASTIC_APM_SET_RESULT_CODE_AND_GOTO_FAILURE();
}
ELASTIC_APM_LOG_DEBUG_FUNCTION_ENTRY_MSG( "dbgFuncName: %s, compiled_filename: %s", dbgFuncName.begin, dbgCompiledFileName );
debugDumpAstTreeToLog( (zend_ast*) ( *pAstChildSlot ), logLevel_debug );
appendedCallAstArgList = createDirectCallAstArgList( lineNumber, constNameForMethodName );
appendedCallAst = createAstStandaloneFqFunctionCall( g_elastic_apm_ast_instrumentation_direct_call_funcName, appendedCallAstArgList );
*((zend_ast**)pAstChildSlot) = createAstListWithTwoChildren( ZEND_AST_STMT_LIST, (zend_ast*) appendToAstDecl, appendedCallAst );
resultCode = resultSuccess;
finally:
ELASTIC_APM_LOG_DEBUG_RESULT_CODE_FUNCTION_EXIT_MSG( "dbgFuncName: %s, compiled_filename: %s", dbgFuncName.begin, dbgCompiledFileName );
debugDumpAstTreeToLog( (zend_ast*) ( *pAstChildSlot ), logLevel_debug );
return resultCode;
failure:
goto finally;
}
static StringView g_wrappedFunctionNewNameSuffix = ELASTIC_APM_STRING_LITERAL_TO_VIEW( "ElasticApmWrapped" );
ResultCode createWrappedFunctionNewName( StringView originalName, /* out */ StringBuffer* pResult )
{
ResultCode resultCode;
StringBuffer result = ELASTIC_APM_EMPTY_STRING_BUFFER;
size_t newNameLength = originalName.length + g_wrappedFunctionNewNameSuffix.length;
size_t contentLength = 0;
ELASTIC_APM_MALLOC_STRING_BUFFER_IF_FAILED_GOTO( /* maxLength */ newNameLength, /* out */ result );
result.begin[ 0 ] = '\0';
ELASTIC_APM_CALL_IF_FAILED_GOTO( appendToStringBuffer( originalName, result, /* in,out */ &contentLength ) );
ELASTIC_APM_CALL_IF_FAILED_GOTO( appendToStringBuffer( g_wrappedFunctionNewNameSuffix, result, /* in,out */ &contentLength ) );
ELASTIC_APM_ASSERT_EQ_UINT64( contentLength, newNameLength );
*pResult = result;
result = ELASTIC_APM_EMPTY_STRING_BUFFER;
resultCode = resultSuccess;
finally:
return resultCode;
failure:
ELASTIC_APM_FREE_STRING_BUFFER_AND_SET_TO_NULL( /* in,out */ result );
goto finally;
}
zend_string* cloneZStringForAst( zend_string* src )
{
if ( src == NULL )
{
return NULL;
}
return createZStringForAst( zStringToStringView( src ) );
}
zend_ast* cloneAstZVal( zend_ast* ast, uint32_t lineNumber )
{
zval clonedZVal;
ZVAL_COPY( /* out */ &clonedZVal, zend_ast_get_zval( ast ) );
return createAstZValWithAttribute( &clonedZVal, ast->attr, lineNumber );
}
// ZEND_AST_CONSTANT was added in PHP 7.3
#if PHP_VERSION_ID >= ELASTIC_APM_BUILD_PHP_VERSION_ID( 7, 3, 0 ) /* if PHP version from 7.3.0 */
zend_ast* cloneAstConstant( zend_ast* ast, uint32_t lineNumber )
{
zend_ast* result = zend_ast_create_constant( zend_ast_get_constant_name( ast ), ast->attr );
zval* pResultZVal = zend_ast_get_zval( result );
Z_LINENO_P( pResultZVal ) = lineNumber;
return result;
}
#endif
ResultCode cloneAstTree( zend_ast* ast, uint32_t lineNumber, /* out */ zend_ast** pResult );
#pragma clang diagnostic push
#pragma ide diagnostic ignored "misc-no-recursion"
ResultCode cloneAstDecl( zend_ast* ast, uint32_t lineNumber, /* out */ zend_ast** pResult )
{
ResultCode resultCode;
ZendAstPtrArrayView children = getAstChildren( ast );
zend_ast_decl* astDecl = (zend_ast_decl*)ast;
zend_ast* clonedChildren[elasticApmZendAstDeclChildrenCount];
if ( children.count != elasticApmZendAstDeclChildrenCount )
{
ELASTIC_APM_LOG_ERROR( "Number of children is not as expected; children.count: %u, elasticApmZendAstDeclChildrenCount: %u"
, (unsigned)children.count, (unsigned)elasticApmZendAstDeclChildrenCount );
ELASTIC_APM_SET_RESULT_CODE_AND_GOTO_FAILURE_EX( resultFailure );
}
ELASTIC_APM_FOR_EACH_INDEX( i, children.count )
{
clonedChildren[ i ] = NULL;
ELASTIC_APM_CALL_IF_FAILED_GOTO( cloneAstTree( children.values[ i ], lineNumber, /* out */ &( clonedChildren[ i ] ) ) );
}
*pResult = zend_ast_create_decl(
astDecl->kind
, astDecl->flags
, lineNumber /* <- start_lineno */
, cloneZStringForAst( astDecl->doc_comment )
, cloneZStringForAst( astDecl->name )
, clonedChildren[ 0 ]
, clonedChildren[ 1 ]
, clonedChildren[ 2 ]
, clonedChildren[ 3 ]
/**
* number of child* parameters accepted by zend_ast_create_decl
* 4 before PHP v8.0.0
* 5 from PHP v8.0.0
*/
#if PHP_VERSION_ID >= ELASTIC_APM_BUILD_PHP_VERSION_ID( 8, 0, 0 )
, clonedChildren[ 4 ]
#endif
);
resultCode = resultSuccess;
finally:
return resultCode;
failure:
goto finally;
}
ResultCode cloneAstList( zend_ast* ast, uint32_t lineNumber, /* out */ zend_ast** pResult )
{
ResultCode resultCode;
zend_ast_list* astList = zend_ast_get_list( ast );
zend_ast* result = createAstListWithAttribute( astList->kind, astList->attr, lineNumber );
ELASTIC_APM_FOR_EACH_INDEX( i, astList->children )
{
zend_ast* clonedChildAst = NULL;
ELASTIC_APM_CALL_IF_FAILED_GOTO( cloneAstTree( astList->child[ i ], lineNumber, /* out */ &clonedChildAst ) );
addChildToAstList( clonedChildAst, /* in,out */ &result );
}
*pResult = result;
resultCode = resultSuccess;
finally:
return resultCode;
failure:
goto finally;
}
ResultCode cloneFallbackAst( zend_ast* ast, uint32_t lineNumber, /* out */ zend_ast** pResult )
{
ResultCode resultCode;
ZendAstPtrArrayView children = getAstChildren( ast );
zend_ast** clonedChildren = NULL;
if ( children.count != 0 )
{
clonedChildren = static_cast<zend_ast **>(emalloc(sizeof( zend_ast* ) * children.count));
}
ELASTIC_APM_FOR_EACH_INDEX( i, children.count )
{
clonedChildren[ i ] = NULL;
ELASTIC_APM_CALL_IF_FAILED_GOTO( cloneAstTree( children.values[ i ], lineNumber, /* out */ &( clonedChildren[ i ] ) ) );
}
ELASTIC_APM_CALL_IF_FAILED_GOTO( createAstExCheckChildrenCount( ast->kind, ast->attr, ELASTIC_APM_MAKE_ARRAY_VIEW( ZendAstPtrArrayView, children.count, clonedChildren ), /* out */ pResult ) );
(*pResult)->lineno = lineNumber;
resultCode = resultSuccess;
finally:
if ( clonedChildren != NULL )
{
efree( clonedChildren );
clonedChildren = NULL;
}
return resultCode;
failure:
goto finally;
}
ResultCode cloneAstTree( zend_ast* ast, uint32_t lineNumber, /* out */ zend_ast** pResult )
{
/**
* @see zend_ast_copy
*/
ResultCode resultCode;
if ( ast == NULL )
{
*pResult = NULL;
ELASTIC_APM_SET_RESULT_CODE_TO_SUCCESS_AND_GOTO_FINALLY();
}
if ( ast->kind == ZEND_AST_ZVAL )
{
*pResult = cloneAstZVal( ast, lineNumber );
ELASTIC_APM_SET_RESULT_CODE_TO_SUCCESS_AND_GOTO_FINALLY();
}
// ZEND_AST_CONSTANT was added in PHP 7.3
#if PHP_VERSION_ID >= ELASTIC_APM_BUILD_PHP_VERSION_ID( 7, 3, 0 ) /* if PHP version from 7.3.0 */
if ( ast->kind == ZEND_AST_CONSTANT )
{
*pResult = cloneAstConstant( ast, lineNumber );
ELASTIC_APM_SET_RESULT_CODE_TO_SUCCESS_AND_GOTO_FINALLY();
}
#endif
if ( isAstDecl( ast->kind ) )
{
ELASTIC_APM_CALL_IF_FAILED_GOTO( cloneAstDecl( ast, lineNumber, /* out */ pResult ) );
ELASTIC_APM_SET_RESULT_CODE_TO_SUCCESS_AND_GOTO_FINALLY();
}
if ( zend_ast_is_list( ast ) )
{
ELASTIC_APM_CALL_IF_FAILED_GOTO( cloneAstList( ast, lineNumber, /* out */ pResult ) );
ELASTIC_APM_SET_RESULT_CODE_TO_SUCCESS_AND_GOTO_FINALLY();
}
ELASTIC_APM_CALL_IF_FAILED_GOTO( cloneFallbackAst( ast, lineNumber, /* out */ pResult ) );
resultCode = resultSuccess;
finally:
return resultCode;
failure:
goto finally;
}
#pragma clang diagnostic pop
zend_ast* createAstAssign( StringView varName, zend_ast* rhsAst )
{
// PHP code:
//
// $args = func_get_args();
// $postHook = \elastic_apm_ast_instrumentation_pre_hook(/* instrumentedClassFullName */ null, __FUNCTION__, $args);
//
// AST:
//
// ZEND_AST_ASSIGN (line: 48, attr: 0, childCount: 2)
// ZEND_AST_VAR (line: 48, attr: 0, childCount: 1)
// ZEND_AST_ZVAL (line: 48, attr: 0, childCount: 0, type: string, value: args)
// ZEND_AST_CALL (line: 48, attr: 0, childCount: 2) <- rhsAst
return createAstWithTwoChildren( ZEND_AST_ASSIGN, createAstVar( varName, zend_ast_get_lineno( rhsAst ) ), rhsAst );
}
static StringView g_argsVarName = ELASTIC_APM_STRING_LITERAL_TO_VIEW( "args" );
static StringView g_postHookVarName = ELASTIC_APM_STRING_LITERAL_TO_VIEW( "postHook" );
zend_ast* createPreHookAstArgList( bool isMethod, uint32_t lineNumber )
{
// PHP code:
//
// \elastic_apm_ast_instrumentation_pre_hook(<instrumented class full name>, __FUNCTION__, $args);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// <instrumented class full name> is __CLASS__ for methods and null for standalone functions
//
// AST:
//
// ZEND_AST_ARG_LIST (line: 48, attr: 0, childCount: 3)
// ZEND_AST_CONST (line: 48, attr: 0, childCount: 1)
// ZEND_AST_ZVAL (line: 48, attr: 1, childCount: 0, type: string, value: null)
// ZEND_AST_MAGIC_CONST (line: 48, attr: __FUNCTION__, childCount: 0)
// ZEND_AST_VAR (line: 48, attr: 0, childCount: 1)
// ZEND_AST_ZVAL (line: 48, attr: 0, childCount: 0, type: string, value: args)
return createAstListWithThreeChildren(
ZEND_AST_ARG_LIST
, isMethod ? createAstMagicConst__CLASS__( lineNumber ) : createAstConstNull( lineNumber )
, createAstMagicConst__FUNCTION__( lineNumber )
, createAstVar( g_argsVarName, lineNumber )
);
}
void createWrapperFunctionBodyPrologAst( /* in,out */ zend_ast** appendToAstStmtList )
{
// PHP code:
//
// $args = func_get_args();
// $postHook = \elastic_apm_ast_instrumentation_pre_hook(/* instrumentedClassFullName */ null, __FUNCTION__, $args);
//
// AST:
//
// ZEND_AST_ASSIGN (line: 48, attr: 0, childCount: 2)
// ZEND_AST_VAR (line: 48, attr: 0, childCount: 1)
// ZEND_AST_ZVAL (line: 48, attr: 0, childCount: 0, type: string, value: args)
// ZEND_AST_CALL (line: 48, attr: 0, childCount: 2)
// ZEND_AST_ZVAL (line: 48, attr: 1, childCount: 0, type: string, value: func_get_args)
// ZEND_AST_ARG_LIST (line: 48, attr: 0, childCount: 0)
// ZEND_AST_ASSIGN (line: 48, attr: 0, childCount: 2)
// ZEND_AST_VAR (line: 48, attr: 0, childCount: 1)
// ZEND_AST_ZVAL (line: 48, attr: 0, childCount: 0, type: string, value: postHook)
// ZEND_AST_CALL (line: 48, attr: 0, childCount: 2)
// ZEND_AST_ZVAL (line: 48, attr: 0, childCount: 0, type: string, value: elastic_apm_ast_instrumentation_pre_hook)
// ZEND_AST_ARG_LIST (line: 48, attr: 0, childCount: 3)
ELASTIC_APM_ASSERT_VALID_IN_PTR_TO_PTR( appendToAstStmtList );
uint32_t lineNumber = zend_ast_get_lineno( *appendToAstStmtList );
zend_ast* func_get_args_astCall = createAstStandaloneFqFunctionCall( ELASTIC_APM_STRING_LITERAL_TO_VIEW( "func_get_args" ), createAstList( ZEND_AST_ARG_LIST, lineNumber ) );
addChildToAstList( createAstAssign( g_argsVarName, func_get_args_astCall ), /* in,out */ appendToAstStmtList );
zend_ast* preHookAstCall = createAstStandaloneFqFunctionCall( g_elastic_apm_ast_instrumentation_pre_hook_funcName, createPreHookAstArgList( /* isMethod */ false, lineNumber ) );
addChildToAstList( createAstAssign( g_postHookVarName, preHookAstCall ), /* in,out */ appendToAstStmtList );
}
zend_ast* createCallPostHookIfNotNullAst( zend_ast* thrownAst, zend_ast* retValAst )
{
// PHP code:
//
// if ($postHook !== null) $postHook(/* thrown */ null, $retVal);
//
// or
//
// if ($postHook !== null) $postHook($thrown, /* retVal */ null);
//
// AST:
//
// ZEND_AST_IF (line: 48, attr: 0, childCount: 1)
// ZEND_AST_IF_ELEM (line: 48, attr: 0, childCount: 2)
// ZEND_AST_BINARY_OP (line: 48, attr: 17, childCount: 2)
// ZEND_AST_VAR (line: 48, attr: 0, childCount: 1)
// ZEND_AST_ZVAL (line: 48, attr: 0, childCount: 0, type: string, value: postHook)
// ZEND_AST_CONST (line: 48, attr: 0, childCount: 1)
// ZEND_AST_ZVAL (line: 48, attr: 1, childCount: 0, type: string, value: null)
// ZEND_AST_CALL (line: 48, attr: 0, childCount: 2)
// ZEND_AST_VAR (line: 48, attr: 0, childCount: 1)
// ZEND_AST_ZVAL (line: 48, attr: 0, childCount: 0, type: string, value: postHook)
// ZEND_AST_ARG_LIST (line: 48, attr: 0, childCount: 2)
// ZEND_AST_CONST (line: 48, attr: 0, childCount: 1)
// ZEND_AST_ZVAL (line: 48, attr: 1, childCount: 0, type: string, value: null)
// ZEND_AST_VAR (line: 48, attr: 0, childCount: 1)
// ZEND_AST_ZVAL (line: 48, attr: 0, childCount: 0, type: string, value: retVal)
//
// or
//
// ZEND_AST_ARG_LIST (line: 48, attr: 0, childCount: 2)
// ZEND_AST_VAR (line: 48, attr: 0, childCount: 1)
// ZEND_AST_ZVAL (line: 48, attr: 0, childCount: 0, type: string, value: thrown)
// ZEND_AST_CONST (line: 48, attr: 0, childCount: 1)
// ZEND_AST_ZVAL (line: 48, attr: 1, childCount: 0, type: string, value: null)
ELASTIC_APM_ASSERT_VALID_PTR( thrownAst );
ELASTIC_APM_ASSERT_VALID_PTR( retValAst );
uint32_t lineNumber = zend_ast_get_lineno( thrownAst );
return createAstListWithOneChild(
ZEND_AST_IF
, createAstWithTwoChildren(
ZEND_AST_IF_ELEM
, zend_ast_create_binary_op(
ZEND_IS_NOT_IDENTICAL
, createAstVar( g_postHookVarName, lineNumber )
, createAstConstNull( lineNumber )
)
, createAstWithTwoChildren(
ZEND_AST_CALL
, createAstVar( g_postHookVarName, lineNumber )
, createAstListWithTwoChildren( ZEND_AST_ARG_LIST, thrownAst, retValAst )
)
)
);
}
zend_ast* createWrappedFunctionCallAstArgList( uint32_t lineNumber )
{
// PHP code:
//
// get_templateElasticApmWrapped(...$args);
// ^^^^^^^^
//
// AST:
//
// ZEND_AST_ARG_LIST (line: 48, attr: 0, childCount: 1)
// ZEND_AST_UNPACK (line: 48, attr: 0, childCount: 1)
// ZEND_AST_VAR (line: 48, attr: 0, childCount: 1)
// ZEND_AST_ZVAL (line: 48, attr: 0, childCount: 0, type: string, value: args)
return createAstListWithOneChild(
ZEND_AST_ARG_LIST
, createAstWithOneChild(
ZEND_AST_UNPACK
, createAstVar( g_argsVarName, lineNumber )
)
);
}
static StringView g_retValVarName = ELASTIC_APM_STRING_LITERAL_TO_VIEW( "retVal" );
zend_ast* createWrapperFunctionBodyTryBlockAst( StringView wrappedFunctionNewName, uint32_t lineNumber )
{
// PHP code:
//
// $retVal = get_templateElasticApmWrapped(...$args);
// if ($postHook !== null) $postHook(/* thrown */ null, $retVal);
// return $retVal;
//
// AST:
//
// ZEND_AST_STMT_LIST (line: 48, attr: 0, childCount: 3)
// ZEND_AST_ASSIGN (line: 48, attr: 0, childCount: 2)
// ZEND_AST_VAR (line: 48, attr: 0, childCount: 1)
// ZEND_AST_ZVAL (line: 48, attr: 0, childCount: 0, type: string, value: retVal)
// ZEND_AST_CALL (line: 48, attr: 0, childCount: 2)
// ZEND_AST_ZVAL (line: 48, attr: 1, childCount: 0, type: string, value: get_templateElasticApmWrapped)
// ZEND_AST_ARG_LIST (line: 48, attr: 0, childCount: 1)
// ZEND_AST_IF (line: 48, attr: 0, childCount: 1) <- if ($postHook !== null) $postHook(/* thrown */ null, $retVal);
// ZEND_AST_RETURN (line: 48, attr: 0, childCount: 1)
// ZEND_AST_VAR (line: 48, attr: 0, childCount: 1)
// ZEND_AST_ZVAL (line: 48, attr: 0, childCount: 0, type: string, value: retVal)
// return createAstList( ZEND_AST_STMT_LIST, lineNumber );
return createAstListWithThreeChildren(
ZEND_AST_STMT_LIST
, createAstAssign( g_retValVarName, createAstStandaloneNotFqFunctionCall( wrappedFunctionNewName, createWrappedFunctionCallAstArgList( lineNumber ) ) )
, createCallPostHookIfNotNullAst( /* thrownAst */ createAstConstNull( lineNumber ), createAstVar( g_retValVarName, lineNumber ) )
, createAstWithOneChild( ZEND_AST_RETURN, createAstVar( g_retValVarName, lineNumber ) )
);
}
static StringView g_thrownVarName = ELASTIC_APM_STRING_LITERAL_TO_VIEW( "thrown" );
zend_ast* createWrapperFunctionBodyCatchPartAst( uint32_t lineNumber )
{
// PHP code:
//
// } catch (\Throwable $thrown) {
// if ($postHook !== null) $postHook($thrown, /* retVal */ null);
// throw $thrown;
// }
//
// AST:
//
// ZEND_AST_CATCH_LIST (line: 48, attr: 0, childCount: 1)
// ZEND_AST_CATCH (line: 48, attr: 0, childCount: 3)
// ZEND_AST_NAME_LIST (line: 48, attr: 0, childCount: 1)
// ZEND_AST_ZVAL (line: 48, attr: 0, childCount: 0, type: string, value: Throwable)
// ZEND_AST_ZVAL (line: 48, attr: 0, childCount: 0, type: string, value: thrown)
// ZEND_AST_STMT_LIST (line: 48, attr: 0, childCount: 2)
// ZEND_AST_IF (line: 48, attr: 0, childCount: 1) <- if ($postHook !== null) $postHook($thrown, /* retVal */ null);
// ZEND_AST_THROW (line: 48, attr: 0, childCount: 1)
// ZEND_AST_VAR (line: 48, attr: 0, childCount: 1)
// ZEND_AST_ZVAL (line: 48, attr: 0, childCount: 0, type: string, value: thrown)
/**
* @see zend_compile_try
*/
return createAstListWithOneChild(
ZEND_AST_CATCH_LIST
, createAstWithThreeChildren(
ZEND_AST_CATCH
/* ZEND_AST_CATCH child[ 0 ] - class name(s) */
, createAstListWithOneChild( ZEND_AST_NAME_LIST, createAstZValString( ELASTIC_APM_STRING_LITERAL_TO_VIEW( "Throwable" ), lineNumber ) )
/* ZEND_AST_CATCH child[ 1 ] - var name */
, createAstZValString( g_thrownVarName, lineNumber )
/* ZEND_AST_CATCH child[ 2 ] - block */
, createAstListWithTwoChildren(
ZEND_AST_STMT_LIST
, createCallPostHookIfNotNullAst( createAstVar( g_thrownVarName, lineNumber ), /* retValAst */ createAstConstNull( lineNumber ) )
, createAstWithOneChild( ZEND_AST_THROW, createAstVar( g_thrownVarName, lineNumber ) )
)
)
);
}
void createWrapperFunctionBodyTryCatchAst( StringView wrappedFunctionNewName, /* in,out */ zend_ast** appendToAstStmtList )
{
// PHP code:
//
// try {
// // ...
// } catch ( ... ) {
// // ...
// }
//
// AST:
//
// ZEND_AST_TRY (line: 48, attr: 0, childCount: 3)
// ZEND_AST_STMT_LIST (line: 48, attr: 0, childCount: 3) <- try block
// ZEND_AST_CATCH_LIST (line: 48, attr: 0, childCount: 1)
// ZEND_AST_CATCH (line: 48, attr: 0, childCount: 3) <- catch block
// NULL <- no finally block
ELASTIC_APM_ASSERT_VALID_IN_PTR_TO_PTR( appendToAstStmtList );
uint32_t lineNumber = zend_ast_get_lineno( *appendToAstStmtList );
/**
* @see zend_compile_try
*/
zend_ast* astTryCatch = createAstWithThreeChildren(
ZEND_AST_TRY
, createWrapperFunctionBodyTryBlockAst( wrappedFunctionNewName, lineNumber )
, createWrapperFunctionBodyCatchPartAst( lineNumber )
, NULL /* <- no finally block */
);
addChildToAstList( astTryCatch, /* in,out */ appendToAstStmtList );
}
zend_ast* createWrapperFunctionBodyAst( StringView wrappedFunctionNewName, uint32_t lineNumber )
{
// PHP code:
//
// // prolog
// try {
// // ...
// } catch ( ... ) {
// // ...
// }
//
// AST:
//
// ZEND_AST_STMT_LIST (line: 48, attr: 0, childCount: 3)
// ZEND_AST_ASSIGN (line: 48, attr: 0, childCount: 2) <- // part of prolog
// ZEND_AST_ASSIGN (line: 48, attr: 0, childCount: 2) <- // part of prolog
// ZEND_AST_TRY (line: 48, attr: 0, childCount: 3)
zend_ast* funcBodyAstStmtList = createAstList( ZEND_AST_STMT_LIST, lineNumber );
createWrapperFunctionBodyPrologAst( /* in,out */ &funcBodyAstStmtList );
createWrapperFunctionBodyTryCatchAst( wrappedFunctionNewName, /* in,out */ &funcBodyAstStmtList );
return funcBodyAstStmtList;
}
ResultCode createWrapperFunctionAst( zend_ast_decl* originalFuncAstDecl, StringView wrappedFunctionNewName, /* out */ zend_ast_decl** pResult )
{
ELASTIC_APM_ASSERT_VALID_PTR( originalFuncAstDecl );
ELASTIC_APM_ASSERT_VALID_OUT_PTR_TO_PTR( pResult );
ResultCode resultCode;
zend_ast* originalFuncBodyAst = NULL;
zend_ast_decl* clonedFuncDecl = NULL;
uint32_t lineNumber = originalFuncAstDecl->end_lineno;
originalFuncBodyAst = originalFuncAstDecl->child[ g_funcDeclBodyChildIndex ];
// Temporarily set the body to NULL because we don't want to clone it
// We restore it back after clone call
originalFuncAstDecl->child[ g_funcDeclBodyChildIndex ] = NULL;
resultCode = cloneAstTree( (zend_ast*)originalFuncAstDecl, lineNumber, /* out */ (zend_ast**)&clonedFuncDecl );
originalFuncAstDecl->child[ g_funcDeclBodyChildIndex ] = originalFuncBodyAst;
if ( resultCode != resultSuccess )
{
goto failure;
}
clonedFuncDecl->child[ g_funcDeclBodyChildIndex ] = createWrapperFunctionBodyAst( wrappedFunctionNewName, lineNumber );
*pResult = clonedFuncDecl;
resultCode = resultSuccess;
finally:
return resultCode;
failure:
goto finally;
}
uint32_t findAstDeclStartLineNumber( zend_ast_decl* astDecl )
{
ZendAstPtrArrayView children = getAstDeclChildren( astDecl );
uint32_t result = astDecl->start_lineno;
ELASTIC_APM_FOR_EACH_INDEX( i, children.count )
{
zend_ast* child = children.values[ i ];
if ( ( child != NULL ) && ( zend_ast_get_lineno( child ) < result ) )
{
result = zend_ast_get_lineno( child );
}
}
return result;
}
ResultCode wrapStandaloneFunctionAstWithPrePostHooks( /* in,out */ zend_ast_decl** pAstChildSlot )
{
// Before:
//
// function get_template() {
// // ...
// }
//
// ZEND_AST_FUNC_DECL (name: get_template, line: 16, flags: 0, attr: 0, childCount: 4) <- original function under original name
//
// After:
//
// { function get_templateElasticApmWrapped() {
// // ...
// } // fold-AST-into-one-line-begin
//
// function get_template()
// {
// // wrapper function body
// }
// } // fold-AST-into-one-line-end
//
// ZEND_AST_STMT_LIST (line: 16, attr: 0, childCount: 2)
// ZEND_AST_FUNC_DECL (name: get_templateElasticApmWrapped, line: 16, flags: 0, attr: 0, childCount: 4) <- original function under wrapped name
// ZEND_AST_FUNC_DECL (name: get_template, line: 25, flags: 0, attr: 0, childCount: 4) <- wrapper function under original name
ELASTIC_APM_ASSERT_VALID_IN_PTR_TO_PTR( pAstChildSlot );
ResultCode resultCode;
char txtOutStreamBuf[ELASTIC_APM_TEXT_OUTPUT_STREAM_ON_STACK_BUFFER_SIZE];
TextOutputStream txtOutStream = ELASTIC_APM_TEXT_OUTPUT_STREAM_FROM_STATIC_BUFFER( txtOutStreamBuf );
String dbgCompiledFileName = stringIfNotNullElse( nullableZStringToStringView( CG(compiled_filename) ).begin, "<N/A>" );
zend_ast_decl* originalFuncAstDecl = *pAstChildSlot; // it's not created by this function, so we should NOT clean it on failure
StringBuffer wrappedFunctionNewName = ELASTIC_APM_EMPTY_STRING_BUFFER;
zend_ast_decl* wrapperFuncAst = NULL;
zend_ast* newCombinedAst = NULL;
ELASTIC_APM_ASSERT( originalFuncAstDecl->kind == ZEND_AST_FUNC_DECL, "originalFuncAstDecl->kind: %s", streamZendAstKind( originalFuncAstDecl->kind, &txtOutStream ) );
textOutputStreamRewind( &txtOutStream );
StringView originalFuncName;
if ( ! getAstDeclName( originalFuncAstDecl, /* out */ &originalFuncName ) )
{
ELASTIC_APM_LOG_ERROR( "Failed to get function name - returning failure" );
ELASTIC_APM_SET_RESULT_CODE_AND_GOTO_FAILURE();
}
ELASTIC_APM_LOG_DEBUG_FUNCTION_ENTRY_MSG( "originalFuncName: %s, compiled_filename: %s", originalFuncName.begin, dbgCompiledFileName );
debugDumpAstTreeToLog( (zend_ast*) ( *pAstChildSlot ), logLevel_debug );
ELASTIC_APM_CALL_IF_FAILED_GOTO( createWrappedFunctionNewName( originalFuncName, /* out */ &wrappedFunctionNewName ) );
ELASTIC_APM_CALL_IF_FAILED_GOTO( createWrapperFunctionAst( originalFuncAstDecl, stringBufferToView( wrappedFunctionNewName ), /* out */ &wrapperFuncAst ) );
newCombinedAst = createAstListWithTwoChildren( ZEND_AST_STMT_LIST, (zend_ast*)originalFuncAstDecl, (zend_ast*)wrapperFuncAst );
newCombinedAst->lineno = findAstDeclStartLineNumber( originalFuncAstDecl );
originalFuncAstDecl->name = createZStringForAst( stringBufferToView( wrappedFunctionNewName ) );
*((zend_ast**)pAstChildSlot) = newCombinedAst;
resultCode = resultSuccess;
finally:
ELASTIC_APM_FREE_STRING_BUFFER_AND_SET_TO_NULL( /* in,out */ wrappedFunctionNewName );
ELASTIC_APM_LOG_DEBUG_RESULT_CODE_FUNCTION_EXIT_MSG( "originalFuncName: %s, compiled_filename: %s", originalFuncName.begin, dbgCompiledFileName );
debugDumpAstTreeToLog( (zend_ast*) ( *pAstChildSlot ), logLevel_debug );
return resultCode;
failure:
goto finally;
}
bool getAstName( zend_ast* ast, /* out */ StringView* name )
{
char txtOutStreamBuf[ELASTIC_APM_TEXT_OUTPUT_STREAM_ON_STACK_BUFFER_SIZE];
TextOutputStream txtOutStream = ELASTIC_APM_TEXT_OUTPUT_STREAM_FROM_STATIC_BUFFER( txtOutStreamBuf );
switch ( ast->kind )
{
case ZEND_AST_CLASS:
case ZEND_AST_FUNC_DECL:
case ZEND_AST_METHOD:
return getAstDeclName( (zend_ast_decl*)ast, /* out */ name );
default:
ELASTIC_APM_ASSERT( false, "Unexpected ast->kind: %s", streamZendAstKind( ast->kind, &txtOutStream ) );
return false;
}
}
bool parseAstNamespace( zend_ast* astNamespace, /* out */ StringView* pName, /* out */ zend_ast** pEnclosedScope )
{
// PHP code:
//
// namespace // global
// {
// }
//
// AST:
//
// ZEND_AST_NAMESPACE (line: 4, attr: 0, childCount: 2)
// NULL
// ZEND_AST_STMT_LIST (line: 4, attr: 0, childCount: 5)
// PHP code:
//
// namespace MyNamespace;
//
// AST:
//
// ZEND_AST_NAMESPACE (line: 3, attr: 0, childCount: 2)
// ZEND_AST_ZVAL (line: 3, attr: 0) [type: string, value: MyNamespace]
// NULL
//
// PHP code:
//
// namespace MyNamespace
// {
// }
//
// AST:
//
// ZEND_AST_NAMESPACE (line: 3, attr: 0, childCount: 2)
// ZEND_AST_ZVAL (line: 3, attr: 0) [type: string, value: MyNamespace]
// ZEND_AST_STMT_LIST (line: 4, attr: 0, childCount: 5)
char txtOutStreamBuf[ELASTIC_APM_TEXT_OUTPUT_STREAM_ON_STACK_BUFFER_SIZE];
TextOutputStream txtOutStream = ELASTIC_APM_TEXT_OUTPUT_STREAM_FROM_STATIC_BUFFER( txtOutStreamBuf );
zend_ast* nameAstZval = NULL;
zend_ast* enclosedScopeAst = NULL;
StringView name;
ELASTIC_APM_ASSERT( astNamespace->kind == ZEND_AST_NAMESPACE, "ast->kind: %s", streamZendAstKind( astNamespace->kind, &txtOutStream ) );
textOutputStreamRewind( &txtOutStream );
ELASTIC_APM_ASSERT_VALID_PTR( pName );
ELASTIC_APM_ASSERT_VALID_OUT_PTR_TO_PTR( pEnclosedScope );
uint32_t childrenCountAstNamespace = zend_ast_get_num_children( astNamespace );
if ( childrenCountAstNamespace < 2 )
{
ELASTIC_APM_LOG_TRACE( "Returning false - childrenCountAstNamespace: %u", (unsigned)childrenCountAstNamespace );
return false;
}
nameAstZval = astNamespace->child[ 0 ];
if ( nameAstZval == NULL )
{
name = ELASTIC_APM_EMPTY_STRING_VIEW;
}
else if ( ! getStringFromAstZVal( nameAstZval, /* out */ &name ) )
{
return false;
}
enclosedScopeAst = astNamespace->child[ 1 ];
if ( ( enclosedScopeAst != NULL ) && ( enclosedScopeAst->kind != ZEND_AST_STMT_LIST ) )
{
ELASTIC_APM_LOG_TRACE( "Returning false - enclosedScopeAst->kind: %s", streamZendAstKind( enclosedScopeAst->kind, &txtOutStream ) );
return false;
}
*pName = name;
*pEnclosedScope = enclosedScopeAst;
ELASTIC_APM_LOG_TRACE( "Returning true - name [length: %" PRIu64 "]: %.*s", (UInt64)(pName->length), (int)(pName->length), pName->begin );
return true;
}
typedef bool (* CheckFindAstReqs)( zend_ast* ast, void* ctx );
bool checkFunctionReqs( zend_ast* ast, void* ctx )
{
char txtOutStreamBuf[ELASTIC_APM_TEXT_OUTPUT_STREAM_ON_STACK_BUFFER_SIZE];
TextOutputStream txtOutStream = ELASTIC_APM_TEXT_OUTPUT_STREAM_FROM_STATIC_BUFFER( txtOutStreamBuf );
ELASTIC_APM_ASSERT( ast->kind == ZEND_AST_FUNC_DECL || ast->kind == ZEND_AST_METHOD, "ast->kind: %s", streamZendAstKind( ast->kind, &txtOutStream ) );
textOutputStreamRewind( &txtOutStream );
size_t minParamsCount = *(size_t*)ctx;
ZendAstPtrArrayView paramsAsts;
if ( ! getAstFunctionParameters( (zend_ast_decl*)ast, /* out */ ¶msAsts ) )
{
return false;
}
return paramsAsts.count >= minParamsCount;
}
bool findAstOfKindCheckNode( zend_ast* ast, zend_ast_kind kindToFind, StringView name, CheckFindAstReqs checkFindAstReqs, void* checkFindAstReqsCtx )
{
if ( ast->kind != kindToFind )
{
return false;
}
if ( name.begin != NULL )
{
StringView astName;
if ( ! ( getAstName( ast, /* out */ &astName ) && areStringViewsEqual( astName, name ) ) )
{
return false;
}
}
return ( checkFindAstReqs == NULL ) || checkFindAstReqs( ast, checkFindAstReqsCtx );
}
#pragma clang diagnostic push
#pragma ide diagnostic ignored "misc-no-recursion"
zend_ast** findChildSlotAstByKind( zend_ast* ast, zend_ast_kind kindToFind, StringView nameSpace, StringView name, CheckFindAstReqs checkFindAstReqs, void* checkFindAstReqsCtx )
{
if ( ! zend_ast_is_list( ast ) )
{
return NULL;
}
zend_ast_list* astAsList = zend_ast_get_list( ast );
ELASTIC_APM_FOR_EACH_INDEX( i, astAsList->children )
{
zend_ast* child = astAsList->child[ i ];
if ( zend_ast_is_list( child ) )
{
zend_ast** foundAst = findChildSlotAstByKind( child, kindToFind, nameSpace, name, checkFindAstReqs, checkFindAstReqsCtx );
if ( foundAst != NULL )
{
return foundAst;
}
continue;
}
if ( child->kind == ZEND_AST_NAMESPACE )
{
StringView foundNamespaceName;
zend_ast* namespaceEnclosedScope = NULL;
if ( ! parseAstNamespace( child, /* out */ &foundNamespaceName, /* out */ &namespaceEnclosedScope ) )
{
continue;
}
if ( ! areStringViewsEqual( foundNamespaceName, nameSpace ) )
{
continue;
}
if ( namespaceEnclosedScope != NULL )
{
zend_ast** foundAst = findChildSlotAstByKind( namespaceEnclosedScope, kindToFind, nameSpace, name, checkFindAstReqs, checkFindAstReqsCtx );
if ( foundAst != NULL )
{
return foundAst;
}
continue;
}
}
if ( findAstOfKindCheckNode( astAsList->child[ i ], kindToFind, name, checkFindAstReqs, checkFindAstReqsCtx ) )
{
// It's not &child on purpose since child is a local variable
return &( astAsList->child[ i ] );
}
}
return NULL;
}
#pragma clang diagnostic pop
zend_ast_decl** findChildSlotForStandaloneFunctionAst( zend_ast* rootAst, StringView nameSpace, StringView funcName, size_t minParamsCount )
{
return (zend_ast_decl**) findChildSlotAstByKind( rootAst, ZEND_AST_FUNC_DECL, nameSpace, funcName, checkFunctionReqs, &minParamsCount );
}
zend_ast_decl* findClassAst( zend_ast* rootAst, StringView nameSpace, StringView className )
{
zend_ast** result = findChildSlotAstByKind( rootAst, ZEND_AST_CLASS, nameSpace, className, /* checkFuncDeclReqs */ NULL, /* checkFindAstReqsCtx */ NULL );
return (zend_ast_decl*)(*result);
}
zend_ast_decl** findChildSlotForMethodAst( zend_ast_decl* astClass, StringView methodName, size_t minParamsCount )
{
// ZEND_AST_CLASS (name: WP_Hook, line: 6, flags: 32, attr: 0, childCount: 3)
// NULL
// ZEND_AST_NAME_LIST (line: 6, attr: 0, childCount: 2)
// ZEND_AST_ZVAL (line: 6, attr: 1) [type: string, value: Iterator]
// ZEND_AST_ZVAL (line: 6, attr: 1) [type: string, value: ArrayAccess]
// ZEND_AST_STMT_LIST (line: 6, attr: 0, childCount: 1)
// ZEND_AST_METHOD (name: add_filter, line: 7, flags: 1, attr: 0, childCount: 4)
char txtOutStreamBuf[ELASTIC_APM_TEXT_OUTPUT_STREAM_ON_STACK_BUFFER_SIZE];
TextOutputStream txtOutStream = ELASTIC_APM_TEXT_OUTPUT_STREAM_FROM_STATIC_BUFFER( txtOutStreamBuf );
ELASTIC_APM_ASSERT( astClass->kind == ZEND_AST_CLASS, "ast->kind: %s", streamZendAstKind( astClass->kind, &txtOutStream ) );
textOutputStreamRewind( &txtOutStream );
zend_ast_decl* astAsDecl = (zend_ast_decl*)astClass;
// class body is always child[ 2 ] - see zend_compile_class_decl
return (zend_ast_decl**) findChildSlotAstByKind( astAsDecl->child[ 2 ], ZEND_AST_METHOD, /* namespace */ ELASTIC_APM_EMPTY_STRING_VIEW, methodName, checkFunctionReqs, &minParamsCount );
}
void elasticApmTransformAstImpl( zend_ast* ast )
{
StringView compiledFileFullPath = nullableZStringToStringView( CG( compiled_filename) );
if ( compiledFileFullPath.begin == NULL )
{
return;
}
size_t fileIndex;
if ( ! wordPressInstrumentationShouldTransformAstInFile( compiledFileFullPath, /* out */ &fileIndex ) )
{
return;
}
ELASTIC_APM_LOG_DEBUG_FUNCTION_ENTRY_MSG( "compiledFileFullPath: %s", compiledFileFullPath.begin );
debugDumpAstTree( compiledFileFullPath, ast, /* isBeforeProcess */ true );
wordPressInstrumentationTransformAst( fileIndex, compiledFileFullPath, ast );
ELASTIC_APM_LOG_DEBUG_FUNCTION_EXIT_MSG( "compiledFileFullPath: %s", compiledFileFullPath.begin );
debugDumpAstTree( compiledFileFullPath, ast, /* isBeforeProcess */ false );
}
void elasticApmTransformAst( zend_ast* ast )
{
ELASTIC_APM_ASSERT( g_isOriginalZendAstProcessSet, "g_originalZendAstProcess: %p", g_originalZendAstProcess );
if ( ( ! g_isLoadingAgentPhpCode ) && ast != NULL )
{
elasticApmTransformAstImpl( ast );
}
if ( g_originalZendAstProcess != NULL )
{
g_originalZendAstProcess( ast );
}
}
void astInstrumentationOnModuleInit( const ConfigSnapshot* config )
{
if ( config->astProcessEnabled )
{
g_originalZendAstProcess = zend_ast_process;
g_isOriginalZendAstProcessSet = true;
zend_ast_process = elasticApmTransformAst;
ELASTIC_APM_LOG_DEBUG( "Changed zend_ast_process: from %p to elasticApmTransformAst (%p)", g_originalZendAstProcess, elasticApmTransformAst );
} else {
ELASTIC_APM_LOG_DEBUG( "AST processing will be DISABLED because configuration option %s (astProcessEnabled) is set to false", ELASTIC_APM_CFG_OPT_NAME_AST_PROCESS_ENABLED );
}
}
void astInstrumentationOnModuleShutdown()
{
if ( g_isOriginalZendAstProcessSet )
{
zend_ast_process_t zendAstProcessBeforeRestore = zend_ast_process;
zend_ast_process = g_originalZendAstProcess;
g_originalZendAstProcess = NULL;
g_isOriginalZendAstProcessSet = false;
ELASTIC_APM_LOG_DEBUG( "Restored zend_ast_process: from %p (%s elasticApmTransformAst: %p) -> %p"
, zendAstProcessBeforeRestore, zendAstProcessBeforeRestore == elasticApmTransformAst ? "==" : "!=", elasticApmTransformAst, g_originalZendAstProcess );
}
}
void astInstrumentationOnRequestInit( const ConfigSnapshot* config )
{
astProcessDebugDumpOnRequestInit( config );
wordPressInstrumentationOnRequestInit();
}
void astInstrumentationOnRequestShutdown()
{
wordPressInstrumentationOnRequestShutdown();
astProcessDebugDumpOnRequestShutdown();
}