blob: aaa8048d593cbb17372146bdb3a597794e36d52c [file] [edit]
/*
* Copyright (C) 2010 Google Inc. All rights reserved.
* Copyright (C) 2014 University of Washington. All rights reserved.
* Copyright (C) 2017-2023 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include <wtf/JSONValues.h>
#include <functional>
#include <wtf/ASCIICType.h>
#include <wtf/CommaPrinter.h>
#include <wtf/MathExtras.h>
#include <wtf/ZippedRange.h>
#include <wtf/text/MakeString.h>
#include <wtf/text/ParsingUtilities.h>
#include <wtf/text/StringBuilder.h>
namespace WTF {
namespace JSONImpl {
namespace {
static constexpr int stackLimit = 1000;
enum class Token {
ObjectBegin,
ObjectEnd,
ArrayBegin,
ArrayEnd,
String,
Number,
BoolTrue,
BoolFalse,
Null,
ListSeparator,
ObjectPairSeparator,
Invalid,
};
constexpr auto nullToken = "null"_s;
constexpr auto trueToken = "true"_s;
constexpr auto falseToken = "false"_s;
template<typename CodeUnit>
bool NODELETE parseConstToken(std::span<const CodeUnit> data, std::span<const CodeUnit>& tokenEnd, ASCIILiteral token)
{
if (data.size() < token.length())
return false;
for (auto [character, tokenCharacter] : zippedRange(data, token.span8())) {
if (character != tokenCharacter)
return false;
}
tokenEnd = data.subspan(token.length());
return true;
}
template<typename CodeUnit>
bool NODELETE readInt(std::span<const CodeUnit> data, std::span<const CodeUnit>& tokenEnd, bool canHaveLeadingZeros)
{
if (data.empty())
return false;
bool hasLeadingZero = data[0] == '0';
size_t originalSize = data.size();
skipWhile<isASCIIDigit>(data);
size_t length = originalSize - data.size();
if (!length)
return false;
if (!canHaveLeadingZeros && length > 1 && hasLeadingZero)
return false;
tokenEnd = data;
return true;
}
template<typename CodeUnit>
bool NODELETE parseNumberToken(std::span<const CodeUnit> data, std::span<const CodeUnit>& tokenEnd)
{
// We just grab the number here. We validate the size in DecodeNumber.
// According to RFC 4627, a valid number is: [minus] int [frac] [exp]
if (data.empty())
return false;
skipExactly(data, '-');
if (!readInt(data, data, false))
return false;
if (data.empty()) {
tokenEnd = data;
return true;
}
// Optional fraction part.
if (skipExactly(data, '.')) {
if (!readInt(data, data, true))
return false;
if (data.empty()) {
tokenEnd = data;
return true;
}
}
// Optional exponent part.
if (skipExactly(data, 'e') || skipExactly(data, 'E')) {
if (data.empty())
return false;
if (skipExactly(data, '-') || skipExactly(data, '+')) {
if (data.empty())
return false;
}
if (!readInt(data, data, true))
return false;
}
tokenEnd = data;
return true;
}
template<typename CodeUnit>
bool NODELETE readHexDigits(std::span<const CodeUnit> data, std::span<const CodeUnit>& tokenEnd, unsigned digits)
{
if (data.size() < digits)
return false;
for (unsigned i = 0; i < digits; ++i) {
if (!isASCIIHexDigit(data.front()))
return false;
skip(data, 1);
}
tokenEnd = data;
return true;
}
template<typename CodeUnit>
bool NODELETE parseStringToken(std::span<const CodeUnit> data, std::span<const CodeUnit>& tokenEnd)
{
while (!data.empty()) {
CodeUnit c = consume(data);
if ('\\' == c && !data.empty()) {
c = consume(data);
// Make sure the escaped char is valid.
switch (c) {
case 'x':
if (!readHexDigits(data, data, 2))
return false;
break;
case 'u':
if (!readHexDigits(data, data, 4))
return false;
break;
case '\\':
case '/':
case 'b':
case 'f':
case 'n':
case 'r':
case 't':
case 'v':
case '"':
break;
default:
return false;
}
} else if ('"' == c) {
tokenEnd = data;
return true;
}
}
return false;
}
template<typename CodeUnit>
Token NODELETE parseToken(std::span<const CodeUnit> data, std::span<const CodeUnit>& tokenStart, std::span<const CodeUnit>& tokenEnd)
{
skipWhile<isASCIIWhitespaceWithoutFF>(data);
if (data.empty())
return Token::Invalid;
tokenStart = data;
switch (data.front()) {
case 'n':
if (parseConstToken(data, tokenEnd, nullToken))
return Token::Null;
break;
case 't':
if (parseConstToken(data, tokenEnd, trueToken))
return Token::BoolTrue;
break;
case 'f':
if (parseConstToken(data, tokenEnd, falseToken))
return Token::BoolFalse;
break;
case '[':
tokenEnd = data.subspan(1);
return Token::ArrayBegin;
case ']':
tokenEnd = data.subspan(1);
return Token::ArrayEnd;
case ',':
tokenEnd = data.subspan(1);
return Token::ListSeparator;
case '{':
tokenEnd = data.subspan(1);
return Token::ObjectBegin;
case '}':
tokenEnd = data.subspan(1);
return Token::ObjectEnd;
case ':':
tokenEnd = data.subspan(1);
return Token::ObjectPairSeparator;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '-':
if (parseNumberToken(data, tokenEnd))
return Token::Number;
break;
case '"':
if (parseStringToken(data.subspan(1), tokenEnd))
return Token::String;
break;
}
return Token::Invalid;
}
template<typename CodeUnit>
bool decodeString(std::span<const CodeUnit> data, StringBuilder& output)
{
while (!data.empty()) {
char16_t c = consume(data);
if ('\\' != c) {
output.append(c);
continue;
}
if (data.empty()) [[unlikely]]
return false;
c = consume(data);
switch (c) {
case '"':
case '/':
case '\\':
break;
case 'b':
c = '\b';
break;
case 'f':
c = '\f';
break;
case 'n':
c = '\n';
break;
case 'r':
c = '\r';
break;
case 't':
c = '\t';
break;
case 'v':
c = '\v';
break;
case 'x':
if (data.size() < 2) [[unlikely]]
return false;
c = toASCIIHexValue(data[0], data[1]);
skip(data, 2);
break;
case 'u':
if (data.size() < 4) [[unlikely]]
return false;
c = toASCIIHexValue(data[0], data[1]) << 8 | toASCIIHexValue(data[2], data[3]);
skip(data, 4);
break;
default:
return false;
}
output.append(c);
}
return true;
}
template<typename CodeUnit>
bool decodeString(std::span<const CodeUnit> data, String& output)
{
if (data.empty()) {
output = emptyString();
return true;
}
StringBuilder buffer;
buffer.reserveCapacity(data.size());
if (!decodeString(data, buffer))
return false;
output = buffer.toString();
return true;
}
template<Value::ParsingMode parsingMode = Value::ParsingMode::Strict, typename CodeUnit>
RefPtr<JSON::Value> buildValue(std::span<const CodeUnit> data, std::span<const CodeUnit>& valueTokenEnd, int depth)
{
if (depth > stackLimit)
return nullptr;
RefPtr<JSON::Value> result;
std::span<const CodeUnit> tokenStart;
std::span<const CodeUnit> tokenEnd;
Token token = parseToken(data, tokenStart, tokenEnd);
switch (token) {
case Token::Invalid:
return nullptr;
case Token::Null:
result = JSON::Value::null();
break;
case Token::BoolTrue:
result = JSON::Value::create(true);
break;
case Token::BoolFalse:
result = JSON::Value::create(false);
break;
case Token::Number: {
bool ok;
double value = charactersToDouble(tokenStart.first(tokenEnd.data() - tokenStart.data()), &ok);
if (!ok)
return nullptr;
result = JSON::Value::create(value);
break;
}
case Token::String: {
String value;
if (tokenEnd.data() - tokenStart.data() < 2)
return nullptr;
bool ok = decodeString(tokenStart.subspan(1, std::to_address(tokenEnd.begin()) - std::to_address(tokenStart.begin()) - 2), value);
if (!ok)
return nullptr;
result = JSON::Value::create(value);
break;
}
case Token::ArrayBegin: {
Ref<JSON::Array> array = JSON::Array::create();
data = tokenEnd;
token = parseToken(data, tokenStart, tokenEnd);
while (token != Token::ArrayEnd) {
RefPtr<JSON::Value> arrayNode = buildValue<parsingMode>(data, tokenEnd, depth + 1);
if (!arrayNode)
return nullptr;
array->pushValue(arrayNode.releaseNonNull());
// After a list value, we expect a comma or the end of the list.
data = tokenEnd;
token = parseToken(data, tokenStart, tokenEnd);
if (token == Token::ListSeparator) {
data = tokenEnd;
token = parseToken(data, tokenStart, tokenEnd);
if (token == Token::ArrayEnd) {
if constexpr (parsingMode != Value::ParsingMode::AllowTrailingCommas)
return nullptr;
else
break;
}
} else if (token != Token::ArrayEnd) {
// Unexpected value after list value. Bail out.
return nullptr;
}
}
ASSERT(token == Token::ArrayEnd);
result = WTF::move(array);
break;
}
case Token::ObjectBegin: {
Ref<JSON::Object> object = JSON::Object::create();
data = tokenEnd;
token = parseToken(data, tokenStart, tokenEnd);
while (token != Token::ObjectEnd) {
if (token != Token::String)
return nullptr;
String key;
if (tokenEnd.data() - tokenStart.data() < 2)
return nullptr;
if (!decodeString(tokenStart.subspan(1, std::to_address(tokenEnd.begin()) - std::to_address(tokenStart.begin()) - 2), key))
return nullptr;
data = tokenEnd;
token = parseToken(data, tokenStart, tokenEnd);
if (token != Token::ObjectPairSeparator)
return nullptr;
data = tokenEnd;
RefPtr<JSON::Value> value = buildValue<parsingMode>(data, tokenEnd, depth + 1);
if (!value)
return nullptr;
object->setValue(key, value.releaseNonNull());
data = tokenEnd;
// After a key/value pair, we expect a comma or the end of the
// object.
token = parseToken(data, tokenStart, tokenEnd);
if (token == Token::ListSeparator) {
data = tokenEnd;
token = parseToken(data, tokenStart, tokenEnd);
if (token == Token::ObjectEnd) {
if constexpr (parsingMode != Value::ParsingMode::AllowTrailingCommas)
return nullptr;
else
break;
}
} else if (token != Token::ObjectEnd) {
// Unexpected value after last object value. Bail out.
return nullptr;
}
}
ASSERT(token == Token::ObjectEnd);
result = WTF::move(object);
break;
}
default:
// We got a token that's not a value.
return nullptr;
}
valueTokenEnd = tokenEnd;
return result;
}
} // anonymous namespace
template<typename Visitor> constexpr decltype(auto) Value::visitDerived(Visitor&& visitor)
{
if (std::holds_alternative<ObjectTypeTag>(m_value))
return std::invoke(std::forward<Visitor>(visitor), static_cast<Object&>(*this));
if (std::holds_alternative<ArrayTypeTag>(m_value))
return std::invoke(std::forward<Visitor>(visitor), static_cast<Array&>(*this));
return std::invoke(std::forward<Visitor>(visitor), static_cast<Value&>(*this));
}
template<typename Visitor> constexpr decltype(auto) Value::visitDerived(Visitor&& visitor) const
{
return const_cast<Value&>(*this).visitDerived([&](auto& derived) {
return std::invoke(std::forward<Visitor>(visitor), std::as_const(derived));
});
}
void Value::operator delete(Value* value, std::destroying_delete_t)
{
value->visitDerived([](auto& derived) {
std::destroy_at(&derived);
std::decay_t<decltype(derived)>::freeAfterDestruction(&derived);
});
}
Ref<Value> Value::null()
{
return adoptRef(*new Value);
}
Ref<Value> Value::create(bool value)
{
return adoptRef(*new Value(value));
}
Ref<Value> Value::create(int value)
{
return adoptRef(*new Value(value));
}
Ref<Value> Value::create(double value)
{
return adoptRef(*new Value(value));
}
Ref<Value> Value::create(const String& value)
{
return adoptRef(*new Value(value));
}
RefPtr<Value> Value::parseJSON(StringView json)
{
return parseJSON(json, ParsingMode::Strict);
}
RefPtr<Value> Value::parseJSON(StringView json, ParsingMode parsingMode)
{
auto containsNonSpace = [] (auto span) {
if (!span.data())
return false;
for (auto character : span) {
if (!isASCIIWhitespaceWithoutFF(character))
return true;
}
return false;
};
RefPtr<Value> result;
if (json.is8Bit()) {
auto data = json.span8();
std::span<const Latin1Character> tokenEnd;
if (parsingMode == ParsingMode::AllowTrailingCommas)
result = buildValue<ParsingMode::AllowTrailingCommas>(data, tokenEnd, 0);
else
result = buildValue(data, tokenEnd, 0);
if (containsNonSpace(tokenEnd))
return nullptr;
} else {
auto data = json.span16();
std::span<const char16_t> tokenEnd;
if (parsingMode == ParsingMode::AllowTrailingCommas)
result = buildValue<ParsingMode::AllowTrailingCommas>(data, tokenEnd, 0);
else
result = buildValue(data, tokenEnd, 0);
if (containsNonSpace(tokenEnd))
return nullptr;
}
return result;
}
std::optional<Ref<Value>> Value::optionalParseJSON(StringView json)
{
auto value = parseJSON(json);
if (value)
return value.releaseNonNull();
return std::nullopt;
}
String Value::toJSONString() const
{
StringBuilder result;
result.reserveCapacity(512);
writeJSON(result);
return result.toString();
}
std::optional<bool> Value::asBoolean() const
{
if (auto* boolean = std::get_if<bool>(&m_value))
return *boolean;
return std::nullopt;
}
std::optional<double> Value::asDouble() const
{
if (auto* number = std::get_if<double>(&m_value))
return *number;
if (auto* number = std::get_if<int>(&m_value))
return static_cast<double>(*number);
return std::nullopt;
}
std::optional<int> Value::asInteger() const
{
if (auto* number = std::get_if<int>(&m_value))
return *number;
if (auto* number = std::get_if<double>(&m_value))
return truncateDoubleToInt32(*number);
return std::nullopt;
}
const String& Value::asString() const
{
if (auto* string = std::get_if<String>(&m_value))
return *string;
return nullString();
}
void Value::dump(PrintStream& out) const
{
WTF::visit(WTF::makeVisitor([&](std::monostate) {
out.print("null"_s);
}, [&](bool boolean) {
out.print(boolean ? "true"_s : "false"_s);
}, [&](double number) {
if (!std::isfinite(number))
out.print("null"_s);
else
out.print(makeString(number));
}, [&](int number) {
out.print(makeString(number));
}, [&](const String& string) {
StringBuilder builder;
builder.appendQuotedJSONString(string);
out.print(builder.toString());
}, [&](ObjectTypeTag) {
// Safety: This lambda runs synchronously so it is safe to capture `this` without refing.
SUPPRESS_UNCOUNTED_LAMBDA_CAPTURE auto& object = *static_cast<const ObjectBase*>(this);
CommaPrinter comma(","_s);
out.print("{"_s);
for (const auto& key : object.m_order) {
auto findResult = object.m_map.find(key);
ASSERT(findResult != object.m_map.end());
StringBuilder builder;
builder.appendQuotedJSONString(findResult->key);
out.print(comma, builder.toString(), ":"_s, findResult->value.get());
}
out.print("}"_s);
}, [&](ArrayTypeTag) {
// Safety: This lambda runs synchronously so it is safe to capture `this` without refing.
SUPPRESS_UNCOUNTED_LAMBDA_CAPTURE auto& array = *static_cast<const ArrayBase*>(this);
CommaPrinter comma(","_s);
out.print("["_s);
for (auto& value : array.m_map)
out.print(comma, value.get());
out.print("]"_s);
}), m_value);
}
void Value::writeJSON(StringBuilder& output) const
{
visitDerived([&](auto& derived) {
derived.writeJSONImpl(output);
});
}
void Value::writeJSONImpl(StringBuilder& output) const
{
WTF::visit(WTF::makeVisitor([&](std::monostate) {
output.append("null"_s);
}, [&](bool boolean) {
if (boolean)
output.append("true"_s);
else
output.append("false"_s);
}, [&](double number) {
if (!std::isfinite(number))
output.append("null"_s);
else
output.append(number);
}, [&](int number) {
output.append(number);
}, [&](const String& string) {
output.appendQuotedJSONString(string);
}, [&](ObjectTypeTag) {
ASSERT_NOT_REACHED();
}, [&](ArrayTypeTag) {
ASSERT_NOT_REACHED();
}), m_value);
}
size_t Value::memoryCost() const
{
return visitDerived([&](auto& derived) {
return derived.memoryCostImpl();
});
}
size_t Value::memoryCostImpl() const
{
size_t memoryCost = sizeof(*this);
if (auto* string = std::get_if<String>(&m_value))
memoryCost += string->sizeInBytes();
return memoryCost;
}
ObjectBase::~ObjectBase() = default;
size_t ObjectBase::memoryCostImpl() const
{
size_t memoryCost = sizeof(*this);
for (const auto& entry : m_map)
memoryCost += entry.key.sizeInBytes() + Ref { entry.value }->memoryCost();
return memoryCost;
}
std::optional<bool> ObjectBase::getBoolean(const String& name) const
{
auto value = getValue(name);
if (!value)
return std::nullopt;
return value->asBoolean();
}
std::optional<double> ObjectBase::getDouble(const String& name) const
{
auto value = getValue(name);
if (!value)
return std::nullopt;
return value->asDouble();
}
std::optional<int> ObjectBase::getInteger(const String& name) const
{
auto value = getValue(name);
if (!value)
return std::nullopt;
return value->asInteger();
}
String ObjectBase::getString(const String& name) const
{
auto value = getValue(name);
if (!value)
return nullString();
return value->asString();
}
RefPtr<Object> ObjectBase::getObject(const String& name) const
{
auto value = getValue(name);
if (!value)
return nullptr;
return value->asObject();
}
RefPtr<Array> ObjectBase::getArray(const String& name) const
{
auto value = getValue(name);
if (!value)
return nullptr;
return value->asArray();
}
RefPtr<Value> ObjectBase::getValue(const String& name) const
{
auto findResult = m_map.find(name);
if (findResult == m_map.end())
return nullptr;
return findResult->value.copyRef();
}
void ObjectBase::remove(const String& name)
{
m_map.remove(name);
m_order.removeFirst(name);
}
void ObjectBase::writeJSONImpl(StringBuilder& output) const
{
output.append('{');
for (size_t i = 0; i < m_order.size(); ++i) {
auto findResult = m_map.find(m_order[i]);
ASSERT(findResult != m_map.end());
if (i)
output.append(',');
output.appendQuotedJSONString(findResult->key);
output.append(':');
Ref { findResult->value }->writeJSON(output);
}
output.append('}');
}
ObjectBase::ObjectBase()
: Value(ObjectTypeTag::Object)
{
}
ArrayBase::~ArrayBase() = default;
void ArrayBase::writeJSONImpl(StringBuilder& output) const
{
output.append('[');
bool isFirst = true;
for (auto& value : m_map) {
if (isFirst)
isFirst = false;
else
output.append(',');
value->writeJSON(output);
}
output.append(']');
}
ArrayBase::ArrayBase()
: Value(ArrayTypeTag::Array)
{
}
Ref<Value> ArrayBase::get(size_t index) const
{
RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(index < m_map.size());
return m_map[index];
}
Ref<Object> Object::create()
{
return adoptRef(*new Object);
}
Ref<Array> Array::create()
{
return adoptRef(*new Array);
}
size_t ArrayBase::memoryCostImpl() const
{
size_t memoryCost = sizeof(*this);
for (const auto& item : m_map)
memoryCost += item->memoryCost();
return memoryCost;
}
// FIXME: <http://webkit.org/b/179847> remove these functions when legacy InspectorObject symbols are no longer needed.
bool Value::asDouble(double& output) const
{
auto x = asDouble();
if (!x)
return false;
output = *x;
return true;
}
bool Value::asInteger(int& output) const
{
auto x = asInteger();
if (!x)
return false;
output = *x;
return true;
}
bool Value::asString(String& output) const
{
auto x = asString();
if (!x)
return false;
output = x;
return true;
}
bool ObjectBase::getBoolean(const String& name, bool& output) const
{
auto x = getBoolean(name);
if (!x)
return false;
output = *x;
return true;
}
bool ObjectBase::getString(const String& name, String& output) const
{
auto x = getString(name);
if (!x)
return false;
output = x;
return true;
}
bool ObjectBase::getObject(const String& name, RefPtr<Object>& output) const
{
auto x = getObject(name);
if (!x)
return false;
output = x.releaseNonNull();
return true;
}
bool ObjectBase::getArray(const String& name, RefPtr<Array>& output) const
{
auto x = getArray(name);
if (!x)
return false;
output = x.releaseNonNull();
return true;
}
bool ObjectBase::getValue(const String& name, RefPtr<Value>& output) const
{
auto x = getValue(name);
if (!x)
return false;
output = x.releaseNonNull();
return true;
}
} // namespace JSONImpl
} // namespace WTF