diff --git a/.eslintrc b/.eslintrc index 7bcee1db1..e4336abdc 100644 --- a/.eslintrc +++ b/.eslintrc @@ -5,6 +5,7 @@ globals: debug: true __SYSCALL: true runtime: true + performance: true env: es6: true diff --git a/SConstruct b/SConstruct index 776a5d746..23dc91f43 100755 --- a/SConstruct +++ b/SConstruct @@ -119,6 +119,7 @@ config = { 'deps/printf', 'deps/miniz', 'deps/libsodium/src/libsodium/include', + 'deps/json11', 'src', 'test', ], @@ -130,6 +131,7 @@ config = { 'acpica', 'printf', 'sodium', + 'json11', 'musl', 'gcc', ], diff --git a/deps/SConscript b/deps/SConscript index ff56d89ab..c2643a615 100644 --- a/deps/SConscript +++ b/deps/SConscript @@ -12,6 +12,7 @@ libs_config = { 'concurrentqueue', 'printf', 'trace_event', + 'json11', 'v8/include', 'v8/src', 'v8', @@ -1238,6 +1239,19 @@ libs_config = { 'libsodium/src/libsodium/sodium/version.c', ], }, + "json11": { + "include": [ + 'musl/src/internal', + 'musl/include', + 'musl/arch/x86_64', + 'musl/arch/x86_64/bits', + 'libcxx/include', + 'json11' + ], + "source": [ + 'json11/json11.cpp', + ], + }, } @@ -1245,4 +1259,3 @@ for targetname, params in libs_config.items(): target_env = env_base.Clone(); target_env.Replace(CPPPATH = params["include"]) target_env.Library(target = targetname, source = params["source"]) - diff --git a/deps/json11/.gitignore b/deps/json11/.gitignore new file mode 100644 index 000000000..e959b6fc8 --- /dev/null +++ b/deps/json11/.gitignore @@ -0,0 +1,12 @@ +# generated files +test +libjson11.a +json11.pc + +# Cmake +CMakeCache.txt +CTestTestfile.cmake +CMakeFiles +CMakeScripts +cmake_install.cmake +install_manifest.txt \ No newline at end of file diff --git a/deps/json11/CMakeLists.txt b/deps/json11/CMakeLists.txt new file mode 100644 index 000000000..ae8c41232 --- /dev/null +++ b/deps/json11/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.2) +project(json11 VERSION 1.0.0 LANGUAGES CXX) + +enable_testing() + +option(JSON11_BUILD_TESTS "Build unit tests" ON) + +add_library(json11 json11.cpp) +target_include_directories(json11 PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_compile_options(json11 + PUBLIC -std=c++11 + PRIVATE -fno-rtti -fno-exceptions -Wall -Wextra -Werror) +configure_file("json11.pc.in" "json11.pc" @ONLY) + +if (JSON11_BUILD_TESTS) + add_executable(json11_test test.cpp) + target_link_libraries(json11_test json11) +endif() + +install(TARGETS json11 DESTINATION lib) +install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/json11.hpp" DESTINATION include) +install(FILES "${CMAKE_BINARY_DIR}/json11.pc" DESTINATION lib/pkgconfig) diff --git a/deps/json11/LICENSE.txt b/deps/json11/LICENSE.txt new file mode 100644 index 000000000..691742e9e --- /dev/null +++ b/deps/json11/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2013 Dropbox, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/deps/json11/Makefile b/deps/json11/Makefile new file mode 100644 index 000000000..f946bdd81 --- /dev/null +++ b/deps/json11/Makefile @@ -0,0 +1,15 @@ +# Environment variable to enable or disable code which demonstrates the behavior change +# in Xcode 7 / Clang 3.7, introduced by DR1467 and described here: +# https://llvm.org/bugs/show_bug.cgi?id=23812 +# Defaults to on in order to act as a warning to anyone who's unaware of the issue. +ifneq ($(JSON11_ENABLE_DR1467_CANARY),) +CANARY_ARGS = -DJSON11_ENABLE_DR1467_CANARY=$(JSON11_ENABLE_DR1467_CANARY) +endif + +test: json11.cpp json11.hpp test.cpp + $(CXX) $(CANARY_ARGS) -O -std=c++11 json11.cpp test.cpp -o test -fno-rtti -fno-exceptions + +clean: + if [ -e test ]; then rm test; fi + +.PHONY: clean diff --git a/deps/json11/README.md b/deps/json11/README.md new file mode 100644 index 000000000..21f4e8b2e --- /dev/null +++ b/deps/json11/README.md @@ -0,0 +1,42 @@ +json11 +------ + +json11 is a tiny JSON library for C++11, providing JSON parsing and serialization. + +The core object provided by the library is json11::Json. A Json object represents any JSON +value: null, bool, number (int or double), string (std::string), array (std::vector), or +object (std::map). + +Json objects act like values. They can be assigned, copied, moved, compared for equality or +order, and so on. There are also helper methods Json::dump, to serialize a Json to a string, and +Json::parse (static) to parse a std::string as a Json object. + +It's easy to make a JSON object with C++11's new initializer syntax: + + Json my_json = Json::object { + { "key1", "value1" }, + { "key2", false }, + { "key3", Json::array { 1, 2, 3 } }, + }; + std::string json_str = my_json.dump(); + +There are also implicit constructors that allow standard and user-defined types to be +automatically converted to JSON. For example: + + class Point { + public: + int x; + int y; + Point (int x, int y) : x(x), y(y) {} + Json to_json() const { return Json::array { x, y }; } + }; + + std::vector points = { { 1, 2 }, { 10, 20 }, { 100, 200 } }; + std::string points_json = Json(points).dump(); + +JSON values can have their values queried and inspected: + + Json json = Json::array { Json::object { { "k", "v" } } }; + std::string str = json[0]["k"].string_value(); + +More documentation is still to come. For now, see json11.hpp. diff --git a/deps/json11/json11.cpp b/deps/json11/json11.cpp new file mode 100644 index 000000000..e3dc11904 --- /dev/null +++ b/deps/json11/json11.cpp @@ -0,0 +1,774 @@ +/* Copyright (c) 2013 Dropbox, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "json11.hpp" +#include +#include +#include +#include +#include + +namespace json11 { + +static const int max_depth = 200; + +using std::string; +using std::vector; +using std::map; +using std::make_shared; +using std::initializer_list; +using std::move; + +/* * * * * * * * * * * * * * * * * * * * + * Serialization + */ + +static void dump(std::nullptr_t, string &out) { + out += "null"; +} + +static void dump(double value, string &out) { + if (std::isfinite(value)) { + char buf[32]; + snprintf(buf, sizeof buf, "%.17g", value); + out += buf; + } else { + out += "null"; + } +} + +static void dump(int value, string &out) { + char buf[32]; + snprintf(buf, sizeof buf, "%d", value); + out += buf; +} + +static void dump(bool value, string &out) { + out += value ? "true" : "false"; +} + +static void dump(const string &value, string &out) { + out += '"'; + for (size_t i = 0; i < value.length(); i++) { + const char ch = value[i]; + if (ch == '\\') { + out += "\\\\"; + } else if (ch == '"') { + out += "\\\""; + } else if (ch == '\b') { + out += "\\b"; + } else if (ch == '\f') { + out += "\\f"; + } else if (ch == '\n') { + out += "\\n"; + } else if (ch == '\r') { + out += "\\r"; + } else if (ch == '\t') { + out += "\\t"; + } else if (static_cast(ch) <= 0x1f) { + char buf[8]; + snprintf(buf, sizeof buf, "\\u%04x", ch); + out += buf; + } else if (static_cast(ch) == 0xe2 && static_cast(value[i+1]) == 0x80 + && static_cast(value[i+2]) == 0xa8) { + out += "\\u2028"; + i += 2; + } else if (static_cast(ch) == 0xe2 && static_cast(value[i+1]) == 0x80 + && static_cast(value[i+2]) == 0xa9) { + out += "\\u2029"; + i += 2; + } else { + out += ch; + } + } + out += '"'; +} + +static void dump(const Json::array &values, string &out) { + bool first = true; + out += "["; + for (const auto &value : values) { + if (!first) + out += ", "; + value.dump(out); + first = false; + } + out += "]"; +} + +static void dump(const Json::object &values, string &out) { + bool first = true; + out += "{"; + for (const auto &kv : values) { + if (!first) + out += ", "; + dump(kv.first, out); + out += ": "; + kv.second.dump(out); + first = false; + } + out += "}"; +} + +void Json::dump(string &out) const { + m_ptr->dump(out); +} + +/* * * * * * * * * * * * * * * * * * * * + * Value wrappers + */ + +template +class Value : public JsonValue { +protected: + + // Constructors + explicit Value(const T &value) : m_value(value) {} + explicit Value(T &&value) : m_value(move(value)) {} + + // Get type tag + Json::Type type() const override { + return tag; + } + + // Comparisons + bool equals(const JsonValue * other) const override { + return m_value == static_cast *>(other)->m_value; + } + bool less(const JsonValue * other) const override { + return m_value < static_cast *>(other)->m_value; + } + + const T m_value; + void dump(string &out) const override { json11::dump(m_value, out); } +}; + +class JsonDouble final : public Value { + double number_value() const override { return m_value; } + int int_value() const override { return static_cast(m_value); } + bool equals(const JsonValue * other) const override { return m_value == other->number_value(); } + bool less(const JsonValue * other) const override { return m_value < other->number_value(); } +public: + explicit JsonDouble(double value) : Value(value) {} +}; + +class JsonInt final : public Value { + double number_value() const override { return m_value; } + int int_value() const override { return m_value; } + bool equals(const JsonValue * other) const override { return m_value == other->number_value(); } + bool less(const JsonValue * other) const override { return m_value < other->number_value(); } +public: + explicit JsonInt(int value) : Value(value) {} +}; + +class JsonBoolean final : public Value { + bool bool_value() const override { return m_value; } +public: + explicit JsonBoolean(bool value) : Value(value) {} +}; + +class JsonString final : public Value { + const string &string_value() const override { return m_value; } +public: + explicit JsonString(const string &value) : Value(value) {} + explicit JsonString(string &&value) : Value(move(value)) {} +}; + +class JsonArray final : public Value { + const Json::array &array_items() const override { return m_value; } + const Json & operator[](size_t i) const override; +public: + explicit JsonArray(const Json::array &value) : Value(value) {} + explicit JsonArray(Json::array &&value) : Value(move(value)) {} +}; + +class JsonObject final : public Value { + const Json::object &object_items() const override { return m_value; } + const Json & operator[](const string &key) const override; +public: + explicit JsonObject(const Json::object &value) : Value(value) {} + explicit JsonObject(Json::object &&value) : Value(move(value)) {} +}; + +class JsonNull final : public Value { +public: + JsonNull() : Value(nullptr) {} +}; + +/* * * * * * * * * * * * * * * * * * * * + * Static globals - static-init-safe + */ +struct Statics { + const std::shared_ptr null = make_shared(); + const std::shared_ptr t = make_shared(true); + const std::shared_ptr f = make_shared(false); + const string empty_string; + const vector empty_vector; + const map empty_map; + Statics() {} +}; + +static const Statics & statics() { + static const Statics s {}; + return s; +} + +static const Json & static_null() { + // This has to be separate, not in Statics, because Json() accesses statics().null. + static const Json json_null; + return json_null; +} + +/* * * * * * * * * * * * * * * * * * * * + * Constructors + */ + +Json::Json() noexcept : m_ptr(statics().null) {} +Json::Json(std::nullptr_t) noexcept : m_ptr(statics().null) {} +Json::Json(double value) : m_ptr(make_shared(value)) {} +Json::Json(int value) : m_ptr(make_shared(value)) {} +Json::Json(bool value) : m_ptr(value ? statics().t : statics().f) {} +Json::Json(const string &value) : m_ptr(make_shared(value)) {} +Json::Json(string &&value) : m_ptr(make_shared(move(value))) {} +Json::Json(const char * value) : m_ptr(make_shared(value)) {} +Json::Json(const Json::array &values) : m_ptr(make_shared(values)) {} +Json::Json(Json::array &&values) : m_ptr(make_shared(move(values))) {} +Json::Json(const Json::object &values) : m_ptr(make_shared(values)) {} +Json::Json(Json::object &&values) : m_ptr(make_shared(move(values))) {} + +/* * * * * * * * * * * * * * * * * * * * + * Accessors + */ + +Json::Type Json::type() const { return m_ptr->type(); } +double Json::number_value() const { return m_ptr->number_value(); } +int Json::int_value() const { return m_ptr->int_value(); } +bool Json::bool_value() const { return m_ptr->bool_value(); } +const string & Json::string_value() const { return m_ptr->string_value(); } +const vector & Json::array_items() const { return m_ptr->array_items(); } +const map & Json::object_items() const { return m_ptr->object_items(); } +const Json & Json::operator[] (size_t i) const { return (*m_ptr)[i]; } +const Json & Json::operator[] (const string &key) const { return (*m_ptr)[key]; } + +double JsonValue::number_value() const { return 0; } +int JsonValue::int_value() const { return 0; } +bool JsonValue::bool_value() const { return false; } +const string & JsonValue::string_value() const { return statics().empty_string; } +const vector & JsonValue::array_items() const { return statics().empty_vector; } +const map & JsonValue::object_items() const { return statics().empty_map; } +const Json & JsonValue::operator[] (size_t) const { return static_null(); } +const Json & JsonValue::operator[] (const string &) const { return static_null(); } + +const Json & JsonObject::operator[] (const string &key) const { + auto iter = m_value.find(key); + return (iter == m_value.end()) ? static_null() : iter->second; +} +const Json & JsonArray::operator[] (size_t i) const { + if (i >= m_value.size()) return static_null(); + else return m_value[i]; +} + +/* * * * * * * * * * * * * * * * * * * * + * Comparison + */ + +bool Json::operator== (const Json &other) const { + if (m_ptr->type() != other.m_ptr->type()) + return false; + + return m_ptr->equals(other.m_ptr.get()); +} + +bool Json::operator< (const Json &other) const { + if (m_ptr->type() != other.m_ptr->type()) + return m_ptr->type() < other.m_ptr->type(); + + return m_ptr->less(other.m_ptr.get()); +} + +/* * * * * * * * * * * * * * * * * * * * + * Parsing + */ + +/* esc(c) + * + * Format char c suitable for printing in an error message. + */ +static inline string esc(char c) { + char buf[12]; + if (static_cast(c) >= 0x20 && static_cast(c) <= 0x7f) { + snprintf(buf, sizeof buf, "'%c' (%d)", c, c); + } else { + snprintf(buf, sizeof buf, "(%d)", c); + } + return string(buf); +} + +static inline bool in_range(long x, long lower, long upper) { + return (x >= lower && x <= upper); +} + +namespace { +/* JsonParser + * + * Object that tracks all state of an in-progress parse. + */ +struct JsonParser final { + + /* State + */ + const string &str; + size_t i; + string &err; + bool failed; + const JsonParse strategy; + + /* fail(msg, err_ret = Json()) + * + * Mark this parse as failed. + */ + Json fail(string &&msg) { + return fail(move(msg), Json()); + } + + template + T fail(string &&msg, const T err_ret) { + if (!failed) + err = std::move(msg); + failed = true; + return err_ret; + } + + /* consume_whitespace() + * + * Advance until the current character is non-whitespace. + */ + void consume_whitespace() { + while (str[i] == ' ' || str[i] == '\r' || str[i] == '\n' || str[i] == '\t') + i++; + } + + /* consume_comment() + * + * Advance comments (c-style inline and multiline). + */ + bool consume_comment() { + bool comment_found = false; + if (str[i] == '/') { + i++; + if (i == str.size()) + return fail("unexpected end of input inside comment", false); + if (str[i] == '/') { // inline comment + i++; + if (i == str.size()) + return fail("unexpected end of input inside inline comment", false); + // advance until next line + while (str[i] != '\n') { + i++; + if (i == str.size()) + return fail("unexpected end of input inside inline comment", false); + } + comment_found = true; + } + else if (str[i] == '*') { // multiline comment + i++; + if (i > str.size()-2) + return fail("unexpected end of input inside multi-line comment", false); + // advance until closing tokens + while (!(str[i] == '*' && str[i+1] == '/')) { + i++; + if (i > str.size()-2) + return fail( + "unexpected end of input inside multi-line comment", false); + } + i += 2; + if (i == str.size()) + return fail( + "unexpected end of input inside multi-line comment", false); + comment_found = true; + } + else + return fail("malformed comment", false); + } + return comment_found; + } + + /* consume_garbage() + * + * Advance until the current character is non-whitespace and non-comment. + */ + void consume_garbage() { + consume_whitespace(); + if(strategy == JsonParse::COMMENTS) { + bool comment_found = false; + do { + comment_found = consume_comment(); + consume_whitespace(); + } + while(comment_found); + } + } + + /* get_next_token() + * + * Return the next non-whitespace character. If the end of the input is reached, + * flag an error and return 0. + */ + char get_next_token() { + consume_garbage(); + if (i == str.size()) + return fail("unexpected end of input", 0); + + return str[i++]; + } + + /* encode_utf8(pt, out) + * + * Encode pt as UTF-8 and add it to out. + */ + void encode_utf8(long pt, string & out) { + if (pt < 0) + return; + + if (pt < 0x80) { + out += static_cast(pt); + } else if (pt < 0x800) { + out += static_cast((pt >> 6) | 0xC0); + out += static_cast((pt & 0x3F) | 0x80); + } else if (pt < 0x10000) { + out += static_cast((pt >> 12) | 0xE0); + out += static_cast(((pt >> 6) & 0x3F) | 0x80); + out += static_cast((pt & 0x3F) | 0x80); + } else { + out += static_cast((pt >> 18) | 0xF0); + out += static_cast(((pt >> 12) & 0x3F) | 0x80); + out += static_cast(((pt >> 6) & 0x3F) | 0x80); + out += static_cast((pt & 0x3F) | 0x80); + } + } + + /* parse_string() + * + * Parse a string, starting at the current position. + */ + string parse_string() { + string out; + long last_escaped_codepoint = -1; + while (true) { + if (i == str.size()) + return fail("unexpected end of input in string", ""); + + char ch = str[i++]; + + if (ch == '"') { + encode_utf8(last_escaped_codepoint, out); + return out; + } + + if (in_range(ch, 0, 0x1f)) + return fail("unescaped " + esc(ch) + " in string", ""); + + // The usual case: non-escaped characters + if (ch != '\\') { + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = -1; + out += ch; + continue; + } + + // Handle escapes + if (i == str.size()) + return fail("unexpected end of input in string", ""); + + ch = str[i++]; + + if (ch == 'u') { + // Extract 4-byte escape sequence + string esc = str.substr(i, 4); + // Explicitly check length of the substring. The following loop + // relies on std::string returning the terminating NUL when + // accessing str[length]. Checking here reduces brittleness. + if (esc.length() < 4) { + return fail("bad \\u escape: " + esc, ""); + } + for (int j = 0; j < 4; j++) { + if (!in_range(esc[j], 'a', 'f') && !in_range(esc[j], 'A', 'F') + && !in_range(esc[j], '0', '9')) + return fail("bad \\u escape: " + esc, ""); + } + + long codepoint = strtol(esc.data(), nullptr, 16); + + // JSON specifies that characters outside the BMP shall be encoded as a pair + // of 4-hex-digit \u escapes encoding their surrogate pair components. Check + // whether we're in the middle of such a beast: the previous codepoint was an + // escaped lead (high) surrogate, and this is a trail (low) surrogate. + if (in_range(last_escaped_codepoint, 0xD800, 0xDBFF) + && in_range(codepoint, 0xDC00, 0xDFFF)) { + // Reassemble the two surrogate pairs into one astral-plane character, per + // the UTF-16 algorithm. + encode_utf8((((last_escaped_codepoint - 0xD800) << 10) + | (codepoint - 0xDC00)) + 0x10000, out); + last_escaped_codepoint = -1; + } else { + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = codepoint; + } + + i += 4; + continue; + } + + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = -1; + + if (ch == 'b') { + out += '\b'; + } else if (ch == 'f') { + out += '\f'; + } else if (ch == 'n') { + out += '\n'; + } else if (ch == 'r') { + out += '\r'; + } else if (ch == 't') { + out += '\t'; + } else if (ch == '"' || ch == '\\' || ch == '/') { + out += ch; + } else { + return fail("invalid escape character " + esc(ch), ""); + } + } + } + + /* parse_number() + * + * Parse a double. + */ + Json parse_number() { + size_t start_pos = i; + + if (str[i] == '-') + i++; + + // Integer part + if (str[i] == '0') { + i++; + if (in_range(str[i], '0', '9')) + return fail("leading 0s not permitted in numbers"); + } else if (in_range(str[i], '1', '9')) { + i++; + while (in_range(str[i], '0', '9')) + i++; + } else { + return fail("invalid " + esc(str[i]) + " in number"); + } + + if (str[i] != '.' && str[i] != 'e' && str[i] != 'E' + && (i - start_pos) <= static_cast(std::numeric_limits::digits10)) { + return std::atoi(str.c_str() + start_pos); + } + + // Decimal part + if (str[i] == '.') { + i++; + if (!in_range(str[i], '0', '9')) + return fail("at least one digit required in fractional part"); + + while (in_range(str[i], '0', '9')) + i++; + } + + // Exponent part + if (str[i] == 'e' || str[i] == 'E') { + i++; + + if (str[i] == '+' || str[i] == '-') + i++; + + if (!in_range(str[i], '0', '9')) + return fail("at least one digit required in exponent"); + + while (in_range(str[i], '0', '9')) + i++; + } + + return std::strtod(str.c_str() + start_pos, nullptr); + } + + /* expect(str, res) + * + * Expect that 'str' starts at the character that was just read. If it does, advance + * the input and return res. If not, flag an error. + */ + Json expect(const string &expected, Json res) { + assert(i != 0); + i--; + if (str.compare(i, expected.length(), expected) == 0) { + i += expected.length(); + return res; + } else { + return fail("parse error: expected " + expected + ", got " + str.substr(i, expected.length())); + } + } + + /* parse_json() + * + * Parse a JSON object. + */ + Json parse_json(int depth) { + if (depth > max_depth) { + return fail("exceeded maximum nesting depth"); + } + + char ch = get_next_token(); + if (failed) + return Json(); + + if (ch == '-' || (ch >= '0' && ch <= '9')) { + i--; + return parse_number(); + } + + if (ch == 't') + return expect("true", true); + + if (ch == 'f') + return expect("false", false); + + if (ch == 'n') + return expect("null", Json()); + + if (ch == '"') + return parse_string(); + + if (ch == '{') { + map data; + ch = get_next_token(); + if (ch == '}') + return data; + + while (1) { + if (ch != '"') + return fail("expected '\"' in object, got " + esc(ch)); + + string key = parse_string(); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch != ':') + return fail("expected ':' in object, got " + esc(ch)); + + data[std::move(key)] = parse_json(depth + 1); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch == '}') + break; + if (ch != ',') + return fail("expected ',' in object, got " + esc(ch)); + + ch = get_next_token(); + } + return data; + } + + if (ch == '[') { + vector data; + ch = get_next_token(); + if (ch == ']') + return data; + + while (1) { + i--; + data.push_back(parse_json(depth + 1)); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch == ']') + break; + if (ch != ',') + return fail("expected ',' in list, got " + esc(ch)); + + ch = get_next_token(); + (void)ch; + } + return data; + } + + return fail("expected value, got " + esc(ch)); + } +}; +}//namespace { + +Json Json::parse(const string &in, string &err, JsonParse strategy) { + JsonParser parser { in, 0, err, false, strategy }; + Json result = parser.parse_json(0); + + // Check for any trailing garbage + parser.consume_garbage(); + if (parser.i != in.size()) + return parser.fail("unexpected trailing " + esc(in[parser.i])); + + return result; +} + +// Documented in json11.hpp +vector Json::parse_multi(const string &in, + std::string::size_type &parser_stop_pos, + string &err, + JsonParse strategy) { + JsonParser parser { in, 0, err, false, strategy }; + parser_stop_pos = 0; + vector json_vec; + while (parser.i != in.size() && !parser.failed) { + json_vec.push_back(parser.parse_json(0)); + // Check for another object + parser.consume_garbage(); + if (!parser.failed) + parser_stop_pos = parser.i; + } + return json_vec; +} + +/* * * * * * * * * * * * * * * * * * * * + * Shape-checking + */ + +bool Json::has_shape(const shape & types, string & err) const { + if (!is_object()) { + err = "expected JSON object, got " + dump(); + return false; + } + + for (auto & item : types) { + if ((*this)[item.first].type() != item.second) { + err = "bad type for " + item.first + " in " + dump(); + return false; + } + } + + return true; +} + +} // namespace json11 diff --git a/deps/json11/json11.hpp b/deps/json11/json11.hpp new file mode 100644 index 000000000..5202ef932 --- /dev/null +++ b/deps/json11/json11.hpp @@ -0,0 +1,232 @@ +/* json11 + * + * json11 is a tiny JSON library for C++11, providing JSON parsing and serialization. + * + * The core object provided by the library is json11::Json. A Json object represents any JSON + * value: null, bool, number (int or double), string (std::string), array (std::vector), or + * object (std::map). + * + * Json objects act like values: they can be assigned, copied, moved, compared for equality or + * order, etc. There are also helper methods Json::dump, to serialize a Json to a string, and + * Json::parse (static) to parse a std::string as a Json object. + * + * Internally, the various types of Json object are represented by the JsonValue class + * hierarchy. + * + * A note on numbers - JSON specifies the syntax of number formatting but not its semantics, + * so some JSON implementations distinguish between integers and floating-point numbers, while + * some don't. In json11, we choose the latter. Because some JSON implementations (namely + * Javascript itself) treat all numbers as the same type, distinguishing the two leads + * to JSON that will be *silently* changed by a round-trip through those implementations. + * Dangerous! To avoid that risk, json11 stores all numbers as double internally, but also + * provides integer helpers. + * + * Fortunately, double-precision IEEE754 ('double') can precisely store any integer in the + * range +/-2^53, which includes every 'int' on most systems. (Timestamps often use int64 + * or long long to avoid the Y2038K problem; a double storing microseconds since some epoch + * will be exact for +/- 275 years.) + */ + +/* Copyright (c) 2013 Dropbox, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#ifdef _MSC_VER + #if _MSC_VER <= 1800 // VS 2013 + #ifndef noexcept + #define noexcept throw() + #endif + + #ifndef snprintf + #define snprintf _snprintf_s + #endif + #endif +#endif + +namespace json11 { + +enum JsonParse { + STANDARD, COMMENTS +}; + +class JsonValue; + +class Json final { +public: + // Types + enum Type { + NUL, NUMBER, BOOL, STRING, ARRAY, OBJECT + }; + + // Array and object typedefs + typedef std::vector array; + typedef std::map object; + + // Constructors for the various types of JSON value. + Json() noexcept; // NUL + Json(std::nullptr_t) noexcept; // NUL + Json(double value); // NUMBER + Json(int value); // NUMBER + Json(bool value); // BOOL + Json(const std::string &value); // STRING + Json(std::string &&value); // STRING + Json(const char * value); // STRING + Json(const array &values); // ARRAY + Json(array &&values); // ARRAY + Json(const object &values); // OBJECT + Json(object &&values); // OBJECT + + // Implicit constructor: anything with a to_json() function. + template + Json(const T & t) : Json(t.to_json()) {} + + // Implicit constructor: map-like objects (std::map, std::unordered_map, etc) + template ::value + && std::is_constructible::value, + int>::type = 0> + Json(const M & m) : Json(object(m.begin(), m.end())) {} + + // Implicit constructor: vector-like objects (std::list, std::vector, std::set, etc) + template ::value, + int>::type = 0> + Json(const V & v) : Json(array(v.begin(), v.end())) {} + + // This prevents Json(some_pointer) from accidentally producing a bool. Use + // Json(bool(some_pointer)) if that behavior is desired. + Json(void *) = delete; + + // Accessors + Type type() const; + + bool is_null() const { return type() == NUL; } + bool is_number() const { return type() == NUMBER; } + bool is_bool() const { return type() == BOOL; } + bool is_string() const { return type() == STRING; } + bool is_array() const { return type() == ARRAY; } + bool is_object() const { return type() == OBJECT; } + + // Return the enclosed value if this is a number, 0 otherwise. Note that json11 does not + // distinguish between integer and non-integer numbers - number_value() and int_value() + // can both be applied to a NUMBER-typed object. + double number_value() const; + int int_value() const; + + // Return the enclosed value if this is a boolean, false otherwise. + bool bool_value() const; + // Return the enclosed string if this is a string, "" otherwise. + const std::string &string_value() const; + // Return the enclosed std::vector if this is an array, or an empty vector otherwise. + const array &array_items() const; + // Return the enclosed std::map if this is an object, or an empty map otherwise. + const object &object_items() const; + + // Return a reference to arr[i] if this is an array, Json() otherwise. + const Json & operator[](size_t i) const; + // Return a reference to obj[key] if this is an object, Json() otherwise. + const Json & operator[](const std::string &key) const; + + // Serialize. + void dump(std::string &out) const; + std::string dump() const { + std::string out; + dump(out); + return out; + } + + // Parse. If parse fails, return Json() and assign an error message to err. + static Json parse(const std::string & in, + std::string & err, + JsonParse strategy = JsonParse::STANDARD); + static Json parse(const char * in, + std::string & err, + JsonParse strategy = JsonParse::STANDARD) { + if (in) { + return parse(std::string(in), err, strategy); + } else { + err = "null input"; + return nullptr; + } + } + // Parse multiple objects, concatenated or separated by whitespace + static std::vector parse_multi( + const std::string & in, + std::string::size_type & parser_stop_pos, + std::string & err, + JsonParse strategy = JsonParse::STANDARD); + + static inline std::vector parse_multi( + const std::string & in, + std::string & err, + JsonParse strategy = JsonParse::STANDARD) { + std::string::size_type parser_stop_pos; + return parse_multi(in, parser_stop_pos, err, strategy); + } + + bool operator== (const Json &rhs) const; + bool operator< (const Json &rhs) const; + bool operator!= (const Json &rhs) const { return !(*this == rhs); } + bool operator<= (const Json &rhs) const { return !(rhs < *this); } + bool operator> (const Json &rhs) const { return (rhs < *this); } + bool operator>= (const Json &rhs) const { return !(*this < rhs); } + + /* has_shape(types, err) + * + * Return true if this is a JSON object and, for each item in types, has a field of + * the given type. If not, return false and set err to a descriptive message. + */ + typedef std::initializer_list> shape; + bool has_shape(const shape & types, std::string & err) const; + +private: + std::shared_ptr m_ptr; +}; + +// Internal class hierarchy - JsonValue objects are not exposed to users of this API. +class JsonValue { +protected: + friend class Json; + friend class JsonInt; + friend class JsonDouble; + virtual Json::Type type() const = 0; + virtual bool equals(const JsonValue * other) const = 0; + virtual bool less(const JsonValue * other) const = 0; + virtual void dump(std::string &out) const = 0; + virtual double number_value() const; + virtual int int_value() const; + virtual bool bool_value() const; + virtual const std::string &string_value() const; + virtual const Json::array &array_items() const; + virtual const Json &operator[](size_t i) const; + virtual const Json::object &object_items() const; + virtual const Json &operator[](const std::string &key) const; + virtual ~JsonValue() {} +}; + +} // namespace json11 diff --git a/deps/json11/json11.pc.in b/deps/json11/json11.pc.in new file mode 100644 index 000000000..de4b6dd73 --- /dev/null +++ b/deps/json11/json11.pc.in @@ -0,0 +1,9 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: @PROJECT_NAME@ +Description: json11 is a tiny JSON library for C++11, providing JSON parsing and serialization. +Version: @PROJECT_VERSION@ +Libs: -L${libdir} -ljson11 +Cflags: -I${includedir} \ No newline at end of file diff --git a/deps/json11/test.cpp b/deps/json11/test.cpp new file mode 100644 index 000000000..cf9a46fcd --- /dev/null +++ b/deps/json11/test.cpp @@ -0,0 +1,285 @@ +/* + * Define JSON11_TEST_CUSTOM_CONFIG to 1 if you want to build this tester into + * your own unit-test framework rather than a stand-alone program. By setting + * The values of the variables included below, you can insert your own custom + * code into this file as it builds, in order to make it into a test case for + * your favorite framework. + */ +#if !JSON11_TEST_CUSTOM_CONFIG +#define JSON11_TEST_CPP_PREFIX_CODE +#define JSON11_TEST_CPP_SUFFIX_CODE +#define JSON11_TEST_STANDALONE_MAIN 1 +#define JSON11_TEST_CASE(name) static void name() +#define JSON11_TEST_ASSERT(b) assert(b) +#ifdef NDEBUG +#undef NDEBUG//at now assert will work even in Release build +#endif +#endif // JSON11_TEST_CUSTOM_CONFIG + +/* + * Enable or disable code which demonstrates the behavior change in Xcode 7 / Clang 3.7, + * introduced by DR1467 and described here: https://llvm.org/bugs/show_bug.cgi?id=23812 + * Defaults to on in order to act as a warning to anyone who's unaware of the issue. + */ +#ifndef JSON11_ENABLE_DR1467_CANARY +#define JSON11_ENABLE_DR1467_CANARY 1 +#endif + +/* + * Beginning of standard source file, which makes use of the customizations above. + */ +#include +#include +#include +#include +#include +#include +#include "json11.hpp" +#include +#include +#include +#include +#include + +// Insert user-defined prefix code (includes, function declarations, etc) +// to set up a custom test suite +JSON11_TEST_CPP_PREFIX_CODE + +using namespace json11; +using std::string; + +// Check that Json has the properties we want. +#define CHECK_TRAIT(x) static_assert(std::x::value, #x) +CHECK_TRAIT(is_nothrow_constructible); +CHECK_TRAIT(is_nothrow_default_constructible); +CHECK_TRAIT(is_copy_constructible); +CHECK_TRAIT(is_nothrow_move_constructible); +CHECK_TRAIT(is_copy_assignable); +CHECK_TRAIT(is_nothrow_move_assignable); +CHECK_TRAIT(is_nothrow_destructible); + +JSON11_TEST_CASE(json11_test) { + const string simple_test = + R"({"k1":"v1", "k2":42, "k3":["a",123,true,false,null]})"; + + string err; + auto json = Json::parse(simple_test, err); + + std::cout << "k1: " << json["k1"].string_value() << "\n"; + std::cout << "k3: " << json["k3"].dump() << "\n"; + + for (auto &k : json["k3"].array_items()) { + std::cout << " - " << k.dump() << "\n"; + } + + const string comment_test = R"({ + // comment /* with nested comment */ + "a": 1, + // comment + // continued + "b": "text", + /* multi + line + comment */ + // and single-line comment + "c": [1, 2, 3] + })"; + + string err_comment; + auto json_comment = Json::parse( + comment_test, err_comment, JsonParse::COMMENTS); + if (!err_comment.empty()) { + printf("Failed: %s\n", err_comment.c_str()); + } else { + printf("Result: %s\n", json_comment.dump().c_str()); + } + + string failing_comment_test = R"({ + /* bad comment + "a": 1, + // another comment to make C parsers which don't understand raw strings happy */ + })"; + + string err_failing_comment; + auto json_failing_comment = Json::parse( + failing_comment_test, err_failing_comment, JsonParse::COMMENTS); + if (!err_failing_comment.empty()) { + printf("Failed: %s\n", err_failing_comment.c_str()); + } else { + printf("Result: %s\n", json_failing_comment.dump().c_str()); + } + + failing_comment_test = R"({ + / / bad comment })"; + + json_failing_comment = Json::parse( + failing_comment_test, err_failing_comment, JsonParse::COMMENTS); + if (!err_failing_comment.empty()) { + printf("Failed: %s\n", err_failing_comment.c_str()); + } else { + printf("Result: %s\n", json_failing_comment.dump().c_str()); + } + + failing_comment_test = R"({// bad comment })"; + + json_failing_comment = Json::parse( + failing_comment_test, err_failing_comment, JsonParse::COMMENTS); + if (!err_failing_comment.empty()) { + printf("Failed: %s\n", err_failing_comment.c_str()); + } else { + printf("Result: %s\n", json_failing_comment.dump().c_str()); + } + + failing_comment_test = R"({ + "a": 1 + }/)"; + + json_failing_comment = Json::parse( + failing_comment_test, err_failing_comment, JsonParse::COMMENTS); + if (!err_failing_comment.empty()) { + printf("Failed: %s\n", err_failing_comment.c_str()); + } else { + printf("Result: %s\n", json_failing_comment.dump().c_str()); + } + + failing_comment_test = R"({/* bad + comment *})"; + + json_failing_comment = Json::parse( + failing_comment_test, err_failing_comment, JsonParse::COMMENTS); + if (!err_failing_comment.empty()) { + printf("Failed: %s\n", err_failing_comment.c_str()); + } else { + printf("Result: %s\n", json_failing_comment.dump().c_str()); + } + + std::list l1 { 1, 2, 3 }; + std::vector l2 { 1, 2, 3 }; + std::set l3 { 1, 2, 3 }; + JSON11_TEST_ASSERT(Json(l1) == Json(l2)); + JSON11_TEST_ASSERT(Json(l2) == Json(l3)); + + std::map m1 { { "k1", "v1" }, { "k2", "v2" } }; + std::unordered_map m2 { { "k1", "v1" }, { "k2", "v2" } }; + JSON11_TEST_ASSERT(Json(m1) == Json(m2)); + + // Json literals + Json obj = Json::object({ + { "k1", "v1" }, + { "k2", 42.0 }, + { "k3", Json::array({ "a", 123.0, true, false, nullptr }) }, + }); + + std::cout << "obj: " << obj.dump() << "\n"; + + JSON11_TEST_ASSERT(Json("a").number_value() == 0); + JSON11_TEST_ASSERT(Json("a").string_value() == "a"); + JSON11_TEST_ASSERT(Json().number_value() == 0); + + JSON11_TEST_ASSERT(obj == json); + JSON11_TEST_ASSERT(Json(42) == Json(42.0)); + JSON11_TEST_ASSERT(Json(42) != Json(42.1)); + + const string unicode_escape_test = + R"([ "blah\ud83d\udca9blah\ud83dblah\udca9blah\u0000blah\u1234" ])"; + + const char utf8[] = "blah" "\xf0\x9f\x92\xa9" "blah" "\xed\xa0\xbd" "blah" + "\xed\xb2\xa9" "blah" "\0" "blah" "\xe1\x88\xb4"; + + Json uni = Json::parse(unicode_escape_test, err); + JSON11_TEST_ASSERT(uni[0].string_value().size() == (sizeof utf8) - 1); + JSON11_TEST_ASSERT(std::memcmp(uni[0].string_value().data(), utf8, sizeof utf8) == 0); + + // Demonstrates the behavior change in Xcode 7 / Clang 3.7, introduced by DR1467 + // and described here: https://llvm.org/bugs/show_bug.cgi?id=23812 + if (JSON11_ENABLE_DR1467_CANARY) { + Json nested_array = Json::array { Json::array { 1, 2, 3 } }; + JSON11_TEST_ASSERT(nested_array.is_array()); + JSON11_TEST_ASSERT(nested_array.array_items().size() == 1); + JSON11_TEST_ASSERT(nested_array.array_items()[0].is_array()); + JSON11_TEST_ASSERT(nested_array.array_items()[0].array_items().size() == 3); + } + + { + const std::string good_json = R"( {"k1" : "v1"})"; + const std::string bad_json1 = good_json + " {"; + const std::string bad_json2 = good_json + R"({"k2":"v2", "k3":[)"; + struct TestMultiParse { + std::string input; + std::string::size_type expect_parser_stop_pos; + size_t expect_not_empty_elms_count; + Json expect_parse_res; + } tests[] = { + {" {", 0, 0, {}}, + {good_json, good_json.size(), 1, Json(std::map{ { "k1", "v1" } })}, + {bad_json1, good_json.size() + 1, 1, Json(std::map{ { "k1", "v1" } })}, + {bad_json2, good_json.size(), 1, Json(std::map{ { "k1", "v1" } })}, + {"{}", 2, 1, Json::object{}}, + }; + for (const auto &tst : tests) { + std::string::size_type parser_stop_pos; + std::string err; + auto res = Json::parse_multi(tst.input, parser_stop_pos, err); + JSON11_TEST_ASSERT(parser_stop_pos == tst.expect_parser_stop_pos); + JSON11_TEST_ASSERT( + (size_t)std::count_if(res.begin(), res.end(), + [](const Json& j) { return !j.is_null(); }) + == tst.expect_not_empty_elms_count); + if (!res.empty()) { + JSON11_TEST_ASSERT(tst.expect_parse_res == res[0]); + } + } + } + Json my_json = Json::object { + { "key1", "value1" }, + { "key2", false }, + { "key3", Json::array { 1, 2, 3 } }, + }; + std::string json_str = my_json.dump(); + printf("%s\n", json_str.c_str()); + + class Point { + public: + int x; + int y; + Point (int x, int y) : x(x), y(y) {} + Json to_json() const { return Json::array { x, y }; } + }; + + std::vector points = { { 1, 2 }, { 10, 20 }, { 100, 200 } }; + std::string points_json = Json(points).dump(); + printf("%s\n", points_json.c_str()); +} + +#if JSON11_TEST_STANDALONE_MAIN + +static void parse_from_stdin() { + string buf; + string line; + while (std::getline(std::cin, line)) { + buf += line + "\n"; + } + + string err; + auto json = Json::parse(buf, err); + if (!err.empty()) { + printf("Failed: %s\n", err.c_str()); + } else { + printf("Result: %s\n", json.dump().c_str()); + } +} + +int main(int argc, char **argv) { + if (argc == 2 && argv[1] == string("--stdin")) { + parse_from_stdin(); + return 0; + } + + json11_test(); +} + +#endif // JSON11_TEST_STANDALONE_MAIN + +// Insert user-defined suffix code (function definitions, etc) +// to set up a custom test suite +JSON11_TEST_CPP_SUFFIX_CODE diff --git a/src/kernel/engines.h b/src/kernel/engines.h index 1bfbaf34e..f32027bb1 100644 --- a/src/kernel/engines.h +++ b/src/kernel/engines.h @@ -23,6 +23,9 @@ #include #include #include +#include +#include +#include namespace rt { @@ -69,10 +72,23 @@ class Engines { v8::V8::SetEntropySource(EntropySource); -#if 0 - const char flags[] = "--expose-wasm"; - v8::V8::SetFlagsFromString(flags, sizeof(flags)); -#endif + InitrdFile runtime_json = GLOBAL_initrd()->Get("/runtime.json"); + if (!runtime_json.IsEmpty()) { + const char* runtime_json_cont = (const char*)runtime_json.Data(); + + std::string err; + json11::Json root = json11::Json::parse(runtime_json_cont, err); + if (err.empty()) { + json11::Json v8 = root["v8"]; + if (!v8.is_null() && v8.is_object()) { + json11::Json flags = v8["flags"]; + if (!flags.is_null() && flags.is_string()) { + const char* flags_str = flags.string_value().c_str(); + v8::V8::SetFlagsFromString(flags_str, strlen(flags_str)); + } + } + } + } v8::V8::InitializePlatform(v8_platform_); v8::V8::Initialize();