* Copyright (c) 2021 Huawei Device Co., Ltd.
* Licensed 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.
*/
#ifndef ECMASCRIPT_BASE_JSON_STRINGIFY_INL_H
#define ECMASCRIPT_BASE_JSON_STRINGIFY_INL_H
#include "ecmascript/js_tagged_value.h"
#include "ecmascript/base/json_helper.h"
#include "ecmascript/js_handle.h"
#include "ecmascript/linked_hash_table.h"
#include "ecmascript/object_factory.h"
#include "ecmascript/global_env.h"
#include "ecmascript/mem/c_containers.h"
namespace panda::ecmascript::base {
class JsonStringifierKeyCache {
public:
static constexpr size_t KEY_CACHE_SIZE = 64;
static constexpr size_t KEY_CACHE_MASK = KEY_CACHE_SIZE - 1;
static constexpr uintptr_t KEY_CACHE_PTR_SHIFT = 4;
JsonStringifierKeyCache()
{
Clear();
}
void Clear()
{
for (size_t i = 0; i < KEY_CACHE_SIZE; ++i) {
keyCache_[i] = JSTaggedValue::Undefined();
}
}
inline size_t GetKeyCacheIndex(const JSTaggedValue &key) const
{
uintptr_t ptr = reinterpret_cast<uintptr_t>(key.GetTaggedObject());
return (ptr >> KEY_CACHE_PTR_SHIFT) & KEY_CACHE_MASK;
}
inline bool TryGetCachedKey(const JSTaggedValue &key) const
{
ASSERT(key != JSTaggedValue::Undefined());
size_t index = GetKeyCacheIndex(key);
JSTaggedValue cached = keyCache_[index];
return cached == key;
}
inline void CacheKey(const JSTaggedValue &key)
{
size_t index = GetKeyCacheIndex(key);
keyCache_[index] = key;
}
private:
JSTaggedValue keyCache_[KEY_CACHE_SIZE];
};
#if ENABLE_LATEST_OPTIMIZATION
class JsonStringifier {
using TransformType = base::JsonHelper::TransformType;
public:
explicit JsonStringifier(JSThread *thread, TransformType transformType = TransformType::NORMAL)
: encoding_(Encoding::ONE_BYTE_ENCODING), oneByteResult_(), twoBytesResult_(),
gap_(), indent_(0), thread_(thread), transformType_(transformType)
{
if (auto existingKeyCache = thread_->GetJsonStringifierKeyCache().lock()) {
keyCache_ = existingKeyCache;
} else {
keyCache_ = std::make_shared<JsonStringifierKeyCache>();
thread_->SetJsonStringifierKeyCache(keyCache_);
}
}
~JsonStringifier()
{
}
NO_COPY_SEMANTIC(JsonStringifier);
NO_MOVE_SEMANTIC(JsonStringifier);
static constexpr size_t INT_MAX_LENGTH = 11;
static constexpr size_t DOUBLE_MAX_LENGTH = 32;
JSHandle<JSTaggedValue> Stringify(const JSHandle<JSTaggedValue> &value, const JSHandle<JSTaggedValue> &replacer,
const JSHandle<JSTaggedValue> &gap);
template <bool ReplacerAndGapUndefined>
JSHandle<JSTaggedValue> StringifyInternal(const JSHandle<JSTaggedValue> &value,
const JSHandle<JSTaggedValue> &gap);
template <typename DestChar>
class FastStringBuilder {
public:
using value_type = DestChar;
FastStringBuilder()
: buffer_(stackBuffer_), length_(0), capacity_(STACK_BUFFER_SIZE), useHeap_(false) {}
~FastStringBuilder()
{
if (useHeap_) {
delete[] buffer_;
buffer_ = nullptr;
}
}
NO_COPY_SEMANTIC(FastStringBuilder);
NO_MOVE_SEMANTIC(FastStringBuilder);
inline void Append(DestChar c)
{
ASSERT(length_ + 1 <= capacity_);
buffer_[length_++] = c;
}
inline void AppendCString(const char* s)
{
while (*s != '\0') {
Append(static_cast<DestChar>(*(s++)));
}
}
template <typename SrcChar>
inline void AppendString(const SrcChar* s, size_t len)
{
ASSERT(length_ + len <= capacity_);
switch (len) {
#define CASE(N) \
case N: \
std::copy_n(s, N, buffer_ + length_); \
break;
CASE(1) CASE(2) CASE(3) CASE(4)
CASE(5) CASE(6) CASE(7) CASE(8)
CASE(9) CASE(10) CASE(11) CASE(12)
CASE(13) CASE(14) CASE(15) CASE(16)
#undef CASE
default:
std::copy_n(s, len, buffer_ + length_);
break;
}
length_ += len;
}
inline void PopBack()
{
if (length_ > 0) {
--length_;
}
}
inline size_t GetLength() const
{
return length_;
}
inline DestChar* GetBuffer()
{
return buffer_;
}
inline size_t GetCapacity() const
{
return capacity_;
}
inline void SetLength(size_t newLength)
{
length_ = newLength;
}
inline void Reverse(size_t start, size_t end)
{
std::reverse(&buffer_[start], &buffer_[end]);
}
ARK_NOINLINE void GrowCapacity(size_t len)
{
size_t newCapacity = capacity_ * GROWTH_FACTOR;
while (newCapacity < length_ + len) {
newCapacity *= GROWTH_FACTOR;
}
DestChar* newBuffer = new DestChar[newCapacity];
std::copy_n(buffer_, length_, newBuffer);
if (useHeap_) {
delete[] buffer_;
buffer_ = nullptr;
}
buffer_ = newBuffer;
capacity_ = newCapacity;
useHeap_ = true;
}
inline void EnsureCapacity(size_t len)
{
if (LIKELY(length_ + len <= capacity_)) {
return;
}
GrowCapacity(len);
}
private:
static constexpr size_t STACK_BUFFER_SIZE = 2048;
static constexpr size_t GROWTH_FACTOR = 2;
DestChar* buffer_;
size_t length_;
size_t capacity_;
bool useHeap_;
DestChar stackBuffer_[STACK_BUFFER_SIZE];
};
template <typename DestChar>
static void AppendIntToFastStringBuilder(FastStringBuilder<DestChar> &str, int number);
template <typename DestChar>
static void AppendDoubleToFastStringBuilder(FastStringBuilder<DestChar> &str, double d);
template <typename DestChar>
static bool AppendSpecialDoubleToFastStringBuilder(FastStringBuilder<DestChar> &str, double d);
template <typename DestChar>
static bool AppendQuotedStringToFastStringBuilder(const JSThread *thread,
FastStringBuilder<DestChar> &output, const EcmaString *str);
template <typename DestType>
static void AppendBigIntToString(DestType &str, BigInt *bigint);
inline void AppendNumberToResult(const JSHandle<JSTaggedValue> &value);
inline FastStringBuilder<uint8_t> &GetOneByteResult()
{
return oneByteResult_;
}
inline const FastStringBuilder<uint8_t> &GetOneByteResult() const
{
return oneByteResult_;
}
private:
void AddDeduplicateProp(const JSHandle<JSTaggedValue> &property);
template <bool ReplacerAndGapUndefined>
JSTaggedValue SerializeJSONProperty(const JSHandle<JSTaggedValue> &value);
template <bool ReplacerAndGapUndefined>
JSTaggedValue GetSerializeValue(const JSHandle<JSTaggedValue> &object, const JSHandle<JSTaggedValue> &key,
const JSHandle<JSTaggedValue> &value);
void SerializeObjectKey(const JSHandle<JSTaggedValue> &key, bool hasContent);
template <bool ReplacerAndGapUndefined>
bool SerializeJSONObject(const JSHandle<JSTaggedValue> &value);
template <bool ReplacerAndGapUndefined>
bool SerializeObjectProperties(const JSHandle<JSObject> &obj, bool hasContent);
template <bool ReplacerAndGapUndefined>
bool SerializeObjectWithReplacerArray(const JSHandle<JSObject> &obj, bool hasContent);
template <bool ReplacerAndGapUndefined>
bool ProcessSerializedProperty(const JSHandle<JSTaggedValue> &object, const JSHandle<JSTaggedValue> &key,
const JSHandle<JSTaggedValue> &value, bool hasContent);
template <bool ReplacerAndGapUndefined>
bool SerializeJSONSharedMap(const JSHandle<JSTaggedValue> &value);
template <bool ReplacerAndGapUndefined>
bool SerializeJSONSharedSet(const JSHandle<JSTaggedValue> &value);
template <bool ReplacerAndGapUndefined>
bool SerializeJSONMap(const JSHandle<JSTaggedValue> &value);
template <bool ReplacerAndGapUndefined>
bool SerializeJSONSet(const JSHandle<JSTaggedValue> &value);
template <bool ReplacerAndGapUndefined>
bool SerializeJSONHashMap(const JSHandle<JSTaggedValue> &value);
template <bool ReplacerAndGapUndefined>
bool SerializeJSONHashSet(const JSHandle<JSTaggedValue> &value);
template <bool ReplacerAndGapUndefined>
bool SerializeLinkedHashMap(const JSHandle<LinkedHashMap> &hashMap);
template <bool ReplacerAndGapUndefined>
bool SerializeLinkedHashSet(const JSHandle<LinkedHashSet> &hashSet);
template <bool ReplacerAndGapUndefined>
bool SerializeJSArray(const JSHandle<JSTaggedValue> &value);
template <bool ReplacerAndGapUndefined>
bool SerializeFastArrayPrimitive(JSTaggedValue tagVal);
template <bool ReplacerAndGapUndefined>
bool SerializeJSProxy(const JSHandle<JSTaggedValue> &object);
template <bool ReplacerAndGapUndefined>
void SerializePrimitiveRef(const JSHandle<JSTaggedValue> &primitiveRef);
bool PushValue(const JSHandle<JSTaggedValue> &value);
void PopValue();
bool CalculateNumberGap(JSTaggedValue gap);
bool CalculateStringGap(const JSHandle<EcmaString> &primString);
template <bool ReplacerAndGapUndefined>
bool AppendJsonString(const JSHandle<JSObject> &obj, bool hasContent);
template <bool ReplacerAndGapUndefined>
bool SerializeElements(const JSHandle<JSObject> &obj, bool hasContent);
template <bool ReplacerAndGapUndefined>
bool SerializeKeys(const JSHandle<JSObject> &obj, bool hasContent);
bool ShouldSerializeKey(bool hasSymbol, JSTaggedValue key);
JSHandle<JSTaggedValue> SerializeHolder(const JSHandle<JSTaggedValue> &object,
const JSHandle<JSTaggedValue> &value);
bool CheckStackPushSameValue(JSHandle<JSTaggedValue> value);
bool HasOwnToJsonProperty(const JSHandle<JSObject> &obj) const;
bool MayHaveInterestingProperties(const JSHandle<JSTaggedValue> &value);
bool CheckProtoChainForInterestingProperties(const JSHandle<JSHClass> &receiverHClass, JSTaggedValue current);
inline void Indent()
{
indent_++;
}
inline void Unindent()
{
indent_--;
}
inline void NewLine();
void ChangeEncoding();
template <size_t N>
inline void AppendLiteral(const char(&src)[N]);
inline void AppendChar(const char src);
inline void AppendIntToResult(int32_t key);
inline void EnsureCapacityFor(size_t size);
inline bool AppendEcmaStringToResult(JSHandle<EcmaString> &string);
inline void AppendBigIntToResult(JSHandle<JSTaggedValue> &valHandle);
inline void PopBack();
template <typename BuilderType>
inline void AppendFastPathKeyString(const EcmaString* strPtr, BuilderType &output);
inline JsonStringifierKeyCache* GetKeyCache() const
{
ASSERT(thread_->GetJsonStringifierKeyCache().lock() == keyCache_);
ASSERT(keyCache_ != nullptr);
return keyCache_.get();
}
template <typename SrcChar>
inline void AppendStringInternal(const SrcChar* s, size_t len)
{
if (encoding_ == Encoding::ONE_BYTE_ENCODING) {
oneByteResult_.AppendString(s, len);
} else {
twoBytesResult_.AppendString(s, len);
}
}
enum class Encoding {
ONE_BYTE_ENCODING,
TWO_BYTE_ENCODING
};
Encoding encoding_;
FastStringBuilder<uint8_t> oneByteResult_;
FastStringBuilder<uint16_t> twoBytesResult_;
C16String gap_;
int indent_;
std::shared_ptr<JsonStringifierKeyCache> keyCache_;
JSThread *thread_ {nullptr};
ObjectFactory *factory_ {nullptr};
CVector<JSHandle<JSTaggedValue>> stack_;
CVector<JSHandle<JSTaggedValue>> propList_;
JSMutableHandle<JSTaggedValue> handleKey_ {};
JSMutableHandle<JSTaggedValue> handleValue_ {};
JSHandle<JSTaggedValue> replacer_ {};
TransformType transformType_ {};
};
#else
class JsonStringifier {
using TransformType = base::JsonHelper::TransformType;
public:
JsonStringifier() = default;
explicit JsonStringifier(JSThread *thread, TransformType transformType = TransformType::NORMAL)
: thread_(thread), transformType_(transformType)
{
}
~JsonStringifier() = default;
NO_COPY_SEMANTIC(JsonStringifier);
NO_MOVE_SEMANTIC(JsonStringifier);
JSHandle<JSTaggedValue> Stringify(const JSHandle<JSTaggedValue> &value, const JSHandle<JSTaggedValue> &replacer,
const JSHandle<JSTaggedValue> &gap);
private:
void AddDeduplicateProp(const JSHandle<JSTaggedValue> &property);
JSTaggedValue SerializeJSONProperty(const JSHandle<JSTaggedValue> &value,
const JSHandle<JSTaggedValue> &replacer);
JSTaggedValue GetSerializeValue(const JSHandle<JSTaggedValue> &object, const JSHandle<JSTaggedValue> &key,
const JSHandle<JSTaggedValue> &value, const JSHandle<JSTaggedValue> &replacer);
void SerializeObjectKey(const JSHandle<JSTaggedValue> &key, bool hasContent);
bool SerializeJSONObject(const JSHandle<JSTaggedValue> &value, const JSHandle<JSTaggedValue> &replacer);
bool SerializeJSONSharedMap(const JSHandle<JSTaggedValue> &value, const JSHandle<JSTaggedValue> &replacer);
bool SerializeJSONSharedSet(const JSHandle<JSTaggedValue> &value, const JSHandle<JSTaggedValue> &replacer);
bool SerializeJSONMap(const JSHandle<JSTaggedValue> &value, const JSHandle<JSTaggedValue> &replacer);
bool SerializeJSONSet(const JSHandle<JSTaggedValue> &value, const JSHandle<JSTaggedValue> &replacer);
bool SerializeJSONHashMap(const JSHandle<JSTaggedValue> &value, const JSHandle<JSTaggedValue> &replacer);
bool SerializeJSONHashSet(const JSHandle<JSTaggedValue> &value, const JSHandle<JSTaggedValue> &replacer);
bool SerializeLinkedHashMap(const JSHandle<LinkedHashMap> &hashMap, const JSHandle<JSTaggedValue> &replacer);
bool SerializeLinkedHashSet(const JSHandle<LinkedHashSet> &hashSet, const JSHandle<JSTaggedValue> &replacer);
bool SerializeJSArray(const JSHandle<JSTaggedValue> &value, const JSHandle<JSTaggedValue> &replacer);
bool SerializeJSProxy(const JSHandle<JSTaggedValue> &object, const JSHandle<JSTaggedValue> &replacer);
void SerializePrimitiveRef(const JSHandle<JSTaggedValue> &primitiveRef, const JSHandle<JSTaggedValue> &replacer);
bool PushValue(const JSHandle<JSTaggedValue> &value);
void PopValue();
bool CalculateNumberGap(JSTaggedValue gap);
bool CalculateStringGap(const JSHandle<EcmaString> &primString);
bool AppendJsonString(const JSHandle<JSObject> &obj, const JSHandle<JSTaggedValue> &replacer, bool hasContent);
bool SerializeElements(const JSHandle<JSObject> &obj, const JSHandle<JSTaggedValue> &replacer, bool hasContent);
bool SerializeKeys(const JSHandle<JSObject> &obj, const JSHandle<JSTaggedValue> &replacer, bool hasContent);
JSHandle<JSTaggedValue> SerializeHolder(const JSHandle<JSTaggedValue> &object,
const JSHandle<JSTaggedValue> &value);
bool CheckStackPushSameValue(JSHandle<JSTaggedValue> value);
CString gap_;
CString result_;
CString indent_;
JSThread *thread_ {nullptr};
ObjectFactory *factory_ {nullptr};
CVector<JSHandle<JSTaggedValue>> stack_;
CVector<JSHandle<JSTaggedValue>> propList_;
JSMutableHandle<JSTaggedValue> handleKey_ {};
JSMutableHandle<JSTaggedValue> handleValue_ {};
TransformType transformType_ {};
};
#endif
}
#endif