740 lines
23 KiB
C++
740 lines
23 KiB
C++
|
// Protocol Buffers - Google's data interchange format
|
||
|
// Copyright 2008 Google Inc. All rights reserved.
|
||
|
// https://developers.google.com/protocol-buffers/
|
||
|
//
|
||
|
// 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 "google/protobuf/json/internal/lexer.h"
|
||
|
|
||
|
#include <cstddef>
|
||
|
#include <cstdint>
|
||
|
#include <functional>
|
||
|
#include <ostream>
|
||
|
#include <string>
|
||
|
#include <utility>
|
||
|
#include <vector>
|
||
|
|
||
|
#include <gmock/gmock.h>
|
||
|
#include <gtest/gtest.h>
|
||
|
#include "absl/algorithm/container.h"
|
||
|
#include "absl/status/status.h"
|
||
|
#include "absl/status/statusor.h"
|
||
|
#include "absl/strings/ascii.h"
|
||
|
#include "absl/strings/escaping.h"
|
||
|
#include "absl/strings/str_cat.h"
|
||
|
#include "absl/strings/str_format.h"
|
||
|
#include "absl/strings/str_replace.h"
|
||
|
#include "absl/strings/string_view.h"
|
||
|
#include "absl/types/variant.h"
|
||
|
#include "google/protobuf/io/zero_copy_stream.h"
|
||
|
#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
|
||
|
#include "google/protobuf/json/internal/test_input_stream.h"
|
||
|
#include "google/protobuf/stubs/status_macros.h"
|
||
|
|
||
|
// Must be included last.
|
||
|
#include "google/protobuf/port_def.inc"
|
||
|
|
||
|
namespace google {
|
||
|
namespace protobuf {
|
||
|
namespace json_internal {
|
||
|
namespace {
|
||
|
using ::testing::_;
|
||
|
using ::testing::ElementsAre;
|
||
|
using ::testing::Field;
|
||
|
using ::testing::HasSubstr;
|
||
|
using ::testing::IsEmpty;
|
||
|
using ::testing::Pair;
|
||
|
using ::testing::SizeIs;
|
||
|
using ::testing::VariantWith;
|
||
|
|
||
|
// TODO(b/234474291): Use the gtest versions once that's available in OSS.
|
||
|
MATCHER_P(IsOkAndHolds, inner,
|
||
|
absl::StrCat("is OK and holds ", testing::PrintToString(inner))) {
|
||
|
if (!arg.ok()) {
|
||
|
*result_listener << arg.status();
|
||
|
return false;
|
||
|
}
|
||
|
return testing::ExplainMatchResult(inner, *arg, result_listener);
|
||
|
}
|
||
|
|
||
|
// absl::Status GetStatus(const absl::Status& s) { return s; }
|
||
|
template <typename T>
|
||
|
absl::Status GetStatus(const absl::StatusOr<T>& s) {
|
||
|
return s.status();
|
||
|
}
|
||
|
|
||
|
MATCHER_P(StatusIs, status,
|
||
|
absl::StrCat(".status() is ", testing::PrintToString(status))) {
|
||
|
return GetStatus(arg).code() == status;
|
||
|
}
|
||
|
|
||
|
#define EXPECT_OK(x) EXPECT_THAT(x, StatusIs(absl::StatusCode::kOk))
|
||
|
#define ASSERT_OK(x) ASSERT_THAT(x, StatusIs(absl::StatusCode::kOk))
|
||
|
|
||
|
// TODO(b/234868512): There are several tests that validate non-standard
|
||
|
// behavior that is assumed to be present in the wild due to Hyrum's Law. These
|
||
|
// tests are grouped under the `NonStandard` suite. These tests ensure the
|
||
|
// non-standard syntax is accepted, and that disabling legacy mode rejects them.
|
||
|
//
|
||
|
// All other tests are strictly-conforming.
|
||
|
|
||
|
// A generic JSON value, which is gtest-matcher friendly and stream-printable.
|
||
|
struct Value {
|
||
|
static absl::StatusOr<Value> Parse(io::ZeroCopyInputStream* stream,
|
||
|
ParseOptions options = {}) {
|
||
|
JsonLexer lex(stream, options);
|
||
|
return Parse(lex);
|
||
|
}
|
||
|
static absl::StatusOr<Value> Parse(JsonLexer& lex) {
|
||
|
absl::StatusOr<JsonLexer::Kind> kind = lex.PeekKind();
|
||
|
RETURN_IF_ERROR(kind.status());
|
||
|
|
||
|
switch (*kind) {
|
||
|
case JsonLexer::kNull:
|
||
|
RETURN_IF_ERROR(lex.Expect("null"));
|
||
|
return Value{Null{}};
|
||
|
case JsonLexer::kFalse:
|
||
|
RETURN_IF_ERROR(lex.Expect("false"));
|
||
|
return Value{false};
|
||
|
case JsonLexer::kTrue:
|
||
|
RETURN_IF_ERROR(lex.Expect("true"));
|
||
|
return Value{true};
|
||
|
case JsonLexer::kNum: {
|
||
|
absl::StatusOr<LocationWith<double>> num = lex.ParseNumber();
|
||
|
RETURN_IF_ERROR(num.status());
|
||
|
return Value{num->value};
|
||
|
}
|
||
|
case JsonLexer::kStr: {
|
||
|
absl::StatusOr<LocationWith<MaybeOwnedString>> str = lex.ParseUtf8();
|
||
|
RETURN_IF_ERROR(str.status());
|
||
|
return Value{str->value.ToString()};
|
||
|
}
|
||
|
case JsonLexer::kArr: {
|
||
|
std::vector<Value> arr;
|
||
|
absl::Status s = lex.VisitArray([&arr, &lex]() -> absl::Status {
|
||
|
absl::StatusOr<Value> val = Value::Parse(lex);
|
||
|
RETURN_IF_ERROR(val.status());
|
||
|
arr.emplace_back(*std::move(val));
|
||
|
return absl::OkStatus();
|
||
|
});
|
||
|
RETURN_IF_ERROR(s);
|
||
|
return Value{std::move(arr)};
|
||
|
}
|
||
|
case JsonLexer::kObj: {
|
||
|
std::vector<std::pair<std::string, Value>> obj;
|
||
|
absl::Status s = lex.VisitObject(
|
||
|
[&obj, &lex](LocationWith<MaybeOwnedString>& key) -> absl::Status {
|
||
|
absl::StatusOr<Value> val = Value::Parse(lex);
|
||
|
RETURN_IF_ERROR(val.status());
|
||
|
obj.emplace_back(std::move(key.value.ToString()),
|
||
|
*std::move(val));
|
||
|
return absl::OkStatus();
|
||
|
});
|
||
|
RETURN_IF_ERROR(s);
|
||
|
return Value{std::move(obj)};
|
||
|
}
|
||
|
}
|
||
|
return absl::InternalError("Unrecognized kind in lexer");
|
||
|
}
|
||
|
|
||
|
friend std::ostream& operator<<(std::ostream& os, const Value& v) {
|
||
|
if (absl::holds_alternative<Null>(v.value)) {
|
||
|
os << "null";
|
||
|
} else if (const auto* x = absl::get_if<bool>(&v.value)) {
|
||
|
os << "bool:" << (*x ? "true" : "false");
|
||
|
} else if (const auto* x = absl::get_if<double>(&v.value)) {
|
||
|
os << "num:" << *x;
|
||
|
} else if (const auto* x = absl::get_if<std::string>(&v.value)) {
|
||
|
os << "str:" << absl::CHexEscape(*x);
|
||
|
} else if (const auto* x = absl::get_if<Array>(&v.value)) {
|
||
|
os << "arr:[";
|
||
|
bool first = true;
|
||
|
for (const auto& val : *x) {
|
||
|
if (!first) {
|
||
|
os << ", ";
|
||
|
}
|
||
|
os << val;
|
||
|
}
|
||
|
os << "]";
|
||
|
} else if (const auto* x = absl::get_if<Object>(&v.value)) {
|
||
|
os << "obj:[";
|
||
|
bool first = true;
|
||
|
for (const auto& kv : *x) {
|
||
|
if (!first) {
|
||
|
os << ", ";
|
||
|
first = false;
|
||
|
}
|
||
|
os << kv.first << ":" << kv.second;
|
||
|
}
|
||
|
os << "]";
|
||
|
}
|
||
|
return os;
|
||
|
}
|
||
|
|
||
|
struct Null {};
|
||
|
using Array = std::vector<Value>;
|
||
|
using Object = std::vector<std::pair<std::string, Value>>;
|
||
|
absl::variant<Null, bool, double, std::string, Array, Object> value;
|
||
|
};
|
||
|
|
||
|
template <typename T, typename M>
|
||
|
testing::Matcher<const Value&> ValueIs(M inner) {
|
||
|
return Field(&Value::value, VariantWith<T>(inner));
|
||
|
}
|
||
|
|
||
|
// Executes `test` once for each three-segment split of `json`.
|
||
|
void Do(absl::string_view json,
|
||
|
std::function<void(io::ZeroCopyInputStream*)> test,
|
||
|
bool verify_all_consumed = true) {
|
||
|
SCOPED_TRACE(absl::StrCat("json: ", absl::CHexEscape(json)));
|
||
|
|
||
|
for (size_t i = 0; i < json.size(); ++i) {
|
||
|
for (size_t j = 0; j < json.size() - i + 1; ++j) {
|
||
|
SCOPED_TRACE(absl::StrFormat("json[0:%d], json[%d:%d], json[%d:%d]", i, i,
|
||
|
i + j, i + j, json.size()));
|
||
|
std::string first(json.substr(0, i));
|
||
|
std::string second(json.substr(i, j));
|
||
|
std::string third(json.substr(i + j));
|
||
|
|
||
|
TestInputStream in = {first, second, third};
|
||
|
test(&in);
|
||
|
if (testing::Test::HasFailure()) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (verify_all_consumed) {
|
||
|
if (!absl::c_all_of(third,
|
||
|
[](char c) { return absl::ascii_isspace(c); })) {
|
||
|
ASSERT_GE(in.Consumed(), 3);
|
||
|
} else if (!absl::c_all_of(
|
||
|
second, [](char c) { return absl::ascii_isspace(c); })) {
|
||
|
ASSERT_GE(in.Consumed(), 2);
|
||
|
} else {
|
||
|
ASSERT_GE(in.Consumed(), 1);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void BadInner(absl::string_view json, ParseOptions opts = {}) {
|
||
|
Do(
|
||
|
json,
|
||
|
[=](io::ZeroCopyInputStream* stream) {
|
||
|
EXPECT_THAT(Value::Parse(stream, opts),
|
||
|
StatusIs(absl::StatusCode::kInvalidArgument));
|
||
|
},
|
||
|
false);
|
||
|
}
|
||
|
|
||
|
// Like Do, but runs a legacy syntax test twice: once with legacy settings, once
|
||
|
// without. For the latter, the test is expected to fail; for the former,
|
||
|
// `test` is called so it can run expectations.
|
||
|
void DoLegacy(absl::string_view json, std::function<void(const Value&)> test) {
|
||
|
Do(json, [&](io::ZeroCopyInputStream* stream) {
|
||
|
ParseOptions options;
|
||
|
options.allow_legacy_syntax = true;
|
||
|
auto value = Value::Parse(stream, options);
|
||
|
ASSERT_OK(value);
|
||
|
test(*value);
|
||
|
});
|
||
|
BadInner(json);
|
||
|
}
|
||
|
|
||
|
// Like Bad, but ensures json fails to parse in both modes.
|
||
|
void Bad(absl::string_view json) {
|
||
|
ParseOptions options;
|
||
|
options.allow_legacy_syntax = true;
|
||
|
BadInner(json, options);
|
||
|
BadInner(json);
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, Null) {
|
||
|
Do("null", [](io::ZeroCopyInputStream* stream) {
|
||
|
EXPECT_THAT(Value::Parse(stream), IsOkAndHolds(ValueIs<Value::Null>(_)));
|
||
|
});
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, False) {
|
||
|
Do("false", [](io::ZeroCopyInputStream* stream) {
|
||
|
EXPECT_THAT(Value::Parse(stream), IsOkAndHolds(ValueIs<bool>(false)));
|
||
|
});
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, True) {
|
||
|
Do("true", [](io::ZeroCopyInputStream* stream) {
|
||
|
EXPECT_THAT(Value::Parse(stream), IsOkAndHolds(ValueIs<bool>(true)));
|
||
|
});
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, Typos) {
|
||
|
Bad("-");
|
||
|
Bad("-foo");
|
||
|
Bad("nule");
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, UnknownCharacters) {
|
||
|
Bad("*");
|
||
|
Bad("[*]");
|
||
|
Bad("{key: *}");
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, EmptyString) {
|
||
|
Do(R"json("")json", [](io::ZeroCopyInputStream* stream) {
|
||
|
EXPECT_THAT(Value::Parse(stream),
|
||
|
IsOkAndHolds(ValueIs<std::string>(IsEmpty())));
|
||
|
});
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, SimpleString) {
|
||
|
Do(R"json("My String")json", [](io::ZeroCopyInputStream* stream) {
|
||
|
EXPECT_THAT(Value::Parse(stream),
|
||
|
IsOkAndHolds(ValueIs<std::string>("My String")));
|
||
|
});
|
||
|
}
|
||
|
|
||
|
TEST(NonStandard, SingleQuoteString) {
|
||
|
DoLegacy(R"json('My String')json", [=](const Value& value) {
|
||
|
EXPECT_THAT(value, ValueIs<std::string>("My String"));
|
||
|
});
|
||
|
}
|
||
|
|
||
|
TEST(NonStandard, ControlCharsInString) {
|
||
|
DoLegacy("\"\1\2\3\4\5\6\7\b\n\f\r\"", [=](const Value& value) {
|
||
|
EXPECT_THAT(value, ValueIs<std::string>("\1\2\3\4\5\6\7\b\n\f\r"));
|
||
|
});
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, Latin) {
|
||
|
Do(R"json("Pokémon")json", [](io::ZeroCopyInputStream* stream) {
|
||
|
EXPECT_THAT(Value::Parse(stream),
|
||
|
IsOkAndHolds(ValueIs<std::string>("Pokémon")));
|
||
|
});
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, Cjk) {
|
||
|
Do(R"json("施氏食獅史")json", [](io::ZeroCopyInputStream* stream) {
|
||
|
EXPECT_THAT(Value::Parse(stream),
|
||
|
IsOkAndHolds(ValueIs<std::string>("施氏食獅史")));
|
||
|
});
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, BrokenString) {
|
||
|
Bad(R"json("broken)json");
|
||
|
Bad(R"json("broken')json");
|
||
|
Bad(R"json("broken\")json");
|
||
|
}
|
||
|
|
||
|
TEST(NonStandard, BrokenString) {
|
||
|
Bad(R"json('broken)json");
|
||
|
Bad(R"json('broken")json");
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, BrokenEscape) {
|
||
|
Bad(R"json("\)json");
|
||
|
Bad(R"json("\a")json");
|
||
|
Bad(R"json("\u")json");
|
||
|
Bad(R"json("\u123")json");
|
||
|
Bad(R"json("\u{1f36f}")json");
|
||
|
Bad(R"json("\u123$$$")json");
|
||
|
Bad(R"json("\ud800\udcfg")json");
|
||
|
}
|
||
|
|
||
|
void GoodNumber(absl::string_view json, double value) {
|
||
|
Do(json, [value](io::ZeroCopyInputStream* stream) {
|
||
|
EXPECT_THAT(Value::Parse(stream), IsOkAndHolds(ValueIs<double>(value)));
|
||
|
});
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, Zero) {
|
||
|
GoodNumber("0", 0);
|
||
|
GoodNumber("0.0", 0);
|
||
|
GoodNumber("0.000", 0);
|
||
|
GoodNumber("-0", -0.0);
|
||
|
GoodNumber("-0.0", -0.0);
|
||
|
|
||
|
Bad("00");
|
||
|
Bad("-00");
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, Integer) {
|
||
|
GoodNumber("123456", 123456);
|
||
|
GoodNumber("-79497823553162768", -79497823553162768);
|
||
|
GoodNumber("11779497823553163264", 11779497823553163264u);
|
||
|
|
||
|
Bad("0777");
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, Overflow) {
|
||
|
GoodNumber("18446744073709551616", 18446744073709552000.0);
|
||
|
GoodNumber("-18446744073709551616", -18446744073709551616.0);
|
||
|
|
||
|
Bad("1.89769e308");
|
||
|
Bad("-1.89769e308");
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, Double) {
|
||
|
GoodNumber("42.5", 42.5);
|
||
|
GoodNumber("42.50", 42.50);
|
||
|
GoodNumber("-1045.235", -1045.235);
|
||
|
GoodNumber("-0.235", -0.235);
|
||
|
|
||
|
Bad("42.");
|
||
|
Bad("01.3");
|
||
|
Bad(".5");
|
||
|
Bad("-.5");
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, Scientific) {
|
||
|
GoodNumber("1.2345e+10", 1.2345e+10);
|
||
|
GoodNumber("1.2345e-10", 1.2345e-10);
|
||
|
GoodNumber("1.2345e10", 1.2345e10);
|
||
|
GoodNumber("1.2345E+10", 1.2345e+10);
|
||
|
GoodNumber("1.2345E-10", 1.2345e-10);
|
||
|
GoodNumber("1.2345E10", 1.2345e10);
|
||
|
GoodNumber("0e0", 0);
|
||
|
GoodNumber("9E9", 9e9);
|
||
|
|
||
|
Bad("1.e5");
|
||
|
Bad("-e5");
|
||
|
Bad("1e");
|
||
|
Bad("1e-");
|
||
|
Bad("1e+");
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, EmptyArray) {
|
||
|
Do("[]", [](io::ZeroCopyInputStream* stream) {
|
||
|
EXPECT_THAT(Value::Parse(stream),
|
||
|
IsOkAndHolds(ValueIs<Value::Array>(IsEmpty())));
|
||
|
});
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, PrimitiveArray) {
|
||
|
absl::string_view json = R"json(
|
||
|
[true, false, null, "string"]
|
||
|
)json";
|
||
|
Do(json, [](io::ZeroCopyInputStream* stream) {
|
||
|
EXPECT_THAT(Value::Parse(stream),
|
||
|
IsOkAndHolds(ValueIs<Value::Array>(ElementsAre(
|
||
|
ValueIs<bool>(true), //
|
||
|
ValueIs<bool>(false), //
|
||
|
ValueIs<Value::Null>(_), //
|
||
|
ValueIs<std::string>("string") //
|
||
|
))));
|
||
|
});
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, BrokenArray) {
|
||
|
Bad("[");
|
||
|
Bad("[[");
|
||
|
Bad("[true, null}");
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, BrokenStringInArray) { Bad(R"json(["Unterminated])json"); }
|
||
|
|
||
|
TEST(LexerTest, NestedArray) {
|
||
|
absl::string_view json = R"json(
|
||
|
[
|
||
|
[22, -127, 45.3, -1056.4, 11779497823553162765],
|
||
|
{"key": true}
|
||
|
]
|
||
|
)json";
|
||
|
Do(json, [](io::ZeroCopyInputStream* stream) {
|
||
|
EXPECT_THAT(Value::Parse(stream),
|
||
|
IsOkAndHolds(ValueIs<Value::Array>(ElementsAre(
|
||
|
ValueIs<Value::Array>(ElementsAre(
|
||
|
ValueIs<double>(22), //
|
||
|
ValueIs<double>(-127), //
|
||
|
ValueIs<double>(45.3), //
|
||
|
ValueIs<double>(-1056.4), //
|
||
|
ValueIs<double>(11779497823553162765u) //
|
||
|
)),
|
||
|
ValueIs<Value::Object>(
|
||
|
ElementsAre(Pair("key", ValueIs<bool>(true))))))));
|
||
|
});
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, EmptyObject) {
|
||
|
Do("{}", [](io::ZeroCopyInputStream* stream) {
|
||
|
EXPECT_THAT(Value::Parse(stream),
|
||
|
IsOkAndHolds(ValueIs<Value::Object>(IsEmpty())));
|
||
|
});
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, BrokenObject) {
|
||
|
Bad("{");
|
||
|
Bad("{{");
|
||
|
Bad(R"json({"key": true])json");
|
||
|
Bad(R"json({"key")json");
|
||
|
Bad(R"json({"key":})json");
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, BrokenStringInObject) {
|
||
|
Bad(R"json({"oops": "Unterminated})json");
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, NonPairInObject) {
|
||
|
Bad("{null}");
|
||
|
Bad("{true}");
|
||
|
Bad("{false}");
|
||
|
Bad("{42}");
|
||
|
Bad("{[null]}");
|
||
|
Bad(R"json({{"nest_pas": true}})json");
|
||
|
Bad(R"json({"missing colon"})json");
|
||
|
}
|
||
|
|
||
|
TEST(NonStandard, NonPairInObject) {
|
||
|
Bad("{'missing colon'}");
|
||
|
Bad("{missing_colon}");
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, WrongCommas) {
|
||
|
Bad("[null null]");
|
||
|
Bad("[null,, null]");
|
||
|
Bad(R"json({"a": 0 "b": true})json");
|
||
|
Bad(R"json({"a": 0,, "b": true})json");
|
||
|
}
|
||
|
|
||
|
TEST(NonStandard, Keys) {
|
||
|
DoLegacy(R"json({'s': true})json", [](const Value& value) {
|
||
|
EXPECT_THAT(value, ValueIs<Value::Object>(
|
||
|
ElementsAre(Pair("s", ValueIs<bool>(true)))));
|
||
|
});
|
||
|
DoLegacy(R"json({key: null})json", [](const Value& value) {
|
||
|
EXPECT_THAT(value, ValueIs<Value::Object>(
|
||
|
ElementsAre(Pair("key", ValueIs<Value::Null>(_)))));
|
||
|
});
|
||
|
DoLegacy(R"json({snake_key: []})json", [](const Value& value) {
|
||
|
EXPECT_THAT(value, ValueIs<Value::Object>(ElementsAre(Pair(
|
||
|
"snake_key", ValueIs<Value::Array>(IsEmpty())))));
|
||
|
});
|
||
|
DoLegacy(R"json({camelKey: {}})json", [](const Value& value) {
|
||
|
EXPECT_THAT(value, ValueIs<Value::Object>(ElementsAre(Pair(
|
||
|
"camelKey", ValueIs<Value::Object>(IsEmpty())))));
|
||
|
});
|
||
|
}
|
||
|
|
||
|
TEST(NonStandard, KeywordPrefixedKeys) {
|
||
|
DoLegacy(R"json({nullkey: "a"})json", [](const Value& value) {
|
||
|
EXPECT_THAT(value, ValueIs<Value::Object>(ElementsAre(
|
||
|
Pair("nullkey", ValueIs<std::string>("a")))));
|
||
|
});
|
||
|
DoLegacy(R"json({truekey: "b"})json", [](const Value& value) {
|
||
|
EXPECT_THAT(value, ValueIs<Value::Object>(ElementsAre(
|
||
|
Pair("truekey", ValueIs<std::string>("b")))));
|
||
|
});
|
||
|
DoLegacy(R"json({falsekey: "c"})json", [](const Value& value) {
|
||
|
EXPECT_THAT(value, ValueIs<Value::Object>(ElementsAre(
|
||
|
Pair("falsekey", ValueIs<std::string>("c")))));
|
||
|
});
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, BadKeys) {
|
||
|
Bad("{null: 0}");
|
||
|
Bad("{true: 0}");
|
||
|
Bad("{false: 0}");
|
||
|
Bad("{lisp-kebab: 0}");
|
||
|
Bad("{42: true}");
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, NestedObject) {
|
||
|
absl::string_view json = R"json(
|
||
|
{
|
||
|
"t": true,
|
||
|
"f": false,
|
||
|
"n": null,
|
||
|
"s": "a string",
|
||
|
"pi": 22,
|
||
|
"ni": -127,
|
||
|
"pd": 45.3,
|
||
|
"nd": -1056.4,
|
||
|
"pl": 11779497823553162765,
|
||
|
"l": [ [ ] ],
|
||
|
"o": { "key": true }
|
||
|
}
|
||
|
)json";
|
||
|
Do(json, [](io::ZeroCopyInputStream* stream) {
|
||
|
EXPECT_THAT(Value::Parse(stream),
|
||
|
IsOkAndHolds(ValueIs<Value::Object>(ElementsAre(
|
||
|
Pair("t", ValueIs<bool>(true)), //
|
||
|
Pair("f", ValueIs<bool>(false)), //
|
||
|
Pair("n", ValueIs<Value::Null>(_)), //
|
||
|
Pair("s", ValueIs<std::string>("a string")), //
|
||
|
Pair("pi", ValueIs<double>(22)), //
|
||
|
Pair("ni", ValueIs<double>(-127)), //
|
||
|
Pair("pd", ValueIs<double>(45.3)), //
|
||
|
Pair("nd", ValueIs<double>(-1056.4)), //
|
||
|
Pair("pl", ValueIs<double>(11779497823553162765u)), //
|
||
|
Pair("l", ValueIs<Value::Array>(ElementsAre(
|
||
|
ValueIs<Value::Array>(IsEmpty())))), //
|
||
|
Pair("o", ValueIs<Value::Object>(ElementsAre(
|
||
|
Pair("key", ValueIs<bool>(true))))) //
|
||
|
))));
|
||
|
});
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, RejectNonUtf8) {
|
||
|
absl::string_view json = R"json(
|
||
|
{ "address": x"施氏食獅史" }
|
||
|
)json";
|
||
|
Bad(absl::StrReplaceAll(json, {{"x", "\xff"}}));
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, RejectNonUtf8String) {
|
||
|
absl::string_view json = R"json(
|
||
|
{ "address": "施氏x食獅史" }
|
||
|
)json";
|
||
|
Bad(absl::StrReplaceAll(json, {{"x", "\xff"}}));
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, RejectNonUtf8Prefix) { Bad("\xff{}"); }
|
||
|
|
||
|
TEST(LexerTest, SurrogateEscape) {
|
||
|
absl::string_view json = R"json(
|
||
|
[ "\ud83d\udc08\u200D\u2b1B\ud83d\uDdA4" ]
|
||
|
)json";
|
||
|
Do(json, [](io::ZeroCopyInputStream* stream) {
|
||
|
EXPECT_THAT(Value::Parse(stream),
|
||
|
IsOkAndHolds(ValueIs<Value::Array>(
|
||
|
ElementsAre(ValueIs<std::string>("🐈⬛🖤")))));
|
||
|
});
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, InvalidCodePoint) { Bad(R"json(["\ude36"])json"); }
|
||
|
|
||
|
TEST(LexerTest, LonelyHighSurrogate) {
|
||
|
Bad(R"json(["\ud83d"])json");
|
||
|
Bad(R"json(["\ud83d|trailing"])json");
|
||
|
Bad(R"json(["\ud83d\ude--"])json");
|
||
|
Bad(R"json(["\ud83d\ud83d"])json");
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, AsciiEscape) {
|
||
|
absl::string_view json = R"json(
|
||
|
["\b", "\ning", "test\f", "\r\t", "test\\\"\/ing"]
|
||
|
)json";
|
||
|
Do(json, [](io::ZeroCopyInputStream* stream) {
|
||
|
EXPECT_THAT(Value::Parse(stream),
|
||
|
IsOkAndHolds(ValueIs<Value::Array>(ElementsAre(
|
||
|
ValueIs<std::string>("\b"), //
|
||
|
ValueIs<std::string>("\ning"), //
|
||
|
ValueIs<std::string>("test\f"), //
|
||
|
ValueIs<std::string>("\r\t"), //
|
||
|
ValueIs<std::string>("test\\\"/ing") //
|
||
|
))));
|
||
|
});
|
||
|
}
|
||
|
|
||
|
TEST(NonStandard, AsciiEscape) {
|
||
|
DoLegacy(R"json(["\'", '\''])json", [](const Value& value) {
|
||
|
EXPECT_THAT(value,
|
||
|
ValueIs<Value::Array>(ElementsAre(ValueIs<std::string>("'"), //
|
||
|
ValueIs<std::string>("'") //
|
||
|
)));
|
||
|
});
|
||
|
}
|
||
|
|
||
|
TEST(NonStandard, TrailingCommas) {
|
||
|
DoLegacy(R"json({"foo": 42,})json", [](const Value& value) {
|
||
|
EXPECT_THAT(value, ValueIs<Value::Object>(
|
||
|
ElementsAre(Pair("foo", ValueIs<double>(42)))));
|
||
|
});
|
||
|
DoLegacy(R"json({"foo": [42,],})json", [](const Value& value) {
|
||
|
EXPECT_THAT(
|
||
|
value,
|
||
|
ValueIs<Value::Object>(ElementsAre(Pair(
|
||
|
"foo", ValueIs<Value::Array>(ElementsAre(ValueIs<double>(42)))))));
|
||
|
});
|
||
|
DoLegacy(R"json([42,])json", [](const Value& value) {
|
||
|
EXPECT_THAT(value, ValueIs<Value::Array>(ElementsAre(ValueIs<double>(42))));
|
||
|
});
|
||
|
DoLegacy(R"json([{},])json", [](const Value& value) {
|
||
|
EXPECT_THAT(value, ValueIs<Value::Array>(
|
||
|
ElementsAre(ValueIs<Value::Object>(IsEmpty()))));
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// These strings are enormous; so that the test actually finishes in a
|
||
|
// reasonable time, we skip using Do().
|
||
|
|
||
|
TEST(LexerTest, ArrayRecursion) {
|
||
|
std::string ok = std::string(ParseOptions::kDefaultDepth, '[') +
|
||
|
std::string(ParseOptions::kDefaultDepth, ']');
|
||
|
|
||
|
{
|
||
|
io::ArrayInputStream stream(ok.data(), static_cast<int>(ok.size()));
|
||
|
auto value = Value::Parse(&stream);
|
||
|
ASSERT_OK(value);
|
||
|
|
||
|
Value* v = &*value;
|
||
|
for (int i = 0; i < ParseOptions::kDefaultDepth - 1; ++i) {
|
||
|
ASSERT_THAT(*v, ValueIs<Value::Array>(SizeIs(1)));
|
||
|
v = &absl::get<Value::Array>(v->value)[0];
|
||
|
}
|
||
|
ASSERT_THAT(*v, ValueIs<Value::Array>(IsEmpty()));
|
||
|
}
|
||
|
|
||
|
{
|
||
|
std::string evil = absl::StrFormat("[%s]", ok);
|
||
|
io::ArrayInputStream stream(evil.data(), static_cast<int>(evil.size()));
|
||
|
ASSERT_THAT(Value::Parse(&stream),
|
||
|
StatusIs(absl::StatusCode::kInvalidArgument));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
TEST(LexerTest, ObjectRecursion) {
|
||
|
std::string ok;
|
||
|
for (int i = 0; i < ParseOptions::kDefaultDepth - 1; ++i) {
|
||
|
absl::StrAppend(&ok, "{\"k\":");
|
||
|
}
|
||
|
absl::StrAppend(&ok, "{");
|
||
|
ok += std::string(ParseOptions::kDefaultDepth, '}');
|
||
|
|
||
|
{
|
||
|
io::ArrayInputStream stream(ok.data(), static_cast<int>(ok.size()));
|
||
|
auto value = Value::Parse(&stream);
|
||
|
ASSERT_OK(value);
|
||
|
|
||
|
Value* v = &*value;
|
||
|
for (int i = 0; i < ParseOptions::kDefaultDepth - 1; ++i) {
|
||
|
ASSERT_THAT(*v, ValueIs<Value::Object>(ElementsAre(Pair("k", _))));
|
||
|
v = &absl::get<Value::Object>(v->value)[0].second;
|
||
|
}
|
||
|
ASSERT_THAT(*v, ValueIs<Value::Object>(IsEmpty()));
|
||
|
}
|
||
|
{
|
||
|
std::string evil = absl::StrFormat("{\"k\":%s}", ok);
|
||
|
io::ArrayInputStream stream(evil.data(), static_cast<int>(evil.size()));
|
||
|
ASSERT_THAT(Value::Parse(&stream),
|
||
|
StatusIs(absl::StatusCode::kInvalidArgument));
|
||
|
}
|
||
|
}
|
||
|
} // namespace
|
||
|
} // namespace json_internal
|
||
|
} // namespace protobuf
|
||
|
} // namespace google
|