From 7032b335385640641b276c1db2924c8e0ffb88fe Mon Sep 17 00:00:00 2001 From: Andrei Golovatskii Date: Thu, 28 Nov 2024 12:41:15 +0300 Subject: [PATCH] [runtime] unit runtime&runtime-light json functions into runtime-common --- builtin-functions/kphp-light/functions.txt | 1 + .../{unsupported => }/serialize.txt | 27 - .../kphp-light/unsupported-functions.txt | 2 +- .../unsupported/unsupported-serialize.txt | 27 + runtime-common/stdlib/stdlib.cmake | 6 +- .../stdlib/string}/from-json-processor.h | 30 +- .../stdlib/string}/json-functions.cpp | 67 +- .../stdlib/string}/json-functions.h | 41 +- .../stdlib/string}/json-processor-utils.h | 14 +- .../stdlib/string}/json-writer.cpp | 60 +- .../stdlib/string}/json-writer.h | 0 runtime-common/stdlib/string/string-context.h | 3 + .../stdlib/string}/to-json-processor.h | 14 +- runtime-light/state/component-state.cpp | 2 +- .../stdlib/confdata/confdata-functions.cpp | 2 +- runtime-light/stdlib/string/json-functions.h | 1 + runtime-light/utils/utils.cmake | 3 +- runtime/from-json-processor.cpp | 11 - runtime/json-functions.cpp | 583 ------------------ runtime/json-functions.h | 178 +----- runtime/memcache.cpp | 2 +- runtime/misc.cpp | 2 +- runtime/runtime.cmake | 1 - server/php-engine.cpp | 2 +- 24 files changed, 150 insertions(+), 929 deletions(-) rename builtin-functions/kphp-light/{unsupported => }/serialize.txt (51%) create mode 100644 builtin-functions/kphp-light/unsupported/unsupported-serialize.txt rename {runtime => runtime-common/stdlib/string}/from-json-processor.h (85%) rename {runtime-light/utils => runtime-common/stdlib/string}/json-functions.cpp (88%) rename {runtime-light/utils => runtime-common/stdlib/string}/json-functions.h (78%) rename {runtime => runtime-common/stdlib/string}/json-processor-utils.h (74%) rename {runtime => runtime-common/stdlib/string}/json-writer.cpp (74%) rename {runtime => runtime-common/stdlib/string}/json-writer.h (100%) rename {runtime => runtime-common/stdlib/string}/to-json-processor.h (92%) create mode 100644 runtime-light/stdlib/string/json-functions.h delete mode 100644 runtime/from-json-processor.cpp delete mode 100644 runtime/json-functions.cpp diff --git a/builtin-functions/kphp-light/functions.txt b/builtin-functions/kphp-light/functions.txt index 36867c0ce7..0df301ad85 100644 --- a/builtin-functions/kphp-light/functions.txt +++ b/builtin-functions/kphp-light/functions.txt @@ -7,6 +7,7 @@ require_once __DIR__ . '/file.txt'; require_once __DIR__ . '/hash.txt'; require_once __DIR__ . '/job-workers.txt'; require_once __DIR__ . '/rpc.txt'; +require_once __DIR__ . '/serialize.txt'; require_once __DIR__ . '/string.txt'; require_once __DIR__ . '/server.txt'; require_once __DIR__ . '/kphp-toggles.txt'; diff --git a/builtin-functions/kphp-light/unsupported/serialize.txt b/builtin-functions/kphp-light/serialize.txt similarity index 51% rename from builtin-functions/kphp-light/unsupported/serialize.txt rename to builtin-functions/kphp-light/serialize.txt index cf02a6dfd5..b4240cef02 100644 --- a/builtin-functions/kphp-light/unsupported/serialize.txt +++ b/builtin-functions/kphp-light/serialize.txt @@ -1,37 +1,11 @@ ; -/** @kphp-extern-func-info cpp_template_call can_throw */ -function instance_deserialize_safe($serialized ::: string, $to_type ::: string) ::: instance<^2>; - define('JSON_UNESCAPED_UNICODE', 1); define('JSON_FORCE_OBJECT', 16); define('JSON_PRETTY_PRINT', 128); // TODO: add actual support define('JSON_PARTIAL_OUTPUT_ON_ERROR', 512); define('JSON_PRESERVE_ZERO_FRACTION', 1024); -/** @kphp-generate-stub-class */ class JsonEncoder { const rename_policy = 'none'; const visibility_policy = 'all'; @@ -52,4 +26,3 @@ class JsonEncoder { /** @kphp-extern-func-info cpp_template_call */ static function from_json_impl(string $encoder_tag, string $json, string $class_name) ::: instance<^3>; } - diff --git a/builtin-functions/kphp-light/unsupported-functions.txt b/builtin-functions/kphp-light/unsupported-functions.txt index bbcaa219ef..e844405f58 100644 --- a/builtin-functions/kphp-light/unsupported-functions.txt +++ b/builtin-functions/kphp-light/unsupported-functions.txt @@ -10,7 +10,7 @@ require_once __DIR__ . '/unsupported/math.txt'; require_once __DIR__ . '/unsupported/memcache.txt'; require_once __DIR__ . '/unsupported/misc.txt'; require_once __DIR__ . '/unsupported/regex.txt'; -require_once __DIR__ . '/unsupported/serialize.txt'; +require_once __DIR__ . '/unsupported/unsupported-serialize.txt'; require_once __DIR__ . '/unsupported/spl.txt'; require_once __DIR__ . '/unsupported/uberh3.txt'; require_once __DIR__ . '/unsupported/unsupported-server.txt'; diff --git a/builtin-functions/kphp-light/unsupported/unsupported-serialize.txt b/builtin-functions/kphp-light/unsupported/unsupported-serialize.txt new file mode 100644 index 0000000000..3d5dca1d7f --- /dev/null +++ b/builtin-functions/kphp-light/unsupported/unsupported-serialize.txt @@ -0,0 +1,27 @@ +; +/** @kphp-extern-func-info cpp_template_call can_throw */ +function instance_deserialize_safe($serialized ::: string, $to_type ::: string) ::: instance<^2>; + diff --git a/runtime-common/stdlib/stdlib.cmake b/runtime-common/stdlib/stdlib.cmake index 2dcff87dbf..b66413b5fd 100644 --- a/runtime-common/stdlib/stdlib.cmake +++ b/runtime-common/stdlib/stdlib.cmake @@ -1,5 +1,7 @@ -prepend(STDLIB_STRING stdlib/string/ string-functions.cpp - mbstring-functions.cpp) +prepend(STDLIB_STRING stdlib/string/ json-functions.cpp + json-writer.cpp + mbstring-functions.cpp + string-functions.cpp) prepend(STDLIB_SERVER stdlib/server/ url-functions.cpp) prepend(STDLIB_VKEXT stdlib/vkext/ vkext.cpp vkext_stats.cpp string-processing.cpp) diff --git a/runtime/from-json-processor.h b/runtime-common/stdlib/string/from-json-processor.h similarity index 85% rename from runtime/from-json-processor.h rename to runtime-common/stdlib/string/from-json-processor.h index c31c630bc8..fef3c8cf83 100644 --- a/runtime/from-json-processor.h +++ b/runtime-common/stdlib/string/from-json-processor.h @@ -4,11 +4,12 @@ #pragma once -#include +#include "runtime/context/runtime-context.h" #include "runtime-common/core/runtime-core.h" -#include "runtime/json-functions.h" -#include "runtime/json-processor-utils.h" +#include "runtime-common/stdlib/string/json-functions.h" +#include "runtime-common/stdlib/string/json-processor-utils.h" +#include "runtime-common/stdlib/string/string-context.h" template class FromJsonVisitor { @@ -91,13 +92,13 @@ class FromJsonVisitor { } void do_set(JsonRawString &value, const mixed &json) noexcept { - kphp_runtime_context.static_SB.clean(); - if (!impl_::JsonEncoder{0, false, get_json_obj_magic_key()}.encode(json)) { + RuntimeContext::get().static_SB.clean(); + if (!impl_::JsonEncoder{0, false, get_json_obj_magic_key()}.encode(json, RuntimeContext::get().static_SB)) { error_.append("failed to decode @kphp-json raw_string field "); error_.append(json_path_.to_string()); return; } - value.str = kphp_runtime_context.static_SB.str(); + value.str = RuntimeContext::get().static_SB.str(); } template @@ -169,14 +170,14 @@ class_instance from_json_impl(const mixed &json, JsonPath &json_path) noexcep FromJsonVisitor visitor{json, impl_::IsJsonFlattenClass::value, json_path}; instance.get()->accept(visitor); if (visitor.has_error()) { - JsonEncoderError::msg.append(visitor.get_error()); + StringLibContext::get().last_json_processor_error.append(visitor.get_error()); return {}; } } if constexpr (impl_::HasClassWakeupMethod::value) { instance.get()->wakeup(instance); } - return JsonEncoderError::msg.empty() ? instance : class_instance{}; + return StringLibContext::get().last_json_processor_error.empty() ? instance : class_instance{}; } template @@ -196,18 +197,19 @@ void FromJsonVisitor::do_set(class_instance &klass, const mixed &json) n template ClassName f$JsonEncoder$$from_json_impl(Tag /*tag*/, const string &json_string, const string &/*class_mame*/) noexcept { - JsonEncoderError::msg = {}; + auto &msg = StringLibContext::get().last_json_processor_error; + msg = {}; auto [json, success] = json_decode(json_string, FromJsonVisitor::get_json_obj_magic_key()); if (!success) { - JsonEncoderError::msg.append(json_string.empty() ? "provided empty json string" : "failed to parse json string"); + msg.append(json_string.empty() ? "provided empty json string" : "failed to parse json string"); return {}; } if constexpr (!impl_::IsJsonFlattenClass::value) { if (!json.is_array() || json.as_array().is_vector()) { - JsonEncoderError::msg.append("root element of json string must be an object type, got "); - JsonEncoderError::msg.append(json.get_type_c_str()); + msg.append("root element of json string must be an object type, got "); + msg.append(json.get_type_c_str()); return {}; } } @@ -216,4 +218,6 @@ ClassName f$JsonEncoder$$from_json_impl(Tag /*tag*/, const string &json_string, return from_json_impl(json, json_path); } -string f$JsonEncoder$$getLastError() noexcept; +inline string f$JsonEncoder$$getLastError() noexcept { + return StringLibContext::get().last_json_processor_error; +} diff --git a/runtime-light/utils/json-functions.cpp b/runtime-common/stdlib/string/json-functions.cpp similarity index 88% rename from runtime-light/utils/json-functions.cpp rename to runtime-common/stdlib/string/json-functions.cpp index d736393bd1..57efca34ff 100644 --- a/runtime-light/utils/json-functions.cpp +++ b/runtime-common/stdlib/string/json-functions.cpp @@ -2,16 +2,16 @@ // Copyright (c) 2020 LLC «V Kontakte» // Distributed under the GPL v3 License, see LICENSE.notice.txt -#include "runtime-light/utils/json-functions.h" +#include "runtime-common/stdlib/string/json-functions.h" #include "common/algorithms/find.h" -//#include "runtime/string_functions.h" +#include "runtime-common/stdlib/string/string-functions.h" // note: json-functions.cpp is used for non-typed json implementation: for json_encode() and json_decode() // for classes, e.g. `JsonEncoder::encode(new A)`, see json-writer.cpp and from/to visitors namespace { -void json_append_one_char(unsigned int c, string_buffer & sb) noexcept { +void json_append_one_char(unsigned int c, string_buffer &sb) noexcept { sb.append_char('\\'); sb.append_char('u'); sb.append_char("0123456789abcdef"[c >> 12]); @@ -20,7 +20,7 @@ void json_append_one_char(unsigned int c, string_buffer & sb) noexcept { sb.append_char("0123456789abcdef"[c & 15]); } -bool json_append_char(unsigned int c, string_buffer & sb) noexcept { +bool json_append_char(unsigned int c, string_buffer &sb) noexcept { if (c < 0x10000) { if (0xD7FF < c && c < 0xE000) { return false; @@ -37,8 +37,7 @@ bool json_append_char(unsigned int c, string_buffer & sb) noexcept { return false; } - -bool do_json_encode_string_php(const JsonPath &json_path, const char *s, int len, int64_t options, string_buffer & sb) noexcept { +bool do_json_encode_string_php(const JsonPath &json_path, const char *s, int len, int64_t options, string_buffer &sb) noexcept { int begin_pos = sb.size(); if (options & JSON_UNESCAPED_UNICODE) { sb.reserve(2 * len + 2); @@ -176,7 +175,7 @@ string JsonPath::to_string() const { } unsigned num_parts = std::clamp(depth, 0U, static_cast(arr.size())); string result; - result.reserve_at_least((num_parts+1) * 8); + result.reserve_at_least((num_parts + 1) * 8); result.push_back('/'); for (unsigned i = 0; i < num_parts; i++) { const char *key = arr[i]; @@ -198,13 +197,12 @@ string JsonPath::to_string() const { namespace impl_ { -JsonEncoder::JsonEncoder(int64_t options, bool simple_encode, const char *json_obj_magic_key) noexcept: - options_(options), - simple_encode_(simple_encode), - json_obj_magic_key_(json_obj_magic_key) { -} +JsonEncoder::JsonEncoder(int64_t options, bool simple_encode, const char *json_obj_magic_key) noexcept + : options_(options) + , simple_encode_(simple_encode) + , json_obj_magic_key_(json_obj_magic_key) {} -bool JsonEncoder::encode(bool b, string_buffer & sb) noexcept { +bool JsonEncoder::encode(bool b, string_buffer &sb) noexcept { if (b) { sb.append("true", 4); } else { @@ -213,17 +211,17 @@ bool JsonEncoder::encode(bool b, string_buffer & sb) noexcept { return true; } -bool JsonEncoder::encode_null(string_buffer & sb) const noexcept { +bool JsonEncoder::encode_null(string_buffer &sb) const noexcept { sb.append("null", 4); return true; } -bool JsonEncoder::encode(int64_t i, string_buffer & sb) noexcept { +bool JsonEncoder::encode(int64_t i, string_buffer &sb) noexcept { sb << i; return true; } -bool JsonEncoder::encode(double d, string_buffer & sb) noexcept { +bool JsonEncoder::encode(double d, string_buffer &sb) noexcept { if (vk::any_of_equal(std::fpclassify(d), FP_INFINITE, FP_NAN)) { php_warning("%s: strange double %lf in function json_encode", json_path_.to_string().c_str(), d); if (options_ & JSON_PARTIAL_OUTPUT_ON_ERROR) { @@ -232,17 +230,16 @@ bool JsonEncoder::encode(double d, string_buffer & sb) noexcept { return false; } } else { - //todo:k2 implement f$number_format - sb << /*(simple_encode_ ? f$number_format(d, 6, string{"."}, string{}) : */ string{d}/*)*/; + sb << (simple_encode_ ? f$number_format(d, 6, string{"."}, string{}) : string{d}); } return true; } -bool JsonEncoder::encode(const string &s, string_buffer & sb) noexcept { +bool JsonEncoder::encode(const string &s, string_buffer &sb) noexcept { return do_json_encode_string_php(json_path_, s.c_str(), s.size(), options_, sb); } -bool JsonEncoder::encode(const mixed &v, string_buffer & sb) noexcept { +bool JsonEncoder::encode(const mixed &v, string_buffer &sb) noexcept { switch (v.get_type()) { case mixed::type::NUL: return encode_null(sb); @@ -278,29 +275,22 @@ bool do_json_decode(const char *s, int s_len, int &i, mixed &v, const char *json json_skip_blanks(s, i); switch (s[i]) { case 'n': - if (s[i + 1] == 'u' && - s[i + 2] == 'l' && - s[i + 3] == 'l') { + if (s[i + 1] == 'u' && s[i + 2] == 'l' && s[i + 3] == 'l') { i += 4; return true; } break; case 't': - if (s[i + 1] == 'r' && - s[i + 2] == 'u' && - s[i + 3] == 'e') { + if (s[i + 1] == 'r' && s[i + 2] == 'u' && s[i + 3] == 'e') { i += 4; - new(&v) mixed(true); + new (&v) mixed(true); return true; } break; case 'f': - if (s[i + 1] == 'a' && - s[i + 2] == 'l' && - s[i + 3] == 's' && - s[i + 4] == 'e') { + if (s[i + 1] == 'a' && s[i + 2] == 'l' && s[i + 3] == 's' && s[i + 4] == 'e') { i += 5; - new(&v) mixed(false); + new (&v) mixed(false); return true; } break; @@ -362,8 +352,7 @@ bool do_json_decode(const char *s, int s_len, int &i, mixed &v, const char *json } if (0xD7FF < num && num < 0xE000) { - if (s[i + 1] == '\\' && s[i + 2] == 'u' && - isxdigit(s[i + 3]) && isxdigit(s[i + 4]) && isxdigit(s[i + 5]) && isxdigit(s[i + 6])) { + if (s[i + 1] == '\\' && s[i + 2] == 'u' && isxdigit(s[i + 3]) && isxdigit(s[i + 4]) && isxdigit(s[i + 5]) && isxdigit(s[i + 6])) { i += 2; int u = 0; for (int t = 0; t < 4; t++) { @@ -417,7 +406,7 @@ bool do_json_decode(const char *s, int s_len, int &i, mixed &v, const char *json } value.shrink(l); - new(&v) mixed(value); + new (&v) mixed(value); i++; return true; } @@ -444,7 +433,7 @@ bool do_json_decode(const char *s, int s_len, int &i, mixed &v, const char *json i++; } - new(&v) mixed(res); + new (&v) mixed(res); return true; } case '{': { @@ -481,7 +470,7 @@ bool do_json_decode(const char *s, int s_len, int &i, mixed &v, const char *json res[string{json_obj_magic_key}] = true; } - new(&v) mixed(res); + new (&v) mixed(res); return true; } default: { @@ -493,7 +482,7 @@ bool do_json_decode(const char *s, int s_len, int &i, mixed &v, const char *json int64_t intval = 0; if (php_try_to_int(s + i, j - i, &intval)) { i = j; - new(&v) mixed(intval); + new (&v) mixed(intval); return true; } @@ -501,7 +490,7 @@ bool do_json_decode(const char *s, int s_len, int &i, mixed &v, const char *json double floatval = strtod(s + i, &end_ptr); if (end_ptr == s + j) { i = j; - new(&v) mixed(floatval); + new (&v) mixed(floatval); return true; } } diff --git a/runtime-light/utils/json-functions.h b/runtime-common/stdlib/string/json-functions.h similarity index 78% rename from runtime-light/utils/json-functions.h rename to runtime-common/stdlib/string/json-functions.h index 5ed8d3a38b..b2e56a209b 100644 --- a/runtime-light/utils/json-functions.h +++ b/runtime-common/stdlib/string/json-functions.h @@ -9,7 +9,6 @@ #include "common/mixin/not_copyable.h" #include "runtime-common/core/runtime-core.h" - constexpr int64_t JSON_UNESCAPED_UNICODE = 1; constexpr int64_t JSON_FORCE_OBJECT = 16; constexpr int64_t JSON_PRETTY_PRINT = 128; // TODO: add actual support to untyped @@ -22,7 +21,7 @@ constexpr int64_t JSON_AVAILABLE_FLAGS_TYPED = JSON_PRETTY_PRINT | JSON_PRESERVE struct JsonPath { constexpr static int MAX_DEPTH = 8; - std::array arr; + std::array arr; unsigned depth = 0; void enter(const char *key) noexcept { @@ -47,31 +46,30 @@ class JsonEncoder : vk::not_copyable { public: JsonEncoder(int64_t options, bool simple_encode, const char *json_obj_magic_key = nullptr) noexcept; - //todo:k2 change static_SB everywhere to string_buffer arg - bool encode(bool b, string_buffer & sb) noexcept; - bool encode(int64_t i, string_buffer & sb) noexcept; - bool encode(const string &s, string_buffer & sb) noexcept; - bool encode(double d, string_buffer & sb) noexcept; - bool encode(const mixed &v, string_buffer & sb) noexcept; + // todo:k2 change static_SB everywhere to string_buffer arg + bool encode(bool b, string_buffer &sb) noexcept; + bool encode(int64_t i, string_buffer &sb) noexcept; + bool encode(const string &s, string_buffer &sb) noexcept; + bool encode(double d, string_buffer &sb) noexcept; + bool encode(const mixed &v, string_buffer &sb) noexcept; template - bool encode(const array &arr, string_buffer & sb) noexcept; + bool encode(const array &arr, string_buffer &sb) noexcept; template - bool encode(const Optional &opt, string_buffer & sb) noexcept; + bool encode(const Optional &opt, string_buffer &sb) noexcept; private: - bool encode_null(string_buffer & sb) const noexcept; + bool encode_null(string_buffer &sb) const noexcept; JsonPath json_path_; const int64_t options_{0}; - //todo:k2 use simple_encode - [[maybe_unused]] const bool simple_encode_{false}; + const bool simple_encode_{false}; const char *json_obj_magic_key_{nullptr}; }; template -bool JsonEncoder::encode(const array &arr, string_buffer & sb) noexcept { +bool JsonEncoder::encode(const array &arr, string_buffer &sb) noexcept { bool is_vector = arr.is_vector(); const bool force_object = static_cast(JSON_FORCE_OBJECT & options_); if (!force_object && !is_vector && arr.is_pseudo_vector()) { @@ -142,7 +140,7 @@ bool JsonEncoder::encode(const array &arr, string_buffer & sb) noexcept { } template -bool JsonEncoder::encode(const Optional &opt, string_buffer & sb) noexcept { +bool JsonEncoder::encode(const Optional &opt, string_buffer &sb) noexcept { switch (opt.value_state()) { case OptionalState::has_value: return encode(opt.val(), sb); @@ -170,8 +168,6 @@ Optional f$json_encode(const T &v, int64_t options = 0, bool simple_enco return sb.c_str(); } -//todo:k2 implement string f$vk_json_encode_safe(const T &v, bool simple_encode = true) noexcept - template inline Optional f$vk_json_encode(const T &v) noexcept { return f$json_encode(v, 0, true); @@ -179,14 +175,3 @@ inline Optional f$vk_json_encode(const T &v) noexcept { std::pair json_decode(const string &v, const char *json_obj_magic_key = nullptr) noexcept; mixed f$json_decode(const string &v, bool assoc = false) noexcept; - -template -string f$JsonEncoder$$to_json_impl(Tag /*tag*/, const class_instance &, int64_t = 0, const array & = {}) noexcept { - php_critical_error("call to unsupported function"); -} - -template -ClassName f$JsonEncoder$$from_json_impl(Tag /*tag*/, const string &, const string & /*class_mame*/) noexcept { - php_critical_error("call to unsupported function"); -} - diff --git a/runtime/json-processor-utils.h b/runtime-common/stdlib/string/json-processor-utils.h similarity index 74% rename from runtime/json-processor-utils.h rename to runtime-common/stdlib/string/json-processor-utils.h index 015cf990d5..a27f5d1f86 100644 --- a/runtime/json-processor-utils.h +++ b/runtime-common/stdlib/string/json-processor-utils.h @@ -6,18 +6,20 @@ #include +#include "runtime-common/core/runtime-core.h" + namespace impl_ { template> -struct IsJsonFlattenClass : std::false_type{}; +struct IsJsonFlattenClass : std::false_type {}; template -struct IsJsonFlattenClass> : std::true_type{}; +struct IsJsonFlattenClass> : std::true_type {}; template> -struct HasClassWakeupMethod : std::false_type{}; +struct HasClassWakeupMethod : std::false_type {}; template -struct HasClassWakeupMethod> : std::true_type{}; +struct HasClassWakeupMethod> : std::true_type {}; } // namespace impl_ struct JsonRawString { @@ -25,7 +27,3 @@ struct JsonRawString { : str(s) {} string &str; }; - -struct JsonEncoderError { - static string msg; -}; diff --git a/runtime/json-writer.cpp b/runtime-common/stdlib/string/json-writer.cpp similarity index 74% rename from runtime/json-writer.cpp rename to runtime-common/stdlib/string/json-writer.cpp index 2e9ef7cdbc..321a12a3fd 100644 --- a/runtime/json-writer.cpp +++ b/runtime-common/stdlib/string/json-writer.cpp @@ -2,7 +2,7 @@ // Copyright (c) 2022 LLC «V Kontakte» // Distributed under the GPL v3 License, see LICENSE.notice.txt -#include "runtime/json-writer.h" +#include "runtime-common/stdlib/string/json-writer.h" #include "runtime/array_functions.h" #include "runtime/context/runtime-context.h" @@ -57,18 +57,18 @@ static void escape_json_string(string_buffer &buffer, std::string_view s) noexce JsonWriter::JsonWriter(bool pretty_print, bool preserve_zero_fraction) noexcept : pretty_print_(pretty_print) , preserve_zero_fraction_(preserve_zero_fraction) { - kphp_runtime_context.static_SB.clean(); + RuntimeContext::get().static_SB.clean(); } JsonWriter::~JsonWriter() noexcept { - kphp_runtime_context.static_SB.clean(); + RuntimeContext::get().static_SB.clean(); } bool JsonWriter::write_bool(bool b) noexcept { if (!register_value()) { return false; } - b ? kphp_runtime_context.static_SB.append("true", 4) : kphp_runtime_context.static_SB.append("false", 5); + b ? RuntimeContext::get().static_SB.append("true", 4) : RuntimeContext::get().static_SB.append("false", 5); return true; } @@ -76,7 +76,7 @@ bool JsonWriter::write_int(int64_t i) noexcept { if (!register_value()) { return false; } - kphp_runtime_context.static_SB << i; + RuntimeContext::get().static_SB << i; return true; } @@ -88,13 +88,13 @@ bool JsonWriter::write_double(double d) noexcept { d = 0.0; } if (double_precision_) { - kphp_runtime_context.static_SB << f$round(d, double_precision_); + RuntimeContext::get().static_SB << f$round(d, double_precision_); } else { - kphp_runtime_context.static_SB << d; + RuntimeContext::get().static_SB << d; } if (preserve_zero_fraction_) { if (double dummy = 0.0; std::modf(d, &dummy) == 0.0) { - kphp_runtime_context.static_SB << ".0"; + RuntimeContext::get().static_SB << ".0"; } } return true; @@ -104,10 +104,10 @@ bool JsonWriter::write_string(const string &s) noexcept { if (!register_value()) { return false; } - kphp_runtime_context.static_SB.reserve(2 * s.size() + 2); - kphp_runtime_context.static_SB.append_char('"'); - escape_json_string(kphp_runtime_context.static_SB, {s.c_str(), s.size()}); - kphp_runtime_context.static_SB.append_char('"'); + RuntimeContext::get().static_SB.reserve(2 * s.size() + 2); + RuntimeContext::get().static_SB.append_char('"'); + escape_json_string(RuntimeContext::get().static_SB, {s.c_str(), s.size()}); + RuntimeContext::get().static_SB.append_char('"'); return true; } @@ -115,7 +115,7 @@ bool JsonWriter::write_raw_string(const string &s) noexcept { if (!register_value()) { return false; } - kphp_runtime_context.static_SB << s; + RuntimeContext::get().static_SB << s; return true; } @@ -123,7 +123,7 @@ bool JsonWriter::write_null() noexcept { if (!register_value()) { return false; } - kphp_runtime_context.static_SB.append("null", 4); + RuntimeContext::get().static_SB.append("null", 4); return true; } @@ -133,22 +133,22 @@ bool JsonWriter::write_key(std::string_view key, bool escape) noexcept { return false; } if (stack_.back().values_count) { - kphp_runtime_context.static_SB << ','; + RuntimeContext::get().static_SB << ','; } if (pretty_print_) { - kphp_runtime_context.static_SB << '\n'; + RuntimeContext::get().static_SB << '\n'; write_indent(); } - kphp_runtime_context.static_SB << '"'; + RuntimeContext::get().static_SB << '"'; if (escape) { - escape_json_string(kphp_runtime_context.static_SB, key); + escape_json_string(RuntimeContext::get().static_SB, key); } else { - kphp_runtime_context.static_SB.append(key.data(), key.size()); + RuntimeContext::get().static_SB.append(key.data(), key.size()); } - kphp_runtime_context.static_SB << '"'; - kphp_runtime_context.static_SB << ':'; + RuntimeContext::get().static_SB << '"'; + RuntimeContext::get().static_SB << ':'; if (pretty_print_) { - kphp_runtime_context.static_SB << ' '; + RuntimeContext::get().static_SB << ' '; } return true; } @@ -178,7 +178,7 @@ string JsonWriter::get_error() const noexcept { } string JsonWriter::get_final_json() const noexcept { - return kphp_runtime_context.static_SB.str(); + return RuntimeContext::get().static_SB.str(); } bool JsonWriter::new_level(bool is_array) noexcept { @@ -187,7 +187,7 @@ bool JsonWriter::new_level(bool is_array) noexcept { } stack_.emplace_back(NestedLevel{.in_array = is_array}); - kphp_runtime_context.static_SB << (is_array ? '[' : '{'); + RuntimeContext::get().static_SB << (is_array ? '[' : '{'); indent_ += 4; return true; } @@ -209,11 +209,11 @@ bool JsonWriter::exit_level(bool is_array) noexcept { indent_ -= 4; if (pretty_print_ && cur_level.values_count) { - kphp_runtime_context.static_SB << '\n'; + RuntimeContext::get().static_SB << '\n'; write_indent(); } - kphp_runtime_context.static_SB << (is_array ? ']' : '}'); + RuntimeContext::get().static_SB << (is_array ? ']' : '}'); return true; } @@ -229,10 +229,10 @@ bool JsonWriter::register_value() noexcept { auto &top = stack_.back(); if (top.in_array) { if (top.values_count) { - kphp_runtime_context.static_SB << ','; + RuntimeContext::get().static_SB << ','; } if (pretty_print_) { - kphp_runtime_context.static_SB << '\n'; + RuntimeContext::get().static_SB << '\n'; write_indent(); } } @@ -243,9 +243,9 @@ bool JsonWriter::register_value() noexcept { void JsonWriter::write_indent() const noexcept { if (indent_) { - kphp_runtime_context.static_SB.reserve(indent_); + RuntimeContext::get().static_SB.reserve(indent_); for (std::size_t i = 0; i < indent_; ++i) { - kphp_runtime_context.static_SB.append_char(' '); + RuntimeContext::get().static_SB.append_char(' '); } } } diff --git a/runtime/json-writer.h b/runtime-common/stdlib/string/json-writer.h similarity index 100% rename from runtime/json-writer.h rename to runtime-common/stdlib/string/json-writer.h diff --git a/runtime-common/stdlib/string/string-context.h b/runtime-common/stdlib/string/string-context.h index d655fc18fe..37553df004 100644 --- a/runtime-common/stdlib/string/string-context.h +++ b/runtime-common/stdlib/string/string-context.h @@ -45,6 +45,9 @@ class StringLibContext final : vk::not_copyable { // mb_string context bool detect_incorrect_encoding_names{}; + // from-json-processor context + string last_json_processor_error; + static StringLibContext &get() noexcept; }; diff --git a/runtime/to-json-processor.h b/runtime-common/stdlib/string/to-json-processor.h similarity index 92% rename from runtime/to-json-processor.h rename to runtime-common/stdlib/string/to-json-processor.h index 3ba5c7f333..8e24c767a3 100644 --- a/runtime/to-json-processor.h +++ b/runtime-common/stdlib/string/to-json-processor.h @@ -5,8 +5,10 @@ #pragma once #include "runtime-common/core/runtime-core.h" -#include "runtime/json-processor-utils.h" -#include "runtime/json-writer.h" +#include "runtime-common/stdlib/string/json-functions.h" +#include "runtime-common/stdlib/string/json-processor-utils.h" +#include "runtime-common/stdlib/string/json-writer.h" +#include "runtime-common/stdlib/string/string-context.h" template class ToJsonVisitor { @@ -138,7 +140,7 @@ class ToJsonVisitor { template void to_json_impl(const class_instance &klass, impl_::JsonWriter &writer, const array &more, std::size_t depth = 0) noexcept { if (depth > 64) { - JsonEncoderError::msg.assign("allowed depth=64 of json object exceeded"); + StringLibContext::get().last_json_processor_error.assign("allowed depth=64 of json object exceeded"); return; } @@ -178,13 +180,13 @@ string f$JsonEncoder$$to_json_impl(Tag /*tag*/, const class_instance &klass, if (unlikely(has_unsupported_option)) { php_warning("Wrong parameter flags = %" PRIi64 " in function JsonEncoder::encode", flags); } - - JsonEncoderError::msg = {}; + auto &error_msg = StringLibContext::get().last_json_processor_error; + error_msg = {}; impl_::JsonWriter writer{(flags & JSON_PRETTY_PRINT) > 0, (flags & JSON_PRESERVE_ZERO_FRACTION) > 0}; to_json_impl(klass, writer, more); - if (!JsonEncoderError::msg.empty()) { + if (!error_msg.empty()) { return {}; } if (!writer.is_complete()) { diff --git a/runtime-light/state/component-state.cpp b/runtime-light/state/component-state.cpp index 924fd6cbd7..b97d8d3c68 100644 --- a/runtime-light/state/component-state.cpp +++ b/runtime-light/state/component-state.cpp @@ -11,8 +11,8 @@ #include "common/php-functions.h" #include "runtime-common/core/runtime-core.h" #include "runtime-common/core/utils/kphp-assert-core.h" +#include "runtime-common/stdlib/string/json-functions.h" #include "runtime-light/k2-platform/k2-api.h" -#include "runtime-light/utils/json-functions.h" void ComponentState::parse_ini_arg(std::string_view key_view, std::string_view value_view) noexcept { if (key_view.size() <= INI_ARG_PREFIX.size()) [[unlikely]] { diff --git a/runtime-light/stdlib/confdata/confdata-functions.cpp b/runtime-light/stdlib/confdata/confdata-functions.cpp index aa2666c2c6..85e7e4cc49 100644 --- a/runtime-light/stdlib/confdata/confdata-functions.cpp +++ b/runtime-light/stdlib/confdata/confdata-functions.cpp @@ -11,13 +11,13 @@ #include "runtime-common/core/runtime-core.h" #include "runtime-common/core/utils/kphp-assert-core.h" +#include "runtime-common/stdlib/string/json-functions.h" #include "runtime-light/coroutine/task.h" #include "runtime-light/state/instance-state.h" #include "runtime-light/stdlib/component/component-api.h" #include "runtime-light/tl/tl-core.h" #include "runtime-light/tl/tl-functions.h" #include "runtime-light/tl/tl-types.h" -#include "runtime-light/utils/json-functions.h" namespace { diff --git a/runtime-light/stdlib/string/json-functions.h b/runtime-light/stdlib/string/json-functions.h new file mode 100644 index 0000000000..9df7a3e45c --- /dev/null +++ b/runtime-light/stdlib/string/json-functions.h @@ -0,0 +1 @@ +// todo:k2 implement string f$vk_json_encode_safe(const T &v, bool simple_encode = true) noexcept diff --git a/runtime-light/utils/utils.cmake b/runtime-light/utils/utils.cmake index b8e422732b..25d6c58900 100644 --- a/runtime-light/utils/utils.cmake +++ b/runtime-light/utils/utils.cmake @@ -1,2 +1 @@ -prepend(RUNTIME_LIGHT_UTILS_SRC utils/ panic.cpp php_assert.cpp - json-functions.cpp) +prepend(RUNTIME_LIGHT_UTILS_SRC utils/ panic.cpp php_assert.cpp) diff --git a/runtime/from-json-processor.cpp b/runtime/from-json-processor.cpp deleted file mode 100644 index 22c45df0f4..0000000000 --- a/runtime/from-json-processor.cpp +++ /dev/null @@ -1,11 +0,0 @@ -// Compiler for PHP (aka KPHP) -// Copyright (c) 2022 LLC «V Kontakte» -// Distributed under the GPL v3 License, see LICENSE.notice.txt - -#include "runtime/from-json-processor.h" - -string JsonEncoderError::msg; - -string f$JsonEncoder$$getLastError() noexcept { - return JsonEncoderError::msg; -} diff --git a/runtime/json-functions.cpp b/runtime/json-functions.cpp deleted file mode 100644 index b4584092d6..0000000000 --- a/runtime/json-functions.cpp +++ /dev/null @@ -1,583 +0,0 @@ -// Compiler for PHP (aka KPHP) -// Copyright (c) 2020 LLC «V Kontakte» -// Distributed under the GPL v3 License, see LICENSE.notice.txt - -#include "runtime/json-functions.h" - -#include "common/algorithms/find.h" -#include "runtime-common/stdlib/string/string-functions.h" - -// note: json-functions.cpp is used for non-typed json implementation: for json_encode() and json_decode() -// for classes, e.g. `JsonEncoder::encode(new A)`, see json-writer.cpp and from/to visitors -namespace { - -void json_append_one_char(unsigned int c) noexcept { - kphp_runtime_context.static_SB.append_char('\\'); - kphp_runtime_context.static_SB.append_char('u'); - kphp_runtime_context.static_SB.append_char("0123456789abcdef"[c >> 12]); - kphp_runtime_context.static_SB.append_char("0123456789abcdef"[(c >> 8) & 15]); - kphp_runtime_context.static_SB.append_char("0123456789abcdef"[(c >> 4) & 15]); - kphp_runtime_context.static_SB.append_char("0123456789abcdef"[c & 15]); -} - -bool json_append_char(unsigned int c) noexcept { - if (c < 0x10000) { - if (0xD7FF < c && c < 0xE000) { - return false; - } - json_append_one_char(c); - return true; - } - if (c <= 0x10ffff) { - c -= 0x10000; - json_append_one_char(0xD800 | (c >> 10)); - json_append_one_char(0xDC00 | (c & 0x3FF)); - return true; - } - return false; -} - - -bool do_json_encode_string_php(const JsonPath &json_path, const char *s, int len, int64_t options) noexcept { - int begin_pos = kphp_runtime_context.static_SB.size(); - if (options & JSON_UNESCAPED_UNICODE) { - kphp_runtime_context.static_SB.reserve(2 * len + 2); - } else { - kphp_runtime_context.static_SB.reserve(6 * len + 2); - } - kphp_runtime_context.static_SB.append_char('"'); - - auto fire_error = [json_path, begin_pos](int pos) { - php_warning("%s: Not a valid utf-8 character at pos %d in function json_encode", json_path.to_string().c_str(), pos); - kphp_runtime_context.static_SB.set_pos(begin_pos); - kphp_runtime_context.static_SB.append("null", 4); - return false; - }; - - for (int pos = 0; pos < len; pos++) { - switch (s[pos]) { - case '"': - kphp_runtime_context.static_SB.append_char('\\'); - kphp_runtime_context.static_SB.append_char('"'); - break; - case '\\': - kphp_runtime_context.static_SB.append_char('\\'); - kphp_runtime_context.static_SB.append_char('\\'); - break; - case '/': - kphp_runtime_context.static_SB.append_char('\\'); - kphp_runtime_context.static_SB.append_char('/'); - break; - case '\b': - kphp_runtime_context.static_SB.append_char('\\'); - kphp_runtime_context.static_SB.append_char('b'); - break; - case '\f': - kphp_runtime_context.static_SB.append_char('\\'); - kphp_runtime_context.static_SB.append_char('f'); - break; - case '\n': - kphp_runtime_context.static_SB.append_char('\\'); - kphp_runtime_context.static_SB.append_char('n'); - break; - case '\r': - kphp_runtime_context.static_SB.append_char('\\'); - kphp_runtime_context.static_SB.append_char('r'); - break; - case '\t': - kphp_runtime_context.static_SB.append_char('\\'); - kphp_runtime_context.static_SB.append_char('t'); - break; - case 0 ... 7: - case 11: - case 14 ... 31: - json_append_one_char(s[pos]); - break; - case -128 ... -1: { - const int a = s[pos]; - if ((a & 0x40) == 0) { - return fire_error(pos); - } - - const int b = s[++pos]; - if ((b & 0xc0) != 0x80) { - return fire_error(pos); - } - if ((a & 0x20) == 0) { - if ((a & 0x1e) <= 0) { - return fire_error(pos); - } - if (options & JSON_UNESCAPED_UNICODE) { - kphp_runtime_context.static_SB.append_char(static_cast(a)); - kphp_runtime_context.static_SB.append_char(static_cast(b)); - } else if (!json_append_char(((a & 0x1f) << 6) | (b & 0x3f))) { - return fire_error(pos); - } - break; - } - - const int c = s[++pos]; - if ((c & 0xc0) != 0x80) { - return fire_error(pos); - } - if ((a & 0x10) == 0) { - if (((a & 0x0f) | (b & 0x20)) <= 0) { - return fire_error(pos); - } - if (options & JSON_UNESCAPED_UNICODE) { - kphp_runtime_context.static_SB.append_char(static_cast(a)); - kphp_runtime_context.static_SB.append_char(static_cast(b)); - kphp_runtime_context.static_SB.append_char(static_cast(c)); - } else if (!json_append_char(((a & 0x0f) << 12) | ((b & 0x3f) << 6) | (c & 0x3f))) { - return fire_error(pos); - } - break; - } - - const int d = s[++pos]; - if ((d & 0xc0) != 0x80) { - return fire_error(pos); - } - if ((a & 0x08) == 0) { - if (((a & 0x07) | (b & 0x30)) <= 0) { - return fire_error(pos); - } - if (options & JSON_UNESCAPED_UNICODE) { - kphp_runtime_context.static_SB.append_char(static_cast(a)); - kphp_runtime_context.static_SB.append_char(static_cast(b)); - kphp_runtime_context.static_SB.append_char(static_cast(c)); - kphp_runtime_context.static_SB.append_char(static_cast(d)); - } else if (!json_append_char(((a & 0x07) << 18) | ((b & 0x3f) << 12) | ((c & 0x3f) << 6) | (d & 0x3f))) { - return fire_error(pos); - } - break; - } - - return fire_error(pos); - } - default: - kphp_runtime_context.static_SB.append_char(s[pos]); - break; - } - } - - kphp_runtime_context.static_SB.append_char('"'); - return true; -} - -bool do_json_encode_string_vkext(const char *s, int len) noexcept { - kphp_runtime_context.static_SB.reserve(2 * len + 2); - if (kphp_runtime_context.sb_lib_context.error_flag == STRING_BUFFER_ERROR_FLAG_FAILED) { - return false; - } - - kphp_runtime_context.static_SB.append_char('"'); - - for (int pos = 0; pos < len; pos++) { - char c = s[pos]; - if (unlikely (static_cast(c) < 32u)) { - switch (c) { - case '\b': - kphp_runtime_context.static_SB.append_char('\\'); - kphp_runtime_context.static_SB.append_char('b'); - break; - case '\f': - kphp_runtime_context.static_SB.append_char('\\'); - kphp_runtime_context.static_SB.append_char('f'); - break; - case '\n': - kphp_runtime_context.static_SB.append_char('\\'); - kphp_runtime_context.static_SB.append_char('n'); - break; - case '\r': - kphp_runtime_context.static_SB.append_char('\\'); - kphp_runtime_context.static_SB.append_char('r'); - break; - case '\t': - kphp_runtime_context.static_SB.append_char('\\'); - kphp_runtime_context.static_SB.append_char('t'); - break; - } - } else { - if (c == '"' || c == '\\' || c == '/') { - kphp_runtime_context.static_SB.append_char('\\'); - } - kphp_runtime_context.static_SB.append_char(c); - } - } - - kphp_runtime_context.static_SB.append_char('"'); - - return true; -} - -} // namespace - -string JsonPath::to_string() const { - // this function is called only when error is occurred, so it's not - // very performance-sensitive - - if (depth == 0) { - return string{"/", 1}; - } - unsigned num_parts = std::clamp(depth, 0U, static_cast(arr.size())); - string result; - result.reserve_at_least((num_parts+1) * 8); - result.push_back('/'); - for (unsigned i = 0; i < num_parts; i++) { - const char *key = arr[i]; - if (key == nullptr) { - // int key indexing - result.append("[.]"); - } else { - // string key indexing - result.append("['"); - result.append(arr[i]); - result.append("']"); - } - } - if (depth >= arr.size()) { - result.append("..."); - } - return result; -} - -namespace impl_ { - -JsonEncoder::JsonEncoder(int64_t options, bool simple_encode, const char *json_obj_magic_key) noexcept: - options_(options), - simple_encode_(simple_encode), - json_obj_magic_key_(json_obj_magic_key) { -} - -bool JsonEncoder::encode(bool b) noexcept { - if (b) { - kphp_runtime_context.static_SB.append("true", 4); - } else { - kphp_runtime_context.static_SB.append("false", 5); - } - return true; -} - -bool JsonEncoder::encode_null() const noexcept { - kphp_runtime_context.static_SB.append("null", 4); - return true; -} - -bool JsonEncoder::encode(int64_t i) noexcept { - kphp_runtime_context.static_SB << i; - return true; -} - -bool JsonEncoder::encode(double d) noexcept { - if (vk::any_of_equal(std::fpclassify(d), FP_INFINITE, FP_NAN)) { - php_warning("%s: strange double %lf in function json_encode", json_path_.to_string().c_str(), d); - if (options_ & JSON_PARTIAL_OUTPUT_ON_ERROR) { - kphp_runtime_context.static_SB.append("0", 1); - } else { - return false; - } - } else { - kphp_runtime_context.static_SB << (simple_encode_ ? f$number_format(d, 6, string{"."}, string{}) : string{d}); - } - return true; -} - -bool JsonEncoder::encode(const string &s) noexcept { - return simple_encode_ ? do_json_encode_string_vkext(s.c_str(), s.size()) : do_json_encode_string_php(json_path_, s.c_str(), s.size(), options_); -} - -bool JsonEncoder::encode(const mixed &v) noexcept { - switch (v.get_type()) { - case mixed::type::NUL: - return encode_null(); - case mixed::type::BOOLEAN: - return encode(v.as_bool()); - case mixed::type::INTEGER: - return encode(v.as_int()); - case mixed::type::FLOAT: - return encode(v.as_double()); - case mixed::type::STRING: - return encode(v.as_string()); - case mixed::type::ARRAY: - return encode(v.as_array()); - case mixed::type::OBJECT: - php_warning("Objects (%s) are not supported in JsonEncoder", v.get_type_or_class_name()); - return false; - default: - __builtin_unreachable(); - } -} - -} // namespace impl_ - -namespace { - -void json_skip_blanks(const char *s, int &i) noexcept { - while (vk::any_of_equal(s[i], ' ', '\t', '\r', '\n')) { - i++; - } -} - -bool do_json_decode(const char *s, int s_len, int &i, mixed &v, const char *json_obj_magic_key) noexcept { - if (!v.is_null()) { - v.destroy(); - } - json_skip_blanks(s, i); - switch (s[i]) { - case 'n': - if (s[i + 1] == 'u' && - s[i + 2] == 'l' && - s[i + 3] == 'l') { - i += 4; - return true; - } - break; - case 't': - if (s[i + 1] == 'r' && - s[i + 2] == 'u' && - s[i + 3] == 'e') { - i += 4; - new(&v) mixed(true); - return true; - } - break; - case 'f': - if (s[i + 1] == 'a' && - s[i + 2] == 'l' && - s[i + 3] == 's' && - s[i + 4] == 'e') { - i += 5; - new(&v) mixed(false); - return true; - } - break; - case '"': { - int j = i + 1; - int slashes = 0; - while (j < s_len && s[j] != '"') { - if (s[j] == '\\') { - slashes++; - j++; - } - j++; - } - if (j < s_len) { - int len = j - i - 1 - slashes; - - string value(len, false); - - i++; - int l; - for (l = 0; l < len && i < j; l++) { - char c = s[i]; - if (c == '\\') { - i++; - switch (s[i]) { - case '"': - case '\\': - case '/': - value[l] = s[i]; - break; - case 'b': - value[l] = '\b'; - break; - case 'f': - value[l] = '\f'; - break; - case 'n': - value[l] = '\n'; - break; - case 'r': - value[l] = '\r'; - break; - case 't': - value[l] = '\t'; - break; - case 'u': - if (isxdigit(s[i + 1]) && isxdigit(s[i + 2]) && isxdigit(s[i + 3]) && isxdigit(s[i + 4])) { - int num = 0; - for (int t = 0; t < 4; t++) { - char c = s[++i]; - if ('0' <= c && c <= '9') { - num = num * 16 + c - '0'; - } else { - c |= 0x20; - if ('a' <= c && c <= 'f') { - num = num * 16 + c - 'a' + 10; - } - } - } - - if (0xD7FF < num && num < 0xE000) { - if (s[i + 1] == '\\' && s[i + 2] == 'u' && - isxdigit(s[i + 3]) && isxdigit(s[i + 4]) && isxdigit(s[i + 5]) && isxdigit(s[i + 6])) { - i += 2; - int u = 0; - for (int t = 0; t < 4; t++) { - char c = s[++i]; - if ('0' <= c && c <= '9') { - u = u * 16 + c - '0'; - } else { - c |= 0x20; - if ('a' <= c && c <= 'f') { - u = u * 16 + c - 'a' + 10; - } - } - } - - if (0xD7FF < u && u < 0xE000) { - num = (((num & 0x3FF) << 10) | (u & 0x3FF)) + 0x10000; - } else { - i -= 6; - return false; - } - } else { - return false; - } - } - - if (num < 128) { - value[l] = static_cast(num); - } else if (num < 0x800) { - value[l++] = static_cast(0xc0 + (num >> 6)); - value[l] = static_cast(0x80 + (num & 63)); - } else if (num < 0xffff) { - value[l++] = static_cast(0xe0 + (num >> 12)); - value[l++] = static_cast(0x80 + ((num >> 6) & 63)); - value[l] = static_cast(0x80 + (num & 63)); - } else { - value[l++] = static_cast(0xf0 + (num >> 18)); - value[l++] = static_cast(0x80 + ((num >> 12) & 63)); - value[l++] = static_cast(0x80 + ((num >> 6) & 63)); - value[l] = static_cast(0x80 + (num & 63)); - } - break; - } - /* fallthrough */ - default: - return false; - } - i++; - } else { - value[l] = s[i++]; - } - } - value.shrink(l); - - new(&v) mixed(value); - i++; - return true; - } - break; - } - case '[': { - array res; - i++; - json_skip_blanks(s, i); - if (s[i] != ']') { - do { - mixed value; - if (!do_json_decode(s, s_len, i, value, json_obj_magic_key)) { - return false; - } - res.push_back(value); - json_skip_blanks(s, i); - } while (s[i++] == ','); - - if (s[i - 1] != ']') { - return false; - } - } else { - i++; - } - - new(&v) mixed(res); - return true; - } - case '{': { - array res; - i++; - json_skip_blanks(s, i); - if (s[i] != '}') { - do { - mixed key; - if (!do_json_decode(s, s_len, i, key, json_obj_magic_key) || !key.is_string()) { - return false; - } - json_skip_blanks(s, i); - if (s[i++] != ':') { - return false; - } - - if (!do_json_decode(s, s_len, i, res[key], json_obj_magic_key)) { - return false; - } - json_skip_blanks(s, i); - } while (s[i++] == ','); - - if (s[i - 1] != '}') { - return false; - } - } else { - i++; - } - - // it's impossible to distinguish whether empty php array was an json array or json object; - // to overcome it we add dummy key to php array that make array::is_vector() returning false, so we have difference - if (json_obj_magic_key && res.empty()) { - res[string{json_obj_magic_key}] = true; - } - - new(&v) mixed(res); - return true; - } - default: { - int j = i; - while (s[j] == '-' || ('0' <= s[j] && s[j] <= '9') || s[j] == 'e' || s[j] == 'E' || s[j] == '+' || s[j] == '.') { - j++; - } - if (j > i) { - int64_t intval = 0; - if (php_try_to_int(s + i, j - i, &intval)) { - i = j; - new(&v) mixed(intval); - return true; - } - - char *end_ptr; - double floatval = strtod(s + i, &end_ptr); - if (end_ptr == s + j) { - i = j; - new(&v) mixed(floatval); - return true; - } - } - break; - } - } - - return false; -} - -} // namespace - -std::pair json_decode(const string &v, const char *json_obj_magic_key) noexcept { - mixed result; - int i = 0; - if (do_json_decode(v.c_str(), v.size(), i, result, json_obj_magic_key)) { - json_skip_blanks(v.c_str(), i); - if (i == static_cast(v.size())) { - bool success = true; - return {result, success}; - } - } - - return {}; -} - -mixed f$json_decode(const string &v, bool assoc) noexcept { - // TODO It was a warning before (in case if assoc is false), but then it was disabled, should we enable it again? - static_cast(assoc); - return json_decode(v).first; -} diff --git a/runtime/json-functions.h b/runtime/json-functions.h index 722c9a7f21..4fbfed5598 100644 --- a/runtime/json-functions.h +++ b/runtime/json-functions.h @@ -5,189 +5,21 @@ #pragma once #include "runtime-common/core/runtime-core.h" +#include "runtime-common/stdlib/string/json-functions.h" #include "runtime/context/runtime-context.h" #include "runtime/exception.h" -#include - -constexpr int64_t JSON_UNESCAPED_UNICODE = 1; -constexpr int64_t JSON_FORCE_OBJECT = 16; -constexpr int64_t JSON_PRETTY_PRINT = 128; // TODO: add actual support to untyped -constexpr int64_t JSON_PARTIAL_OUTPUT_ON_ERROR = 512; -constexpr int64_t JSON_PRESERVE_ZERO_FRACTION = 1024; - -constexpr int64_t JSON_AVAILABLE_OPTIONS = JSON_UNESCAPED_UNICODE | JSON_FORCE_OBJECT | JSON_PARTIAL_OUTPUT_ON_ERROR; -constexpr int64_t JSON_AVAILABLE_FLAGS_TYPED = JSON_PRETTY_PRINT | JSON_PRESERVE_ZERO_FRACTION; - -struct JsonPath { - constexpr static int MAX_DEPTH = 8; - - std::array arr; - unsigned depth = 0; - - void enter(const char *key) noexcept { - if (depth < arr.size()) { - arr[depth] = key; - } - depth++; - } - - void leave() noexcept { - depth--; - } - - string to_string() const; -}; - -namespace impl_ { -// note: this class in runtime is used for non-typed json implementation: for json_encode() and json_decode() -// for classes, e.g. `JsonEncoder::encode(new A)`, see json-writer.h and from/to visitors -// todo somewhen, unify this JsonEncoder and JsonWriter, and support JSON_PRETTY_PRINT then -class JsonEncoder : vk::not_copyable { -public: - JsonEncoder(int64_t options, bool simple_encode, const char *json_obj_magic_key = nullptr) noexcept; - - bool encode(bool b) noexcept; - bool encode(int64_t i) noexcept; - bool encode(const string &s) noexcept; - bool encode(double d) noexcept; - bool encode(const mixed &v) noexcept; - - template - bool encode(const array &arr) noexcept; - - template - bool encode(const Optional &opt) noexcept; - -private: - bool encode_null() const noexcept; - - JsonPath json_path_; - const int64_t options_{0}; - const bool simple_encode_{false}; - const char *json_obj_magic_key_{nullptr}; -}; - -template -bool JsonEncoder::encode(const array &arr) noexcept { - bool is_vector = arr.is_vector(); - const bool force_object = static_cast(JSON_FORCE_OBJECT & options_); - if (!force_object && !is_vector && arr.is_pseudo_vector()) { - if (arr.get_next_key() == arr.count()) { - is_vector = true; - } else { - php_warning("%s: Corner case in json conversion, [] could be easy transformed to {}", json_path_.to_string().c_str()); - } - } - is_vector &= !force_object; - - kphp_runtime_context.static_SB << "{["[is_vector]; - - if (is_vector) { - int i = 0; - json_path_.enter(nullptr); // similar key for all entries - for (auto p : arr) { - if (i != 0) { - kphp_runtime_context.static_SB << ','; - } - if (!encode(p.get_value())) { - if (!(options_ & JSON_PARTIAL_OUTPUT_ON_ERROR)) { - return false; - } - } - i++; - } - json_path_.leave(); - } else { - bool is_first = true; - for (auto p : arr) { - if (!is_first) { - kphp_runtime_context.static_SB << ','; - } - is_first = false; - const char *next_key = nullptr; - const auto key = p.get_key(); - if (array::is_int_key(key)) { - auto int_key = key.to_int(); - next_key = nullptr; - kphp_runtime_context.static_SB << '"' << int_key << '"'; - } else { - const string &str_key = key.as_string(); - // skip service key intended only for distinguish empty json object with empty json array - if (json_obj_magic_key_ && !strcmp(json_obj_magic_key_, str_key.c_str())) { - continue; - } - next_key = str_key.c_str(); - if (!encode(str_key)) { - if (!(options_ & JSON_PARTIAL_OUTPUT_ON_ERROR)) { - return false; - } - } - } - kphp_runtime_context.static_SB << ':'; - json_path_.enter(next_key); - if (!encode(p.get_value())) { - if (!(options_ & JSON_PARTIAL_OUTPUT_ON_ERROR)) { - return false; - } - } - json_path_.leave(); - } - } - - kphp_runtime_context.static_SB << "}]"[is_vector]; - return true; -} - -template -bool JsonEncoder::encode(const Optional &opt) noexcept { - switch (opt.value_state()) { - case OptionalState::has_value: - return encode(opt.val()); - case OptionalState::false_value: - return encode(false); - case OptionalState::null_value: - return encode_null(); - } - __builtin_unreachable(); -} - -} // namespace impl_ - -template -Optional f$json_encode(const T &v, int64_t options = 0, bool simple_encode = false) noexcept { - const bool has_unsupported_option = static_cast(options & ~JSON_AVAILABLE_OPTIONS); - if (unlikely(has_unsupported_option)) { - php_warning("Wrong parameter options = %" PRIi64 " in function json_encode", options); - return false; - } - - kphp_runtime_context.static_SB.clean(); - if (unlikely(!impl_::JsonEncoder(options, simple_encode).encode(v))) { - return false; - } - return kphp_runtime_context.static_SB.str(); -} - template string f$vk_json_encode_safe(const T &v, bool simple_encode = true) noexcept { kphp_runtime_context.static_SB.clean(); kphp_runtime_context.sb_lib_context.error_flag = STRING_BUFFER_ERROR_FLAG_ON; impl_::JsonEncoder(0, simple_encode).encode(v); - if (unlikely(kphp_runtime_context.sb_lib_context.error_flag == STRING_BUFFER_ERROR_FLAG_FAILED)) { + if (unlikely(kphp_runtime_context.sb_lib_context.error_flag == STRING_BUFFER_ERROR_FLAG_FAILED)) { kphp_runtime_context.static_SB.clean(); - kphp_runtime_context.sb_lib_context.error_flag = STRING_BUFFER_ERROR_FLAG_OFF; - THROW_EXCEPTION (new_Exception(string(__FILE__), __LINE__, string("json_encode buffer overflow", 27))); + kphp_runtime_context.sb_lib_context.error_flag = STRING_BUFFER_ERROR_FLAG_OFF; + THROW_EXCEPTION(new_Exception(string(__FILE__), __LINE__, string("json_encode buffer overflow", 27))); return {}; } - kphp_runtime_context.sb_lib_context.error_flag = STRING_BUFFER_ERROR_FLAG_OFF; + kphp_runtime_context.sb_lib_context.error_flag = STRING_BUFFER_ERROR_FLAG_OFF; return kphp_runtime_context.static_SB.str(); } - -template -inline Optional f$vk_json_encode(const T &v) noexcept { - return f$json_encode(v, 0, true); -} - -std::pair json_decode(const string &v, const char *json_obj_magic_key = nullptr) noexcept; -mixed f$json_decode(const string &v, bool assoc = false) noexcept; diff --git a/runtime/memcache.cpp b/runtime/memcache.cpp index 595b5fd1e6..0bcc030bbf 100644 --- a/runtime/memcache.cpp +++ b/runtime/memcache.cpp @@ -5,8 +5,8 @@ #include "runtime/memcache.h" #include "runtime-common/core/utils/kphp-assert-core.h" +#include "runtime-common/stdlib/string/json-functions.h" #include "runtime/array_functions.h" -#include "runtime/json-functions.h" #include "runtime/net_events.h" #include "runtime/serialize-functions.h" #include "runtime/zlib.h" diff --git a/runtime/misc.cpp b/runtime/misc.cpp index ebca95e435..7882740bde 100644 --- a/runtime/misc.cpp +++ b/runtime/misc.cpp @@ -11,12 +11,12 @@ #include #include +#include "runtime-common/stdlib/string/json-functions.h" #include "runtime/critical_section.h" #include "runtime/datetime/datetime_functions.h" #include "runtime/exception.h" #include "runtime/files.h" #include "runtime/interface.h" -#include "runtime/json-functions.h" #include "runtime/math_functions.h" #include "runtime/string_functions.h" #include "server/json-logger.h" diff --git a/runtime/runtime.cmake b/runtime/runtime.cmake index 2e89554fd0..c4e188fc19 100644 --- a/runtime/runtime.cmake +++ b/runtime/runtime.cmake @@ -81,7 +81,6 @@ prepend(KPHP_RUNTIME_SOURCES ${BASE_DIR}/runtime/ exception.cpp exec.cpp files.cpp - from-json-processor.cpp instance-cache.cpp instance-copy-processor.cpp inter-process-mutex.cpp diff --git a/server/php-engine.cpp b/server/php-engine.cpp index c19b6e7fb1..bfe11539b0 100644 --- a/server/php-engine.cpp +++ b/server/php-engine.cpp @@ -59,8 +59,8 @@ #include "net/net-tcp-rpc-client.h" #include "net/net-tcp-rpc-server.h" +#include "runtime-common/stdlib/string/json-functions.h" #include "runtime/interface.h" -#include "runtime/json-functions.h" #include "runtime/kphp_ml/kphp_ml_init.h" #include "runtime/profiler.h" #include "runtime/rpc.h"