in prod/native/libphpbridge/code/PhpBridge.cpp [74:462]
void PhpBridge::compileAndExecuteFile(std::string_view fileName) const {
#if PHP_VERSION_ID < 80100
AutoZval fn{fileName};
#else
AutoZendString fn(fileName);
#endif
// zend_error_handling zeh;
// zend_replace_error_handling(EH_THROW, nullptr, &zeh);
// utils::callOnScopeExit callOnExit([&zeh]() { zend_restore_error_handling(&zeh); });
auto exceptionState = saveExceptionState();
utils::callOnScopeExit releaseException([exceptionState]() {
if (EG(exception) && EG(exception) != (zend_object *)-1) {
zend_object_release(EG(exception));
EG(exception) = nullptr;
}
restoreExceptionState(exceptionState);
});
zend_op_array *opArray = nullptr;
{
zend_file_handle file_handle;
#if PHP_VERSION_ID >= 80100
zend_stream_init_filename_ex(&file_handle, fn.get());
utils::callOnScopeExit releaseFileHandle([fh = &file_handle]() { zend_destroy_file_handle(fh); });
if (php_stream_open_for_zend_ex(&file_handle, USE_PATH|STREAM_OPEN_FOR_INCLUDE) == zend_result::FAILURE) {
#else
if (php_stream_open_for_zend_ex(Z_STRVAL_P(fn.get()), &file_handle, USE_PATH|STREAM_OPEN_FOR_INCLUDE) == zend_result::FAILURE) {
#endif
std::string msg = "Unable to open file for compilation '"s;
msg.append(fileName);
msg.append("'"s);
throw std::runtime_error(msg);
}
opArray = zend_compile_file(&file_handle, ZEND_INCLUDE);
if (opArray && file_handle.handle.stream.handle) {
zend_string *opened_path = nullptr;
if (!file_handle.opened_path) {
#if PHP_VERSION_ID >= 80100
file_handle.opened_path = opened_path = zend_string_copy(fn.get());
#else
file_handle.opened_path = opened_path = zend_string_copy(Z_STR_P(fn.get()));
#endif
}
zend_hash_add_empty_element(&EG(included_files), file_handle.opened_path);
if (opened_path) {
zend_string_release_ex(opened_path, 0);
}
}
#if PHP_VERSION_ID < 80100
zend_destroy_file_handle(&file_handle);
#endif
}
if (!opArray) {
std::string msg = "Error during compilation of file '"s;
msg.append(fileName);
msg.append("' ");
msg.append(exceptionToString(EG(exception)));
throw std::runtime_error(msg);
}
AutoZval returnValue;
zend_execute(opArray, returnValue.get());
destroy_op_array(opArray);
efree(opArray);
if (EG(exception) && EG(exception) != (zend_object *)-1) {
std::string msg = "Error during execution of file '"s;
msg.append(fileName);
msg.append("'. ");
msg.append(exceptionToString(EG(exception)));
throw std::runtime_error(msg);
}
}
bool PhpBridge::callPHPSideEntryPoint(LogLevel logLevel, std::chrono::time_point<std::chrono::system_clock> requestInitStart) const {
auto phpPartFacadeClass = findClassEntry("elastic\\otel\\phppartfacade"sv);
if (!phpPartFacadeClass) {
return false;
}
std::array<AutoZval, 3> arguments{std::string_view(ELASTIC_OTEL_VERSION), logLevel, (double)std::chrono::duration_cast<std::chrono::microseconds>(requestInitStart.time_since_epoch()).count()};
AutoZval rv;
return callMethod(nullptr, "\\Elastic\\OTel\\PhpPartFacade::bootstrap"sv, arguments.data()->get(), arguments.size(), rv.get());
}
bool PhpBridge::callPHPSideExitPoint() const {
auto phpPartFacadeClass = findClassEntry("elastic\\otel\\phppartfacade"sv);
if (!phpPartFacadeClass) {
return false;
}
AutoZval rv;
return callMethod(nullptr, "Elastic\\OTel\\PhpPartFacade::shutdown"sv, nullptr, 0, rv.get());
}
bool PhpBridge::callPHPSideErrorHandler(int type, std::string_view errorFilename, uint32_t errorLineno, std::string_view message) const {
auto phpPartFacadeClass = findClassEntry("elastic\\otel\\phppartfacade"sv);
if (!phpPartFacadeClass) {
return false;
}
std::array<AutoZval, 4> arguments{type, errorFilename, errorLineno, message};
AutoZval rv;
return callMethod(nullptr, "\\Elastic\\OTel\\PhpPartFacade::handleError"sv, arguments.data()->get(), arguments.size(), rv.get());
}
//NOTE: argument must be lower case
zend_class_entry *findClassEntry(std::string_view className) {
return static_cast<zend_class_entry *>(zend_hash_str_find_ptr(EG(class_table), className.data(), className.length()));
}
zval *getClassStaticPropertyValue(zend_class_entry *ce, std::string_view propertyName) {
if (!ce) {
return nullptr;
}
return zend_read_static_property(ce, propertyName.data(), propertyName.length(), true);
}
zval *getClassPropertyValue(zend_class_entry *ce, zval *object, std::string_view propertyName) {
AutoZval rv;
if (Z_TYPE_P(object) != IS_OBJECT) {
return nullptr;
}
#if PHP_VERSION_ID >= 80000
return zend_read_property(ce, Z_OBJ_P(object), propertyName.data(), propertyName.length(), 1, rv.get());
#else
return zend_read_property(ce, object, propertyName.data(), propertyName.length(), 1, rv.get());
#endif
}
zval *getClassPropertyValue(zend_class_entry *ce, zend_object *object, std::string_view propertyName) {
AutoZval rv;
#if PHP_VERSION_ID >= 80000
return zend_read_property(ce, object, propertyName.data(), propertyName.length(), 1, rv.get());
#else
zval zvObj;
ZVAL_OBJ(&zvObj, object);
return zend_read_property(ce, &zvObj, propertyName.data(), propertyName.length(), 1, rv.get());
#endif
}
bool forceSetObjectPropertyValue(zend_object *object, zend_string *propertyName, zval *value) {
auto ce = object->ce;
decltype(zend_property_info::flags) originalFlags = 0;
bool flagChanged = false;
if (zend_hash_num_elements(&ce->properties_info) == 0) {
return false;
}
zval *zv = zend_hash_find(&ce->properties_info, propertyName);
if (!zv) {
return false;
}
auto prop_info = static_cast<zend_property_info *>(Z_PTR_P(zv));
originalFlags = prop_info->flags;
if (prop_info->flags & ZEND_ACC_READONLY) {
prop_info->flags = prop_info->flags & ~ZEND_ACC_READONLY;
}
zend_update_property_ex(object->ce, object, propertyName, value);
if (flagChanged) {
prop_info->flags = originalFlags;
}
return true;
}
bool callMethod(zval *object, std::string_view methodName, zval arguments[], int32_t argCount, zval *returnValue) {
elasticapm::utils::callOnScopeExit callOnExit([exceptionState = saveExceptionState()]() { restoreExceptionState(exceptionState); });
AutoZval zMethodName;
ZVAL_STRINGL(zMethodName.get(), methodName.data(), methodName.length());
#if PHP_VERSION_ID >= 80000
return _call_user_function_impl(object, zMethodName.get(), returnValue, argCount, arguments, nullptr) == SUCCESS;
#else
return _call_user_function_ex(object, zMethodName.get(), returnValue, argCount, arguments, 0) == SUCCESS;
#endif
}
bool isObjectOfClass(zval *object, std::string_view className) {
if (!object || Z_TYPE_P(object) != IS_OBJECT) {
return false;
}
if (!Z_OBJCE_P(object)->name) {
return false;
}
return std::string_view{Z_OBJCE_P(object)->name->val, Z_OBJCE_P(object)->name->len} == className;
}
void getCallArguments(zval *zv, zend_execute_data *ex) {
zval *p, *q;
uint32_t i, first_extra_arg;
uint32_t arg_count = ZEND_CALL_NUM_ARGS(ex);
// @see
// https://github.com/php/php-src/blob/php-8.1.0/Zend/zend_builtin_functions.c#L235
if (!arg_count) {
ZVAL_EMPTY_ARRAY(zv);
return;
}
array_init_size(zv, arg_count);
if (ex->func->type == ZEND_INTERNAL_FUNCTION) {
first_extra_arg = arg_count;
} else {
first_extra_arg = ex->func->op_array.num_args;
}
zend_hash_real_init_packed(Z_ARRVAL_P(zv));
ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(zv)) {
i = 0;
p = ZEND_CALL_ARG(ex, 1);
if (arg_count > first_extra_arg) {
while (i < first_extra_arg) {
q = p;
if (EXPECTED(Z_TYPE_INFO_P(q) != IS_UNDEF)) {
ZVAL_DEREF(q);
if (Z_OPT_REFCOUNTED_P(q)) {
Z_ADDREF_P(q);
}
ZEND_HASH_FILL_SET(q);
} else {
ZEND_HASH_FILL_SET_NULL();
}
ZEND_HASH_FILL_NEXT();
p++;
i++;
}
p = ZEND_CALL_VAR_NUM(ex, ex->func->op_array.last_var +
ex->func->op_array.T);
}
while (i < arg_count) {
q = p;
if (EXPECTED(Z_TYPE_INFO_P(q) != IS_UNDEF)) {
ZVAL_DEREF(q);
if (Z_OPT_REFCOUNTED_P(q)) {
Z_ADDREF_P(q);
}
ZEND_HASH_FILL_SET(q);
} else {
ZEND_HASH_FILL_SET_NULL();
}
ZEND_HASH_FILL_NEXT();
p++;
i++;
}
}
ZEND_HASH_FILL_END();
Z_ARRVAL_P(zv)->nNumOfElements = arg_count;
}
void getScopeNameOrThis(zval *zv, zend_execute_data *execute_data) {
if (execute_data->func->op_array.scope) {
if (execute_data->func->op_array.fn_flags & ZEND_ACC_STATIC) {
ZVAL_STR(zv, zend_get_called_scope(execute_data)->name);
} else {
ZVAL_OBJ_COPY(zv, zend_get_this_object(execute_data));
}
} else {
ZVAL_NULL(zv);
}
}
void getFunctionName(zval *zv, zend_execute_data *ex) {
ZVAL_STR_COPY(zv, ex->func->op_array.function_name);
}
void getFunctionDeclaringScope(zval *zv, zend_execute_data *ex) {
if (ex->func->op_array.scope) {
ZVAL_STR_COPY(zv, ex->func->op_array.scope->name);
} else {
ZVAL_NULL(zv);
}
}
void getFunctionDeclarationFileName(zval *zv, zend_execute_data *ex) {
if (ex->func->type != ZEND_INTERNAL_FUNCTION) {
ZVAL_STR_COPY(zv, ex->func->op_array.filename);
} else {
ZVAL_NULL(zv);
}
}
void getFunctionDeclarationLineNo(zval *zv, zend_execute_data *ex) {
if (ex->func->type != ZEND_INTERNAL_FUNCTION) {
ZVAL_LONG(zv, ex->func->op_array.line_start);
} else {
ZVAL_NULL(zv);
}
}
void getFunctionReturnValue(zval *zv, zval *retval) {
if (UNEXPECTED(!retval || Z_ISUNDEF_P(retval))) {
ZVAL_NULL(zv);
} else {
ZVAL_COPY(zv, retval);
}
}
void getCurrentException(zval *zv, zend_object *exception) {
if (exception && zend_is_unwind_exit(exception)) {
ZVAL_NULL(zv);
} else if (UNEXPECTED(exception) && instanceof_function(exception->ce, zend_ce_throwable)) {
ZVAL_OBJ_COPY(zv, exception);
} else {
ZVAL_NULL(zv);
}
}
void PhpBridge::getCompiledFiles(std::function<void(std::string_view)> recordFile) const {
void *ptr = nullptr;
ZEND_HASH_FOREACH_PTR(EG(class_table), ptr) {
zend_class_entry *ce = static_cast<zend_class_entry *>(ptr);
if (ce && ce->type == ZEND_USER_CLASS) {
auto filename = ce->info.user.filename;
if (filename) {
recordFile(std::string_view(ZSTR_VAL(filename), ZSTR_LEN(filename)));
}
}
}
ZEND_HASH_FOREACH_END();
ZEND_HASH_FOREACH_PTR(EG(function_table), ptr) {
zend_function *func = static_cast<zend_function *>(ptr);
if (func->type == ZEND_USER_FUNCTION && func->op_array.filename) {
recordFile(std::string_view(ZSTR_VAL(func->op_array.filename), ZSTR_LEN(func->op_array.filename)));
}
}
ZEND_HASH_FOREACH_END();
}
std::pair<std::size_t, std::size_t> PhpBridge::getNewlyCompiledFiles(std::function<void(std::string_view)> recordFile, std::size_t lastClassIndex, std::size_t lastFunctionIndex) const {
void *ptr = nullptr;
ZEND_HASH_FOREACH_PTR_FROM(EG(class_table), ptr, lastClassIndex) {
lastClassIndex++;
zend_class_entry *ce = static_cast<zend_class_entry *>(ptr);
if (ce && ce->type == ZEND_USER_CLASS) {
auto filename = ce->info.user.filename;
if (filename) {
recordFile(std::string_view(ZSTR_VAL(filename), ZSTR_LEN(filename)));
}
}
}
ZEND_HASH_FOREACH_END();
ZEND_HASH_FOREACH_PTR_FROM(EG(function_table), ptr, lastFunctionIndex) {
lastFunctionIndex++;
zend_function *func = static_cast<zend_function *>(ptr);
if (func->type == ZEND_USER_FUNCTION && func->op_array.filename) {
recordFile(std::string_view(ZSTR_VAL(func->op_array.filename), ZSTR_LEN(func->op_array.filename)));
}
}
ZEND_HASH_FOREACH_END();
return {lastClassIndex, lastFunctionIndex};
}
std::pair<int, int> PhpBridge::getPhpVersionMajorMinor() const {
return {PHP_MAJOR_VERSION, PHP_MINOR_VERSION};
}
} // namespace elasticapm::php