prod/native/libphpbridge/code/AutoZval.h (362 lines of code) (raw):
/*
* Copyright Elasticsearch B.V. and/or 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.
*/
#pragma once
#include <Zend/zend_API.h>
#include <Zend/zend_types.h>
#include <Zend/zend_variables.h>
#include <array>
#include <format>
#include <iostream>
#include <optional>
#include <stdexcept>
#include <type_traits>
#include <variant>
namespace elasticapm::php {
template<typename T>
concept NotZvalPointer = !std::same_as<T, zval *>;
class AutoZval {
public:
AutoZval(const AutoZval &) = delete;
AutoZval() {
ZVAL_UNDEF(&value);
}
AutoZval &operator=(const AutoZval &other) {
zval_ptr_dtor(&value);
ZVAL_COPY(&value, &other.value); // add ref
return *this;
}
AutoZval &operator=(AutoZval &&other) { // move
memcpy(&value, &other.value, sizeof(zval));
ZVAL_UNDEF(&other.value); // prevent destructor
return *this;
}
AutoZval(AutoZval &&other) { // move
memcpy(&value, &other.value, sizeof(zval));
ZVAL_UNDEF(&other.value); // prevent destructor
}
explicit AutoZval(zval *zv) { // copy from pointer (add reference)
if (!zv) {
setNull();
return;
}
ZVAL_COPY(&value, zv);
}
AutoZval(auto &&value) {
set(value);
}
~AutoZval() {
zval_ptr_dtor(&value);
}
constexpr zval &operator*() noexcept {
return value;
}
constexpr zval *get() noexcept {
return &value;
}
constexpr const zval *get() const noexcept {
return &value;
}
zval *data() {
return &value;
}
void setString(std::string_view str) {
ZVAL_STRINGL(&value, str.data(), str.length());
}
template<typename T, std::enable_if_t< std::is_convertible<T, zend_long>::value, bool> = true >
constexpr void setLong(T val) {
ZVAL_LONG(&value, val);
}
constexpr void setNull() {
ZVAL_NULL(&value);
}
constexpr void setDouble(double val) {
ZVAL_DOUBLE(&value, val);
}
constexpr void arrayInit() {
array_init(&value);
}
constexpr void arrayAddNextWithRef(zval *val) {
Z_TRY_ADDREF_P(val);
add_next_index_zval(&value, val);
}
constexpr void arrayAddNextWithRef(AutoZval const &val) {
arrayAddNextWithRef(const_cast<zval *>(val.get()));
}
constexpr void set(NotZvalPointer auto &&val) {
if constexpr (std::is_same_v<decltype(val), bool>) {
ZVAL_BOOL(&value, val);
} else if constexpr (std::is_floating_point_v<std::remove_reference_t<decltype(val)>>) {
ZVAL_DOUBLE(&value, val);
} else if constexpr (!std::is_null_pointer_v<std::remove_reference_t<decltype(val)>> && std::is_convertible_v<decltype(val), std::string_view>) {
std::string_view sv{val};
ZVAL_STRINGL(&value, sv.data(), sv.length());
} else if constexpr (std::is_null_pointer_v<std::remove_reference_t<decltype(val)>>) {
ZVAL_NULL(&value);
} else {
ZVAL_LONG(&value, val);
}
}
bool isNull() const {
return Z_TYPE_P(&value) == IS_NULL;
}
bool isUndef() const {
return Z_TYPE_P(&value) == IS_UNDEF;
}
bool isString() const {
return Z_TYPE_P(&value) == IS_STRING;
}
bool isLong() const {
return Z_TYPE_P(&value) == IS_LONG;
}
bool isDouble() const {
return Z_TYPE_P(&value) == IS_DOUBLE;
}
bool isBoolean() const {
return Z_TYPE_P(&value) == IS_TRUE || Z_TYPE_P(&value) == IS_FALSE;
}
bool isArray() const {
return Z_TYPE_P(&value) == IS_ARRAY;
}
bool isObject() const {
return Z_TYPE_P(&value) == IS_OBJECT;
}
bool isResource() const {
return Z_TYPE_P(&value) == IS_RESOURCE;
}
uint8_t getType() const {
return Z_TYPE_P(&value);
}
bool isStringValidUtf8() const {
return (GC_FLAGS(Z_STR(value)) & IS_STR_VALID_UTF8);
}
template <std::size_t ArgsNm = 0>
AutoZval callMethod(std::string_view methodName, std::array<AutoZval, ArgsNm> params = {}) const { // TODO const? can modify object
if (!isObject()) {
throw std::runtime_error("Can't call method on non-object");
}
elasticapm::php::AutoZval zMethodName;
elasticapm::php::AutoZval returnValue;
ZVAL_STRINGL(zMethodName.get(), methodName.data(), methodName.length());
if (_call_user_function_impl(const_cast<zval *>(&value), zMethodName.get(), returnValue.get(), params.size(), params.data()->get(), nullptr) != SUCCESS) {
throw std::runtime_error("Unable to call user method");
}
return returnValue;
}
AutoZval readProperty(std::string_view propertyName) const {
if (!isObject()) {
throw std::runtime_error("Can't get property from non-object");
}
return AutoZval(zend_read_property(Z_OBJCE(value), Z_OBJ(value), propertyName.data(), propertyName.length(), 1, nullptr));
}
bool instanceOf(std::string_view type) const {
if (!isObject()) {
throw std::runtime_error("Non-object");
}
auto objectType = std::string_view{ZSTR_VAL(Z_OBJCE(value)->name), ZSTR_LEN(Z_OBJCE(value)->name)};
return type == objectType;
}
AutoZval const &assertObjectType(std::string_view type) const {
if (!instanceOf(type)) {
auto objectType = std::string_view{ZSTR_VAL(Z_OBJCE(value)->name), ZSTR_LEN(Z_OBJCE(value)->name)};
throw std::runtime_error(std::format("Invalid object type: expected '{}', but got '{}'", type, objectType));
}
return *this;
}
std::optional<std::string_view> getOptStringView() const {
if (!isString()) {
return std::nullopt;
}
return std::string_view{Z_STRVAL(value), Z_STRLEN(value)};
}
std::string_view getStringView() const {
if (!isString()) {
throw std::runtime_error("Not a string");
}
return {Z_STRVAL(value), Z_STRLEN(value)};
}
std::optional<zend_long> getOptLong() const {
if (isLong()) {
return Z_LVAL(value);
}
return std::nullopt;
}
double getNumberAsDouble() const {
if (isDouble()) {
return Z_DVAL(value);
} else if (isLong()) {
return static_cast<double>(Z_LVAL(value));
}
throw std::runtime_error("Not a number");
}
zend_long getNumberAsLong() const {
if (isLong()) {
return Z_LVAL(value);
} else if (isDouble()) {
return static_cast<zend_long>(Z_DVAL(value));
}
throw std::runtime_error("Not a number");
}
zend_long getLong() const {
if (!isLong()) {
throw std::runtime_error("Not a long");
}
return Z_LVAL(value);
}
double getDouble() const {
if (!isDouble()) {
throw std::runtime_error("Not a double");
}
return Z_DVAL(value);
}
bool getBoolean() const {
if (Z_TYPE_P(&value) == IS_TRUE) {
return true;
} else if (Z_TYPE_P(&value) == IS_FALSE) {
return false;
} else {
throw std::runtime_error("Not a boolean");
}
}
uint32_t getArrayCount() const {
return zend_array_count(Z_ARRVAL(value));
}
// =============================
// Iterator for values only
// =============================
template <typename TP = AutoZval>
class IteratorImpl {
public:
using value_type = TP;
IteratorImpl(HashTable *ht, bool end = false) : ht_(ht), end_(end) {
if (!end_) {
if (!ht_ || zend_array_count(ht_) == 0) {
end_ = true;
} else {
zend_hash_internal_pointer_reset(ht_);
moveToValid();
}
}
}
value_type operator*() const {
zval *val = zend_hash_get_current_data(ht_);
return val ? AutoZval(val) : AutoZval();
}
IteratorImpl &operator++() {
zend_hash_move_forward(ht_);
if (zend_hash_has_more_elements(ht_) != SUCCESS) {
end_ = true;
} else {
moveToValid();
}
return *this;
}
bool operator!=(const IteratorImpl &other) const {
return ht_ != other.ht_ || end_ != other.end_;
}
private:
void moveToValid() {
while (zend_hash_has_more_elements(ht_) == SUCCESS) {
zval *val = zend_hash_get_current_data(ht_);
if (val && !Z_ISUNDEF_P(val)) {
return;
}
zend_hash_move_forward(ht_);
}
end_ = true;
}
HashTable *ht_;
bool end_;
};
using iterator = IteratorImpl<AutoZval>;
using const_iterator = IteratorImpl<const AutoZval>;
iterator begin() {
if (!isArray())
throw std::runtime_error("Zval is not an array");
return iterator(Z_ARRVAL(value), false);
}
iterator end() {
if (!isArray())
throw std::runtime_error("Zval is not an array");
return iterator(Z_ARRVAL(value), true);
}
const_iterator begin() const {
return cbegin();
}
const_iterator end() const {
return cend();
}
const_iterator cbegin() const {
if (!isArray())
throw std::runtime_error("Zval is not an array");
return const_iterator(Z_ARRVAL(value), false);
}
const_iterator cend() const {
if (!isArray())
throw std::runtime_error("Zval is not an array");
return const_iterator(Z_ARRVAL(value), true);
}
// =============================
// Iterator for key-value pairs
// =============================
class KeyValueIterator {
public:
using Key = std::variant<std::string_view, zend_ulong>;
using Value = AutoZval;
using Pair = std::pair<Key, Value>;
explicit KeyValueIterator(HashTable *ht, bool end = false) : ht_(ht), end_(end) {
if (!end_) {
if (!ht_ || zend_array_count(ht_) == 0) {
end_ = true;
} else {
zend_hash_internal_pointer_reset(ht_);
moveToValid();
}
}
}
Pair operator*() const {
zend_string *key_str = nullptr;
zend_ulong key_index = 0;
int key_type = zend_hash_get_current_key(ht_, &key_str, &key_index);
Key key;
if (key_type == HASH_KEY_IS_STRING && key_str) {
key = std::string_view(ZSTR_VAL(key_str), ZSTR_LEN(key_str));
} else {
key = key_index;
}
zval *val = zend_hash_get_current_data(ht_);
return {key, val ? AutoZval(val) : AutoZval()};
}
KeyValueIterator &operator++() {
zend_hash_move_forward(ht_);
if (zend_hash_has_more_elements(ht_) != SUCCESS) {
end_ = true;
} else {
moveToValid();
}
return *this;
}
bool operator!=(const KeyValueIterator &other) const {
return ht_ != other.ht_ || end_ != other.end_;
}
private:
void moveToValid() {
while (zend_hash_has_more_elements(ht_) == SUCCESS) {
zval *val = zend_hash_get_current_data(ht_);
if (val && !Z_ISUNDEF_P(val)) {
return;
}
zend_hash_move_forward(ht_);
}
end_ = true;
}
HashTable *ht_;
bool end_;
};
KeyValueIterator kvbegin() const {
if (!isArray())
throw std::runtime_error("Zval is not an array");
return KeyValueIterator(Z_ARRVAL(value), false);
}
KeyValueIterator kvend() const {
if (!isArray())
throw std::runtime_error("Zval is not an array");
return KeyValueIterator(Z_ARRVAL(value), true);
}
private:
zval value;
};
static_assert(sizeof(zval) == sizeof(AutoZval));
static_assert(sizeof(zval[10]) == sizeof(std::array<AutoZval, 10>));
} // namespace elasticapm::php