/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef vm_JSONParser_h #define vm_JSONParser_h #include "mozilla/Assertions.h" // MOZ_ASSERT #include "mozilla/Attributes.h" // MOZ_STACK_CLASS #include "mozilla/Range.h" // mozilla::Range #include "mozilla/RangedPtr.h" // mozilla::RangedPtr #include // size_t #include // uint32_t #include // std::move #include "builtin/ParseRecordObject.h" // js::ParseRecordObject #include "ds/IdValuePair.h" // IdValuePair #include "gc/GC.h" // AutoSelectGCHeap #include "js/GCVector.h" // JS::GCVector #include "js/RootingAPI.h" // JS::Handle, JS::MutableHandle, MutableWrappedPtrOperations #include "js/Value.h" // JS::Value, JS::BooleanValue, JS::NullValue #include "js/Vector.h" // Vector #include "util/StringBuffer.h" // JSStringBuilder #include "vm/StringType.h" // JSString, JSAtom struct JSContext; class JSTracer; namespace js { class FrontendContext; enum class JSONToken { String, Number, True, False, Null, ArrayOpen, ArrayClose, ObjectOpen, ObjectClose, Colon, Comma, OOM, Error }; enum class JSONStringType { PropertyName, LiteralValue }; template class MOZ_STACK_CLASS JSONTokenizer { public: using CharPtr = mozilla::RangedPtr; protected: CharPtr sourceStart; CharPtr current; const CharPtr begin, end; ParserT* parser = nullptr; JSONTokenizer(CharPtr sourceStart, CharPtr current, const CharPtr begin, const CharPtr end, ParserT* parser) : sourceStart(sourceStart), current(current), begin(begin), end(end), parser(parser) { MOZ_ASSERT(current <= end); MOZ_ASSERT(parser); } public: JSONTokenizer(CharPtr current, const CharPtr begin, const CharPtr end, ParserT* parser) : JSONTokenizer(current, current, begin, end, parser) {} explicit JSONTokenizer(mozilla::Range data, ParserT* parser) : JSONTokenizer(data.begin(), data.begin(), data.end(), parser) {} JSONTokenizer(JSONTokenizer&& other) noexcept : JSONTokenizer(other.sourceStart, other.current, other.begin, other.end, other.parser) {} JSONTokenizer(const JSONTokenizer& other) = delete; void operator=(const JSONTokenizer& other) = delete; void fixupParser(ParserT* newParser) { parser = newParser; } void getTextPosition(uint32_t* column, uint32_t* line); bool consumeTrailingWhitespaces(); JSONToken advance(); JSONToken advancePropertyName(); JSONToken advancePropertyColon(); JSONToken advanceAfterProperty(); JSONToken advanceAfterObjectOpen(); JSONToken advanceAfterArrayElement(); void unget() { --current; } #ifdef DEBUG bool finished() { return end == current; } #endif JSONToken token(JSONToken t) { MOZ_ASSERT(t != JSONToken::String); MOZ_ASSERT(t != JSONToken::Number); return t; } template JSONToken stringToken(const CharPtr start, size_t length); template JSONToken stringToken(StringBuilderT& builder); JSONToken numberToken(double d); template JSONToken readString(); JSONToken readNumber(); void error(const char* msg); protected: inline mozilla::Span getSource() const { return mozilla::Span(sourceStart.get(), current.get()); } }; // Possible states the parser can be in between values. enum class JSONParserState { // An array element has just being parsed. FinishArrayElement, // An object property has just been parsed. FinishObjectMember, // At the start of the parse, before any values have been processed. JSONValue }; // Character-type-agnostic base class for JSONFullParseHandler. // JSONParser is templatized to work on either Latin1 // or TwoByte input strings, JSONFullParseHandlerAnyChar holds all state and // methods that can be shared between the two encodings. class MOZ_STACK_CLASS JSONFullParseHandlerAnyChar { public: // State related to the parser's current position. At all points in the // parse this keeps track of the stack of arrays and objects which have // been started but not finished yet. The actual JS object is not // allocated until the literal is closed, so that the result can be sized // according to its contents and have its type and shape filled in using // caches. // State for an array that is currently being parsed. This includes all // elements that have been seen so far. using ElementVector = JS::GCVector; // State for an object that is currently being parsed. This includes all // the key/value pairs that have been seen so far. using PropertyVector = IdValueVector; enum class ParseType { // Parsing a string as if by JSON.parse. JSONParse, // Parsing what may or may not be JSON in a string of eval code. // In this case, a failure to parse indicates either syntax that isn't JSON, // or syntax that has different semantics in eval code than in JSON. AttemptForEval, }; // Stack element for an in progress array or object. struct StackEntry { ElementVector& elements() { MOZ_ASSERT(state == JSONParserState::FinishArrayElement); return *static_cast(vector); } PropertyVector& properties() { MOZ_ASSERT(state == JSONParserState::FinishObjectMember); return *static_cast(vector); } explicit StackEntry(ElementVector* elements) : state(JSONParserState::FinishArrayElement), vector(elements) {} explicit StackEntry(PropertyVector* properties) : state(JSONParserState::FinishObjectMember), vector(properties) {} JSONParserState state; private: void* vector; }; public: /* Data members */ JSContext* cx; JS::Value v; ParseType parseType = ParseType::JSONParse; AutoSelectGCHeap gcHeap; private: // Unused element and property vectors for previous in progress arrays and // objects. These vectors are not freed until the end of the parse to avoid // unnecessary freeing and allocation. Vector freeElements; Vector freeProperties; public: explicit JSONFullParseHandlerAnyChar(JSContext* cx); ~JSONFullParseHandlerAnyChar(); // Allow move construction for use with Rooted. JSONFullParseHandlerAnyChar(JSONFullParseHandlerAnyChar&& other) noexcept; JSONFullParseHandlerAnyChar(const JSONFullParseHandlerAnyChar& other) = delete; void operator=(const JSONFullParseHandlerAnyChar& other) = delete; JSContext* context() { return cx; } JS::Value numberValue() const { MOZ_ASSERT(v.isNumber()); return v; } JS::Value stringValue() const { MOZ_ASSERT(v.isString()); return v; } JSAtom* atomValue() const { JS::Value strval = stringValue(); return &strval.toString()->asAtom(); } inline JS::Value booleanValue(bool value) { return JS::BooleanValue(value); } inline JS::Value nullValue() { return JS::NullValue(); } inline bool objectOpen(Vector& stack, PropertyVector** properties); inline bool objectPropertyName(Vector& stack, bool* isProtoInEval); inline void finishObjectMember(Vector& stack, JS::Handle value, PropertyVector** properties); inline bool finishObject(Vector& stack, JS::MutableHandle vp, PropertyVector* properties); inline bool arrayOpen(Vector& stack, ElementVector** elements); inline bool arrayElement(Vector& stack, JS::Handle value, ElementVector** elements); inline bool finishArray(Vector& stack, JS::MutableHandle vp, ElementVector* elements); inline bool errorReturn() const { return parseType == ParseType::AttemptForEval; } inline bool ignoreError() const { return parseType == ParseType::AttemptForEval; } inline void freeStackEntry(StackEntry& entry); void trace(JSTracer* trc); }; template class MOZ_STACK_CLASS JSONFullParseHandler : public JSONFullParseHandlerAnyChar { using Base = JSONFullParseHandlerAnyChar; using CharPtr = mozilla::RangedPtr; public: using ContextT = JSContext; class StringBuilder { public: JSStringBuilder buffer; explicit StringBuilder(JSContext* cx) : buffer(cx) {} bool append(char16_t c); bool append(const CharT* begin, const CharT* end); }; ParseRecordObject parseRecord; explicit JSONFullParseHandler(JSContext* cx) : Base(cx) {} JSONFullParseHandler(JSONFullParseHandler&& other) noexcept : Base(std::move(other)) {} JSONFullParseHandler(const JSONFullParseHandler& other) = delete; void operator=(const JSONFullParseHandler& other) = delete; template inline bool setStringValue(CharPtr start, size_t length, mozilla::Span&& source); template inline bool setStringValue(StringBuilder& builder, mozilla::Span&& source); inline bool setNumberValue(double d, mozilla::Span&& source); inline bool setBooleanValue(bool value, mozilla::Span&& source); inline bool setNullValue(mozilla::Span&& source); void reportError(const char* msg, uint32_t line, uint32_t column); void trace(JSTracer* trc); protected: inline bool createJSONParseRecord(const Value& value, mozilla::Span& source); }; template class MOZ_STACK_CLASS JSONSyntaxParseHandler { private: using CharPtr = mozilla::RangedPtr; public: /* Types for templatized parser. */ using ContextT = FrontendContext; class DummyValue {}; struct ElementVector {}; struct PropertyVector {}; class StringBuilder { public: explicit StringBuilder(FrontendContext* fc) {} bool append(char16_t c) { return true; } bool append(const CharT* begin, const CharT* end) { return true; } }; struct StackEntry { JSONParserState state; }; public: FrontendContext* fc; /* Public API */ /* Create a parser for the provided JSON data. */ explicit JSONSyntaxParseHandler(FrontendContext* fc) : fc(fc) {} JSONSyntaxParseHandler(JSONSyntaxParseHandler&& other) noexcept : fc(other.fc) {} JSONSyntaxParseHandler(const JSONSyntaxParseHandler& other) = delete; void operator=(const JSONSyntaxParseHandler& other) = delete; FrontendContext* context() { return fc; } template inline bool setStringValue(CharPtr start, size_t length, mozilla::Span&& source) { return true; } template inline bool setStringValue(StringBuilder& builder, mozilla::Span&& source) { return true; } inline bool setNumberValue(double d, mozilla::Span&& source) { return true; } inline bool setBooleanValue(bool value, mozilla::Span&& source) { return true; } inline bool setNullValue(mozilla::Span&& source) { return true; } inline DummyValue numberValue() const { return DummyValue(); } inline DummyValue stringValue() const { return DummyValue(); } inline DummyValue booleanValue(bool value) { return DummyValue(); } inline DummyValue nullValue() { return DummyValue(); } inline bool objectOpen(Vector& stack, PropertyVector** properties); inline bool objectPropertyName(Vector& stack, bool* isProtoInEval) { *isProtoInEval = false; return true; } inline void finishObjectMember(Vector& stack, DummyValue& value, PropertyVector** properties) {} inline bool finishObject(Vector& stack, DummyValue* vp, PropertyVector* properties); inline bool arrayOpen(Vector& stack, ElementVector** elements); inline bool arrayElement(Vector& stack, DummyValue& value, ElementVector** elements) { return true; } inline bool finishArray(Vector& stack, DummyValue* vp, ElementVector* elements); inline bool errorReturn() const { return false; } inline bool ignoreError() const { return false; } inline void freeStackEntry(StackEntry& entry) {} void reportError(const char* msg, uint32_t line, uint32_t column); }; template class MOZ_STACK_CLASS JSONPerHandlerParser { using ContextT = typename HandlerT::ContextT; using Tokenizer = JSONTokenizer, typename HandlerT::StringBuilder>; public: using StringBuilder = typename HandlerT::StringBuilder; public: HandlerT handler; Tokenizer tokenizer; // All in progress arrays and objects being parsed, in order from outermost // to innermost. Vector stack; public: JSONPerHandlerParser(ContextT* context, mozilla::Range data) : handler(context), tokenizer(data, this), stack(context) {} JSONPerHandlerParser(JSONPerHandlerParser&& other) noexcept : handler(std::move(other.handler)), tokenizer(std::move(other.tokenizer)), stack(handler.context()) { tokenizer.fixupParser(this); } ~JSONPerHandlerParser(); JSONPerHandlerParser(const JSONPerHandlerParser& other) = delete; void operator=(const JSONPerHandlerParser& other) = delete; template inline bool parseImpl(TempValueT& value, ResultSetter setResult); void outOfMemory(); void error(const char* msg); }; template class MOZ_STACK_CLASS JSONParser : JSONPerHandlerParser> { using Base = JSONPerHandlerParser>; public: using ParseType = JSONFullParseHandlerAnyChar::ParseType; /* Public API */ /* Create a parser for the provided JSON data. */ JSONParser(JSContext* cx, mozilla::Range data, ParseType parseType) : Base(cx, data) { this->handler.parseType = parseType; } /* Allow move construction for use with Rooted. */ JSONParser(JSONParser&& other) noexcept : Base(std::move(other)) {} JSONParser(const JSONParser& other) = delete; void operator=(const JSONParser& other) = delete; /* * Parse the JSON data specified at construction time. If it parses * successfully, store the prescribed value in *vp and return true. If an * internal error (e.g. OOM) occurs during parsing, return false. * Otherwise, if invalid input was specifed but no internal error occurred, * behavior depends upon the error handling specified at construction: if * error handling is RaiseError then throw a SyntaxError and return false, * otherwise return true and set *vp to |undefined|. (JSON syntax can't * represent |undefined|, so the JSON data couldn't have specified it.) */ bool parse(JS::MutableHandle vp); bool parse(JS::MutableHandle vp, JS::MutableHandle pro); void trace(JSTracer* trc); }; template class MutableWrappedPtrOperations, Wrapper> : public WrappedPtrOperations, Wrapper> { public: bool parse(JS::MutableHandle vp) { return static_cast(this)->get().parse(vp); } bool parse(JS::MutableHandle vp, JS::MutableHandle pro) { return static_cast(this)->get().parse(vp, pro); } }; template class MOZ_STACK_CLASS JSONSyntaxParser : JSONPerHandlerParser> { using HandlerT = JSONSyntaxParseHandler; using Base = JSONPerHandlerParser; public: JSONSyntaxParser(FrontendContext* fc, mozilla::Range data) : Base(fc, data) {} JSONSyntaxParser(JSONSyntaxParser&& other) noexcept : Base(std::move(other)) {} JSONSyntaxParser(const JSONSyntaxParser& other) = delete; void operator=(const JSONSyntaxParser& other) = delete; bool parse(); }; } /* namespace js */ #endif /* vm_JSONParser_h */