Files
ark_js_runtime/ecmascript/base/json_parser.h
T
xujie c7ed414099 optimize json parse
Signed-off-by: xujie <xujie101@huawei.com>
2021-09-24 10:36:25 +08:00

768 lines
24 KiB
C++

/*
* 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_PARSE_INL_H
#define ECMASCRIPT_BASE_JSON_PARSE_INL_H
#include "ecmascript/base/json_parser.h"
#include "ecmascript/base/builtins_base.h"
#include "ecmascript/base/number_helper.h"
#include "ecmascript/base/string_helper.h"
#include "ecmascript/base/utf_helper.h"
#include "ecmascript/ecma_string-inl.h"
#include "ecmascript/ecma_string.h"
#include "ecmascript/internal_call_params.h"
#include "ecmascript/interpreter/fast_runtime_stub-inl.h"
#include "ecmascript/js_array.h"
#include "ecmascript/js_function.h"
#include "ecmascript/js_handle.h"
#include "ecmascript/js_tagged_value.h"
#include "ecmascript/object_factory.h"
namespace panda::ecmascript::base {
constexpr unsigned int UNICODE_DIGIT_LENGTH = 4;
constexpr unsigned int NUMBER_TEN = 10;
constexpr unsigned int NUMBER_SIXTEEN = 16;
constexpr unsigned char CODE_SPACE = 0x20;
constexpr unsigned char ASCII_END = 0X7F;
enum class Tokens : uint8_t {
// six structural tokens
OBJECT = 0,
ARRAY,
NUMBER,
STRING,
LITERAL_TRUE,
LITERAL_FALSE,
LITERAL_NULL,
TOKEN_ILLEGAL,
};
template<typename T>
class JsonParser {
public:
using Text = const T *;
explicit JsonParser() = default;
explicit JsonParser(JSThread *thread) : thread_(thread) {}
~JsonParser() = default;
NO_COPY_SEMANTIC(JsonParser);
NO_MOVE_SEMANTIC(JsonParser);
JSHandle<JSTaggedValue> Parse(Text begin, Text end)
{
end_ = end - 1;
current_ = begin;
auto vm = thread_->GetEcmaVM();
factory_ = vm->GetFactory();
env_ = *vm->GetGlobalEnv();
SkipEndWhiteSpace();
range_ = end_;
JSTaggedValue result = ParseJSONText<false>();
return JSHandle<JSTaggedValue>(thread_, result);
}
JSHandle<JSTaggedValue> ParseUtf8(EcmaString *str)
{
ASSERT(str != nullptr);
isAsciiString_ = true;
uint32_t len = str->GetUtf8Length();
CVector<T> buf(len);
str->CopyDataUtf8(buf.data(), len);
Text begin = buf.data();
return Parse(begin, begin + str->GetLength());
}
JSHandle<JSTaggedValue> ParseUtf16(EcmaString *str)
{
ASSERT(str != nullptr);
uint32_t len = str->GetLength();
CVector<T> buf(len);
str->CopyDataUtf16(buf.data(), len);
Text begin = buf.data();
return Parse(begin, begin + len);
}
private:
template<bool inObjorArr = false>
JSTaggedValue ParseJSONText()
{
SkipStartWhiteSpace();
Tokens token = ParseToken();
switch (token) {
case Tokens::OBJECT:
return ParseObject<inObjorArr>();
case Tokens::ARRAY:
return ParseArray<inObjorArr>();
case Tokens::LITERAL_TRUE:
return ParseLiteral("true", Tokens::LITERAL_TRUE);
case Tokens::LITERAL_FALSE:
return ParseLiteral("false", Tokens::LITERAL_FALSE);
case Tokens::LITERAL_NULL:
return ParseLiteral("null", Tokens::LITERAL_NULL);
case Tokens::NUMBER:
return ParseNumber<inObjorArr>();
case Tokens::STRING:
return ParseString<inObjorArr>();
default:
THROW_SYNTAX_ERROR_AND_RETURN(thread_, "Unexpected Text in JSON", JSTaggedValue::Exception());
}
}
template<bool inObjOrArr = false>
JSTaggedValue ParseNumber()
{
if (inObjOrArr) {
bool isFast = true;
bool isNumber = ReadNumberRange(isFast);
if (!isNumber) {
THROW_SYNTAX_ERROR_AND_RETURN(thread_, "Unexpected Number in JSON", JSTaggedValue::Exception());
}
if (isFast) {
std::string strNum(current_, end_ + 1);
current_ = end_;
return JSTaggedValue(std::stod(strNum));
}
}
Text current = current_;
bool hasExponent = false;
if (*current_ == '-') {
if (UNLIKELY(current_++ == end_)) {
THROW_SYNTAX_ERROR_AND_RETURN(thread_, "Unexpected Number in JSON", JSTaggedValue::Exception());
}
}
if (*current_ == '0') {
if (!CheckZeroBeginNumber(hasExponent)) {
THROW_SYNTAX_ERROR_AND_RETURN(thread_, "Unexpected Number in JSON", JSTaggedValue::Exception());
}
} else if (*current_ >= '1' && *current_ <= '9') {
if (!CheckNonZeroBeginNumber(hasExponent)) {
THROW_SYNTAX_ERROR_AND_RETURN(thread_, "Unexpected Number in JSON", JSTaggedValue::Exception());
}
} else {
THROW_SYNTAX_ERROR_AND_RETURN(thread_, "Unexpected Number in JSON", JSTaggedValue::Exception());
}
std::string strNum(current, end_ + 1);
current_ = end_;
return JSTaggedValue(std::stod(strNum));
}
bool ReadJsonStringRange(bool &isFastString, bool &isAscii)
{
current_++;
if (isAsciiString_) {
return ReadAsciiStringRange(isFastString);
}
return ReadStringRange(isFastString, isAscii);
}
bool IsFastParseJsonString(bool &isFastString, bool &isAscii)
{
current_++;
if (isAsciiString_) {
return IsFastParseString(isFastString, isAscii);
}
return IsFastParseAsciiString(isFastString);
}
bool ParseBackslash(CString &res)
{
if (current_ == end_) {
return false;
}
current_++;
switch (*current_) {
case '\"':
res += "\"";
break;
case '\\':
res += "\\";
break;
case '/':
res += "/";
break;
case 'b':
res += "\b";
break;
case 'f':
res += "\f";
break;
case 'n':
res += "\n";
break;
case 'r':
res += "\r";
break;
case 't':
res += "\t";
break;
case 'u': {
CVector<uint16_t> vec;
if (UNLIKELY(!ConvertStringUnicode(vec))) {
return false;
}
std::u16string u16Str;
u16Str.assign(vec.begin(), vec.end());
res += base::StringHelper::U16stringToString(u16Str);
break;
}
default:
return false;
}
return true;
}
JSTaggedValue SlowParseString()
{
end_--;
CString res;
while (current_ <= end_) {
if (*current_ == '\\') {
bool isLegalChar = ParseBackslash(res);
if (!isLegalChar) {
THROW_SYNTAX_ERROR_AND_RETURN(thread_, "Unexpected string in JSON", JSTaggedValue::Exception());
}
} else {
res += *current_;
}
current_++;
}
return factory_->NewFromUtf8Literal(reinterpret_cast<const uint8_t *>(res.c_str()), res.length())
.GetTaggedValue();
}
template<bool inObjorArr = false>
JSTaggedValue ParseString()
{
bool isFastString = true;
bool isAscii = true;
bool isLegal = true;
if (inObjorArr) {
isLegal = ReadJsonStringRange(isFastString, isAscii);
if (!isLegal) {
THROW_SYNTAX_ERROR_AND_RETURN(thread_, "Unexpected end Text in JSON", JSTaggedValue::Exception());
}
if (isFastString) {
if (isAscii) {
CString value(current_, end_);
current_ = end_;
return factory_->NewFromUtf8LiteralUnCheck(
reinterpret_cast<const uint8_t *>(value.c_str()), value.length(), true).GetTaggedValue();
}
std::u16string value(current_, end_);
current_ = end_;
return factory_->NewFromUtf16LiteralUnCheck(
reinterpret_cast<const uint16_t *>(value.c_str()), value.length(), false).GetTaggedValue();
}
} else {
if (*end_ != '"' || current_ == end_) {
THROW_SYNTAX_ERROR_AND_RETURN(thread_, "Unexpected end Text in JSON", JSTaggedValue::Exception());
}
isLegal = IsFastParseJsonString(isFastString, isAscii);
if (!isLegal) {
THROW_SYNTAX_ERROR_AND_RETURN(thread_, "Unexpected end Text in JSON", JSTaggedValue::Exception());
}
if (LIKELY(isFastString)) {
if (isAscii) {
CString value(current_, end_);
return factory_->NewFromUtf8LiteralUnCheck(
reinterpret_cast<const uint8_t *>(value.c_str()), value.length(), true).GetTaggedValue();
}
std::u16string value(current_, end_);
return factory_->NewFromUtf16LiteralUnCheck(
reinterpret_cast<const uint16_t *>(value.c_str()), value.length(), false).GetTaggedValue();
}
}
return SlowParseString();
}
template<bool inObjorArr = false>
JSTaggedValue ParseArray()
{
if (UNLIKELY(*range_ != ']' && !inObjorArr)) {
THROW_SYNTAX_ERROR_AND_RETURN(thread_, "Unexpected Array in JSON", JSTaggedValue::Exception());
}
current_++;
JSHandle<JSArray> arr = factory_->NewJSArray();
if (*current_ == ']') {
return arr.GetTaggedValue();
}
JSTaggedValue value;
uint32_t index = 0;
while (current_ <= range_) {
value = ParseJSONText<true>();
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
FastRuntimeStub::SetPropertyByIndex<true>(thread_, arr.GetTaggedValue(), index++, value);
GetNextNonSpaceChar();
if (*current_ == ',') {
current_++;
} else if (*current_ == ']') {
if (inObjorArr || current_ == range_) {
return arr.GetTaggedValue();
} else {
THROW_SYNTAX_ERROR_AND_RETURN(thread_, "Unexpected Array in JSON", JSTaggedValue::Exception());
}
}
}
THROW_SYNTAX_ERROR_AND_RETURN(thread_, "Unexpected Array in JSON", JSTaggedValue::Exception());
}
template<bool inObjorArr = false>
JSTaggedValue ParseObject()
{
if (UNLIKELY(*range_ != '}' && !inObjorArr)) {
THROW_SYNTAX_ERROR_AND_RETURN(thread_, "Unexpected Object in JSON", JSTaggedValue::Exception());
}
JSHandle<JSTaggedValue> proto = env_->GetObjectFunction();
JSHandle<JSObject> result = factory_->NewJSObjectByConstructor(JSHandle<JSFunction>(proto), proto);
current_++;
if (*current_ == '}') {
return result.GetTaggedValue();
}
JSMutableHandle<JSTaggedValue> keyHandle(thread_, JSTaggedValue::Undefined());
JSTaggedValue value;
while (current_ <= range_) {
SkipStartWhiteSpace();
if (*current_ == '"') {
keyHandle.Update(ParseString<true>());
} else {
if (*current_ == '}' && (inObjorArr || current_ == range_)) {
return result.GetTaggedValue();
}
THROW_SYNTAX_ERROR_AND_RETURN(thread_, "Unexpected Object in JSON", JSTaggedValue::Exception());
}
GetNextNonSpaceChar();
if (*current_ == ':') {
current_++;
} else {
THROW_SYNTAX_ERROR_AND_RETURN(thread_, "Unexpected Object in JSON", JSTaggedValue::Exception());
}
value = ParseJSONText<true>();
FastRuntimeStub::SetPropertyByValue<true>(
thread_, result.GetTaggedValue(), keyHandle.GetTaggedValue(), value);
GetNextNonSpaceChar();
if (*current_ == ',') {
current_++;
} else if (*current_ == '}') {
if (inObjorArr || current_ == range_) {
return result.GetTaggedValue();
} else {
THROW_SYNTAX_ERROR_AND_RETURN(thread_, "Unexpected Object in JSON", JSTaggedValue::Exception());
}
}
}
THROW_SYNTAX_ERROR_AND_RETURN(thread_, "Unexpected Object in JSON", JSTaggedValue::Exception());
}
void SkipEndWhiteSpace()
{
while (current_ != end_) {
if (*end_ == ' ' || *end_ == '\r' || *end_ == '\n' || *end_ == '\t') {
end_--;
} else {
break;
}
}
}
void SkipStartWhiteSpace()
{
while (current_ != end_) {
if (*current_ == ' ' || *current_ == '\r' || *current_ == '\n' || *current_ == '\t') {
current_++;
} else {
break;
}
}
}
void GetNextNonSpaceChar()
{
current_++;
SkipStartWhiteSpace();
}
Tokens ParseToken()
{
switch (*current_) {
case '{':
return Tokens::OBJECT;
case '[':
return Tokens::ARRAY;
case '"':
return Tokens::STRING;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '-':
return Tokens::NUMBER;
case 't':
return Tokens::LITERAL_TRUE;
case 'f':
return Tokens::LITERAL_FALSE;
case 'n':
return Tokens::LITERAL_NULL;
default:
return Tokens::TOKEN_ILLEGAL;
}
}
JSTaggedValue ParseLiteral(CString str, Tokens literalToken)
{
uint32_t strLen = str.size() - 1;
uint32_t remainingLength = range_ - current_;
if (UNLIKELY(remainingLength < strLen)) {
THROW_SYNTAX_ERROR_AND_RETURN(thread_, "Unexpected Text in JSON", JSTaggedValue::Exception());
}
bool isMatch = MatchText(str, strLen);
if (LIKELY(isMatch)) {
switch (literalToken) {
case Tokens::LITERAL_TRUE:
return JSTaggedValue::True();
case Tokens::LITERAL_FALSE:
return JSTaggedValue::False();
case Tokens::LITERAL_NULL:
return JSTaggedValue::Null();
default:
UNREACHABLE();
}
}
THROW_SYNTAX_ERROR_AND_RETURN(thread_, "Unexpected Text in JSON", JSTaggedValue::Exception());
}
bool MatchText(CString str, uint32_t matchLen)
{
const char *text = str.c_str();
uint32_t pos = 1;
while (pos <= matchLen) {
if (current_[pos] != text[pos]) {
return false;
}
pos++;
}
current_ += matchLen;
return true;
}
bool ReadNumberRange(bool &isFast)
{
Text current = current_;
if (*current == '0') {
isFast = false;
current++;
} else if (*current == '-') {
current++;
if (*current == '0') {
isFast = false;
current++;
}
}
while (current != range_) {
if (IsNumberCharacter(*current)) {
current++;
continue;
} else if (IsNumberSignalCharacter(*current)) {
isFast = false;
current++;
continue;
}
Text end = current;
while (current != range_) {
if (*current == ' ' || *current == '\r' || *current == '\n' || *current == '\t') {
current++;
} else if (*current == ',' || *current == ']' || *current == '}') {
end_ = end - 1;
return true;
} else {
return false;
}
}
return false;
}
end_ = range_ - 1;
return true;
}
bool IsNumberCharacter(T ch)
{
if (ch >= '0' && ch <= '9') {
return true;
}
return false;
}
bool IsNumberSignalCharacter(T ch)
{
return ch == '.' || ch == 'e' || ch == 'E' || ch == '+' || ch == '-';
}
bool IsExponentNumber()
{
if (IsNumberCharacter(*current_)) {
return true;
} else if (*current_ == '-' || *current_ == '+') {
if (current_ == end_) {
return false;
}
current_++;
if (IsNumberCharacter(*current_)) {
return true;
}
}
return false;
}
bool IsDecimalsLegal(bool &hasExponent)
{
if (current_ == end_ && !IsNumberCharacter(*++current_)) {
return false;
}
while (current_ != end_) {
current_++;
if (IsNumberCharacter(*current_)) {
continue;
} else if (*current_ == 'e' || *current_ == 'E') {
if (hasExponent || current_ == end_) {
return false;
}
current_++;
if (!IsExponentNumber()) {
return false;
}
hasExponent = true;
} else {
return false;
}
}
return true;
}
bool IsExponentLegal(bool &hasExponent)
{
if (hasExponent || current_ == end_) {
return false;
}
current_++;
if (!IsExponentNumber()) {
return false;
}
while (current_ != end_) {
if (!IsNumberCharacter(*current_)) {
return false;
}
current_++;
}
return true;
}
bool ReadStringRange(bool &isFast, bool &isAscii)
{
T c = 0;
Text current = current_;
while (current != range_) {
c = *current;
if (c == '"') {
end_ = current;
return true;
} else if (UNLIKELY(c == '\\')) {
if (*(current + 1) == '"') {
current++;
}
isFast = false;
}
if (!IsLegalAsciiCharacter(c, isAscii)) {
return false;
}
current++;
}
return false;
}
bool ReadAsciiStringRange(bool &isFast)
{
T c = 0;
Text current = current_;
while (current != range_) {
c = *current;
if (c == '"') {
end_ = current;
return true;
} else if (UNLIKELY(c == '\\')) {
if (*(current + 1) == '"') {
current++;
}
isFast = false;
} else if (UNLIKELY(c < CODE_SPACE)) {
return false;
}
current++;
}
return false;
}
bool IsFastParseString(bool &isFast, bool &isAscii)
{
Text current = current_;
while (current != end_) {
if (!IsLegalAsciiCharacter(*current, isAscii)) {
return false;
}
if (*current == '\\') {
isFast = false;
}
current++;
}
return true;
}
bool IsFastParseAsciiString(bool &isFast)
{
Text current = current_;
while (current != end_) {
if (*current < CODE_SPACE) {
return false;
} else if (*current == '\\') {
isFast = false;
}
current++;
}
return true;
}
bool ConvertStringUnicode(CVector<uint16_t> &vec)
{
uint32_t remainingLength = end_ - current_;
if (remainingLength < UNICODE_DIGIT_LENGTH) {
return false;
}
uint16_t res = 0;
uint32_t exponent = UNICODE_DIGIT_LENGTH;
for (uint32_t pos = 0; pos < UNICODE_DIGIT_LENGTH; pos++) {
current_++;
exponent--;
if (*current_ >= '0' && *current_ <= '9') {
res += (*current_ - '0') * pow(NUMBER_SIXTEEN, exponent);
} else if (*current_ >= 'a' && *current_ <= 'f') {
res += (*current_ - 'a' + NUMBER_TEN) * pow(NUMBER_SIXTEEN, exponent);
} else if (*current_ >= 'A' && *current_ <= 'F') {
res += (*current_ - 'A' + NUMBER_TEN) * pow(NUMBER_SIXTEEN, exponent);
} else {
return false;
}
}
if (res < CODE_SPACE) {
return false;
}
vec.emplace_back(res);
if (*(current_ + 1) == '\\' && *(current_ + 2) == 'u') { // 2: next two chars
current_ += 2; // 2: point moves backwards by two digits
return ConvertStringUnicode(vec);
}
return true;
}
bool CheckZeroBeginNumber(bool &hasExponent)
{
if (current_++ != end_) {
if (*current_ == '.') {
if (!IsDecimalsLegal(hasExponent)) {
return false;
}
} else if (*current_ == 'e' || *current_ == 'E') {
if (!IsExponentLegal(hasExponent)) {
return false;
}
} else {
return false;
}
}
return true;
}
bool CheckNonZeroBeginNumber(bool &hasExponent)
{
while (current_ != end_) {
current_++;
if (IsNumberCharacter(*current_)) {
continue;
} else if (*current_ == '.') {
if (!IsDecimalsLegal(hasExponent)) {
return false;
}
} else if (*current_ == 'e' || *current_ == 'E') {
if (!IsExponentLegal(hasExponent)) {
return false;
}
} else {
return false;
}
}
return true;
}
bool IsLegalAsciiCharacter(T c, bool &isAscii)
{
if (c <= ASCII_END) {
if (c >= CODE_SPACE) {
return true;
}
return false;
}
isAscii = false;
return true;
}
bool isAsciiString_{false};
Text end_{nullptr};
Text current_{nullptr};
Text range_{nullptr};
JSThread *thread_{nullptr};
ObjectFactory *factory_{nullptr};
GlobalEnv *env_{nullptr};
};
class Internalize {
public:
static JSHandle<JSTaggedValue> InternalizeJsonProperty(JSThread *thread, const JSHandle<JSObject> &holder,
const JSHandle<JSTaggedValue> &name,
const JSHandle<JSTaggedValue> &receiver);
private:
static bool RecurseAndApply(JSThread *thread, const JSHandle<JSObject> &holder, const JSHandle<JSTaggedValue> &name,
const JSHandle<JSTaggedValue> &receiver);
};
} // namespace panda::ecmascript::base
#endif // ECMASCRIPT_BASE_JSON_PARSE_INL_H