core/JSONClass.cpp
author Chris Peyer <cpeyer@adobe.com>
Fri, 29 Apr 2011 15:46:49 -0700
changeset 6247 cbb91ee4ba3573db00e4875b8c19dea0cb0e7e61
parent 6236 bd426b64ca73f3296e41bf5037a8663ad61008a1
child 6275 e8af368caf150d0c88db09368860ad0f57d8a4a2
permissions -rw-r--r--
Bug 608760: Update test to be ATS compatible (r=jsudduth)

/* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*- */
/* vi: set ts=4 sw=4 expandtab: (add to ~/.vimrc: set modeline modelines=5) */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is [Open Source Virtual Machine.].
 *
 * The Initial Developer of the Original Code is
 * Adobe System Incorporated.
 * Portions created by the Initial Developer are Copyright (C) 2004-2011
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Adobe AS3 Team
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include "avmplus.h"
#include "BuiltinNatives.h"
#include "TypeDescriber.h"

namespace avmplus
{
    // The design in this file is deliberately modeled after
    // the AS3 JSON implementation by Lars Hansen attached
    // to Bugzilla 584704:
    // https://bugzilla.mozilla.org/show_bug.cgi?id=584704
    // Subsequent devlopment of the C++ version was discussed
    // in Bugzilla 640711:
    // https://bugzilla.mozilla.org/show_bug.cgi?id=640711

    // Architecture overview: The only class visible outside this file
    // is JSONClass, and it provides a parsing method, parseCore, which
    // reads in strings and produces objects, and a stringification
    // method, stringifySpecializedToString, which reads in objects and
    // produces strings.
    //
    //
    // PARSING
    //
    // JSONClass::parseCore is a simple, hopefully fast, parser.  It
    // delegates its work to the JSONParser class, which defines a
    // set of mutually recursive methods.  The parser is simple
    // for the following reasons:
    //
    // 1. The extra effort of running a optionally provided
    //    reviver callback has been left as a post-pass in the AS3 glue
    //    code.  (For why the reviver must be left as a post-pass, see
    //    discussion in Lars's original code on Bugzilla 584704.)
    // 2. The parser makes no attempt to construct instances of AS3
    //    objects; it closely follows the ECMA-262 5th edition specification
    //    for how parsing behaves.  If the client wants AS3 class instances,
    //    for now they must construct them via the reviver callback.
    // 3. Since the reviver callback is a post-pass, control will not
    //    flow into AS3 code from the parser.
    //
    // The main issues that can arise during parsing are thrown exceptions,
    // from ether invalid parse input, or from internal control stack
    // exhaustion from the recursive parsing routines.  Since the code
    // can throw exceptions, the parser is careful not to allocate
    // unmanaged heap memory.
    //
    // The parser maintains a cursor into the input string, representing
    // the start of the next token to be processed.  When the token
    // is more than a single character, the entire string associated with
    // the token is stored in a String object in the parser.
    //
    // Right now the parser only supports parsing String objects.  We will
    // probably generalize it beyond that, initially to ByteArrays and then
    // to classes providing a proper streaming interface to Unicode data.
    // (The IDataInput API does not currently suffice for this.)
    //
    //
    // STRINGIFICATION
    //
    // JSONClass::stringifySpecializedToString is more complex than the
    // parser.  It delegates its work to the JSONSerializer class.
    // Stringification is more complex because:
    //
    // 1. It must interleave invocations of the replacer function with
    //    its traveral of the object graph; this is dictated by ECMA-262
    //    because the client code can observe the traversal's behavior
    //    via toJSON methods and via the replacer function.
    // 2. The stringifier not only traverses the dynamic own properties of
    //    objects (as dictated by ECMA-262), but also the fixed properties
    //    of class instances (which is not part of ECMA-262).
    // 3. It must detect cycles in the input object graph and signal
    //    an exception in response to them.
    //
    // In addition, the JSONSerializer class has gone through a couple
    // iterations of development, and some of the prototypes would maintain
    // references to unmanaged memory on the C stack as it traversed the
    // input object.  This meant that it could not simply throw an
    // AS3 exception the same way that the Parser does; instead it needed
    // to unwind the stack and free the memory properly.  This led to
    // the use of a pattern of passing back a ReturnCondition from
    // the various Stringification methods.  In hindsight; it may have
    // been better practice to maintain all references to unmanaged memory
    // within the JSONSerializer instance, which would have allowed storage
    // to be freed at the point where the exception is first caught and
    // then immediately rethrow it.
    //
    // The structure of the code is meant to parallel the description
    // of the stringification algorithm described in section 15 of
    // ECMA-262 5th ed.  Some deviations in its structure were
    // introduced to avoid creation of intermediate strings during
    // the process.  (And of course, other code changes were introduced
    // to support AS3-only features such as class instances that are
    // not part of ECMA-262.)  It may be useful for the reader to first
    // review the algorithm described there before diving into the code
    // below.

    class JSONParser
    {
        friend class JSONClass;
        JSONParser(Toplevel* toplevel, String* text);

        // Main entry point.  Parses a single value, and checks that
        // there are no subsequent tokens.  Returns the parsed value;
        // throws on syntax error.
        Atom parseNative();

        // Value parser.
        //
        // Returns the parsed value, which is one of null, Boolean,
        // String, Number/int/uint, Array, Object.
        Atom parseValue();

        // Returns Array for successful parse; throws on failed parse.
        ArrayObject* parseArray();

        // Returns Object for successful parse; throws on failed parse.
        ScriptObject* parseObject();

        // Returns Number for successful parse; throws on failed parse.
        Atom parseFloat(String* v);

        // Scanner.
        // Consumes the next token, stores it in 'token' and 'value'.
        // Throws kJSONInvalidParseInput Syntax Error if and only if
        // next token could not be parsed.
        void advance();
        void adv_number();
        void adv_digits();
        void adv_string();

        wchar hexDigitValue(wchar c);
        bool isHexDigit(wchar c);

        void throwParseInputSyntaxError() {
            m_toplevel->syntaxErrorClass()->throwError(kJSONInvalidParseInput);
        }

    private:
        // Notes:
        // - JSONParser class is solely stack-allocated.
        // - Invoking its methods may cause AS3 exceptions to be thrown
        // - Therefore, its stack-allocated members will not always
        //   have their destructors called.  This is okay, since the
        //   members all point only to GC-allocated memory; in
        //   particular, m_textUTF8.m_buffer is GC-allocated.

        Toplevel*  const m_toplevel;
        String*  const m_text;          // The input string
        StUTF8String m_textUTF8;        // input string as utf8 byte array
        uint32_t m_i;                   // Current index in 'text'
        uint32_t const m_len;           // Length of 'text'
        char m_token;                   // The current token

        String* m_value;                // Any value associated with
                                        // the current token (for
                                        // numbers and strings)
    };

    enum ReturnCondition {
        kSomeOutput,      // normal case; emitted > 0 characters
        kNoOutput,        // emitted *zero* characters
        kCycleTraversed,  // cyclic traversal, prepare to throw exception
        kThrewException   // callback threw an exception; percolating up
    };

#define RET_EXN_CHECK(__x__)                                        \
    do { if ((__x__) >= kCycleTraversed) return (__x__); } while (0)

    class JSONSerializer
    {
        friend class JSONClass;

        JSONSerializer(Toplevel* toplevel,
                       Namespace *as3ns,
                       ArrayObject* proplist,
                       FunctionObject* replacer,
                       String* gap);

        String* stringify(Atom value);
    private:
        // Str(key, holder, pendingPrefix, pendingPropnameColon)
        // If holder[key] post JSON conversion is not undefined,
        // then:
        // - emits pendingPrefix,
        // - emits String(key)+":" iff pendingPropnameColon,
        // - emits JSON-formatted JSON-converted holder[key].
        //
        // The invariant is that pendingPropnameColon will be true
        // when called from JO* methods (i.e. object traversals), and
        // false when called from JA* methods (i.e. array traversals).
        //
        // Returns kSomeOutput if holder[key] post JSON
        // conversion was not undefined (and thus we emitted
        // at least one character to the output).
        //
        // Returns kNoOutput if holder[key] post JSON
        // conversion was undefined.
        //
        // Returns kThrewException if the attempt to stringify threw
        // an exception; bubble this result back up (releasing
        // resources along the way) without emitting further output.

        ReturnCondition Str(String* key, ScriptObject* holder,
                            String* pendingPrefix,
                            bool pendingPropnameColon);
        ReturnCondition Str(uint32_t key, ScriptObject* holder,
                            String* pendingPrefix,
                            bool pendingPropnameColon);
        ReturnCondition Str(Multiname const* key, ScriptObject* holder,
                            String* pendingPrefix,
                            bool pendingPropnameColon);

        // StrFoundValue is called only if holder[key] == val and is
        // not undefined.
        ReturnCondition StrFoundValue(Atom val,
                                      Atom key, // key is String or uint32_t
                                      ScriptObject* holder,
                                      String* pendingPrefix,
                                      bool pendingPropnameColon);

        // Attempts call to method via toplevel.
        // If method throws, returns true.
        // If method returns normally, then sets *recv to result and returns false.
        bool TryToplevelCall(Atom* method,
                             int32_t argc,
                             Atom* args,
                             Atom* recv)
        {
            TRY(core(), kCatchAction_Rethrow)
            {
                *recv = m_toplevel->op_call(*method, argc, args);
                return false;
            }
            CATCH(Exception* exception)
            {
                m_exception = exception;
                return true;
            }
            END_CATCH
            END_TRY
        }

        bool TryAS3Call(FunctionObject* f,
                        Atom* thisAtom,
                        Atom* args,
                        int32_t argc,
                        Atom* recv)
        {
            TRY(core(), kCatchAction_Rethrow)
            {
                *recv = f->AS3_call(*thisAtom, args, argc);
                return false;
            }
            CATCH(Exception* exception)
            {
                m_exception = exception;
                return true;
            }
            END_CATCH
            END_TRY
        }

        void Quote(String* value);
        void QuoteAtomKey(Atom key);

        ReturnCondition JO(ScriptObject* value);
        ReturnCondition JODescriptions(ScriptObject* names,
                                       ScriptObject* value,
                                       String* pendingPre,
                                       String* connective);
        ReturnCondition JOProp(Atom p,
                               ScriptObject* value,
                               String* pendingPrefix);
        ReturnCondition JOPropMultiname(Atom p,
                                        ScriptObject* value,
                                        String* pendingPrefix);

        ReturnCondition JAarray(ArrayObject* value);
        ReturnCondition JAvector(VectorBaseObject* value);
        ReturnCondition JAfinish(ScriptObject* value, uint32_t len);

    private:
        AvmCore* core() const;
        String* emptyString();
        Atom propName(char const* literal);
        ScriptObject* newobject();
        bool willDefinitelyEmitOn(Atom value);
        bool hasTransientMetadata(ScriptObject* description);
        bool stackOverflowCheck();

        // void emit(char const* literal);

        void emit(char ch);
        void emit(char const* buf, uint32_t len);
        void emit(utf8_t const* buf, uint32_t len);
        void emit(String* chars);

        void committedToEmitFor(Atom key, String* pending,
                                bool pendingPropnameColon);

        bool isXMLInstance(Atom atom);
        bool isVectorInstance(ScriptObject* obj);

        // if value is parent of object in current traversal then
        // returns true.  Otherwise marks value as parent of
        // objects in current traversal and returns false.
        bool valueActive(ScriptObject* value);
        // unmarks value (ie not parent of any object in current traversal)
        void valueNotActive(ScriptObject* value);

        struct AutoDestructingAtomArray {
            AutoDestructingAtomArray(MMgc::FixedMalloc* fm, int32_t atomCount)
                : m_atoms(NULL)
                , m_atomCount(atomCount)
                , m_fixedmalloc(fm)
            {
                if (atomCount > 0)
                    m_atoms = (Atom*)fm->Alloc(atomCount*sizeof(Atom));
            }

            ~AutoDestructingAtomArray() {
                if (m_atomCount > 0)
                    m_fixedmalloc->Free(m_atoms);
            }

            Atom*              m_atoms;
            int32_t            m_atomCount;
            MMgc::FixedMalloc* m_fixedmalloc;
        };

        struct Rope
        {
            struct Chunk
            {
                typedef utf8_t bufelem_t;

                // Arbitrarily picking BUF_SIZE such that
                // sizeof(Chunk) is approximately 4096 bytes.
                static const int32_t BUF_SIZE =
                    (4096 - 2*sizeof(intptr_t))/sizeof(bufelem_t);
                // (Alternative BUF_SIZE with small chunks for testing)
                // static const int32_t BUF_SIZE = 16;

                Chunk* m_next;
                int32_t m_cursor;
                bufelem_t m_payload[BUF_SIZE];

                REALLY_INLINE bool exhausted() {
                    AvmAssert(m_cursor <= BUF_SIZE);
                    return m_cursor == BUF_SIZE;
                }

                // amount of space left in m_payload array
                // measured in bufelem_t's.
                REALLY_INLINE int32_t avail() { return BUF_SIZE - m_cursor; }

                REALLY_INLINE int32_t min(int32_t x, int32_t y) { return (x<y)?x:y; }

                // returns amount actually written, measured in bufelem_t's
                REALLY_INLINE int32_t emit(bufelem_t const* src, int32_t len) {
                    int32_t write_amt = min(len, avail());
                    AvmAssert(write_amt > 0);
                    memcpy(&m_payload[m_cursor],
                           src,
                           write_amt*sizeof(bufelem_t));
                    m_cursor += write_amt;
                    return write_amt;
                }
            };

            Chunk* newChunk(Chunk* prev) {
                Chunk* ret = (Chunk*)m_fixedmalloc->Alloc(sizeof(Chunk));
                prev->m_next = ret;
                ret->m_next = NULL;
                ret->m_cursor = 0;
                return ret;
            }

             REALLY_INLINE void emit(char ch) {
                 utf8_t b = ch;
                 this->emit(&b, 1);
             }
             REALLY_INLINE void emit(utf8_t const* buf, int32_t len) {
                 while (len > 0) {
                     int32_t wrote = m_ropeEnd->emit(buf, len);
                     len -= wrote;
                     AvmAssert(len >= 0);
                     buf += wrote;
                     m_len += wrote;
                     if (m_ropeEnd->exhausted()) {
                         Chunk* newchunk = newChunk(m_ropeEnd);
                         m_ropeEnd = newchunk;
                     }
                 }
             }

             String* toStringViaConcat(AvmCore* core) {
                 char* concatenated = concat();
                 int32_t len = length();
                 String* str = core->newStringUTF8(concatenated, len);
                 m_fixedmalloc->Free(concatenated);
                 return str;
             }

#if 0
             // Bugzilla 650918: toStringViaAppend() avoids building
             // intermediate concatenated array; but auto-growing
             // strings suffer O(n lg n) time cost, versus O(n) for
             // toStringViaConcat() above.  If String constructor had
             // parameter for initial capacity, then below could win.
             //
             // (It would need to be vetted for correctness, of course;
             //  in particular, multi-byte codepoints at the edges of
             //  chunks would need to be addressed.)
             String* toStringViaAppend(AvmCore* core) {
                 Chunk* c = m_ropeStart;
                 String* str = core->newStringUTF8(c->m_payload,
                                                   c->m_cursor);
                 c = c->m_next;
                 while (c != NULL) {
                     str = str->appendLatin1(c->m_payload, c->m_cursor);
                     c = c->m_next;
                 }
                 return str;
             }
#endif
             int32_t length() { return m_len; }
             int32_t checkLength() {
                 int32_t ret = 0;
                 Chunk* r = m_ropeStart;
                 while (r != NULL) {
                     ret += r->m_cursor;
                     r = r->m_next;
                 }
                 return ret;
             }

             // it is responsbility of caller to free the returned buffer!
             char* concat() {
                 AvmAssert(checkLength() == m_len);
                 char* dst = (char*)m_fixedmalloc->Alloc(m_len);
                 char* end = dst;
                 Chunk* r = m_ropeStart;
                 while (r != NULL) {
                     memcpy(end, r->m_payload, r->m_cursor);
                     end += r->m_cursor;
                     r = r->m_next;
                 }
                 return dst;
             }

             void freeChunks() {
                 Chunk* r = m_ropeStart;
                 Chunk* next;
                 while (r != NULL) {
                     next = r->m_next;
                     m_fixedmalloc->Free(r);
                     r = next;
                 }
                 m_ropeStart = NULL;
                 m_ropeEnd   = NULL;
             }

             Rope(MMgc::FixedMalloc* fm)
                 : m_fixedmalloc(fm)
                 , m_len(0)
             {
                 Chunk* firstChunk = (Chunk*)fm->Alloc(sizeof(Chunk));
                 firstChunk->m_next = NULL;
                 firstChunk->m_cursor = 0;
                 m_ropeStart = firstChunk;
                 m_ropeEnd   = m_ropeStart;
             }

             Chunk*             m_ropeStart;
             Chunk*             m_ropeEnd;
             MMgc::FixedMalloc* m_fixedmalloc;
             int32_t            m_len;
         };


    private:
        // (Note: JSONSerializer class is solely stack-allocated.)

        Toplevel* const  m_toplevel;
        Namespace* const m_as3ns;
        ArrayObject*     m_proplist; // null implies enumerate all properties
        FunctionObject*  m_replacerFunction; // null implies no replacer
        String* const    m_gap;
        String*          m_indent;

        // (As far as Felix can tell, there is no stack-allocated hash
        // table class.  That is probably not a huge loss, as
        // hash-tables in general must be expandable, and we are
        // trying to get rid of calloc-abuse.)
        HeapHashtable*   m_activeValues;

        // some preallocated strings
        String*          m_str_toJSON;
        String*          m_str_comma;
        String*          m_str_newline;
        String*          m_str_commanewline;

        Exception* m_exception;         // If a callback throws then
                                        // we carry the resulting
                                        // exception here for
                                        // percolation.


        MMgc::FixedMalloc*     m_fixedmalloc;

        // These should *only* be written-to by the emit methods
        // and only read-from by the stringify method.
        Rope m_rope;
    };

    JSONClass::JSONClass(VTable* cvtable)
        : ClassClosure(cvtable)
    {

        // Bug 651971: Lars says ES5 spec requires
        // JSON's [[Prototype]] is Object.prototype.
        // If that's not happening already,
        // probably need to make it so here.

    }

    Atom JSONClass::parseCore(String* text)
    {
        JSONParser parser(vtable->toplevel(), text);
        return parser.parseNative();
    }

    String* JSONClass::stringifySpecializedToString(Atom value,
                                                    ArrayObject* proplist,
                                                    FunctionObject* replacer,
                                                    String* gap)
    {
        Toplevel *toplevel = vtable->toplevel();
        Namespace *as3ns = get_as3ns();
        JSONSerializer serializer(toplevel, as3ns, proplist, replacer, gap);
        return serializer.stringify(value);
    }

    JSONParser::JSONParser(Toplevel* toplevel, String* text)
        : m_toplevel(toplevel)
        , m_text(text)
        , m_textUTF8(text)
        , m_i(0)
        , m_len(uint32_t(text->length()))
        , m_token('\0')
        , m_value(NULL)
    { }

        // If the reviver is null then the parser returns null, Boolean, Number/int/uint, String, Array, or Object.

    Atom JSONParser::parseNative()
    {
        advance();
        Atom atom = parseValue();

        // even if an initial parse succeeds, must confirm that
        // subsequent call to advance found no remaining tokens.
        if (m_token != '\0')
            throwParseInputSyntaxError();

        return atom;
    }

    // Value parser.
    //
    // Returns the parsed value, which is one of null, Boolean,
    // String, Number/int/uint, Array, or Object if parse is
    // successful.
    //
    // If the parse fails, throw kJSONInvalidParseInput Syntax Error
    //
    // Copied below from original AS3 by lhansen:
    // ISSUE: AS3 has a richer notion of number than ECMAScript, and for numbers in the int
    // range we could return an int value rather than a Number value.  There could be a
    // performance impact in some cases.  At present we return a Number value (actually
    // the result of parseFloat).

    Atom JSONParser::parseValue()
    {
        m_toplevel->core()->stackCheck(m_toplevel);

        String* v;
        switch (m_token) {
        case 'n':
            advance();
            return nullObjectAtom;
        case 't':
            advance();
            return trueAtom;
        case 'f':
            advance();
            return falseAtom;
        case '"':
            v = m_value;
            advance();
            return v->toAtom();
        case '0':
            v = m_value;
            advance();
            return parseFloat(v);
        case '{': {
            ScriptObject* obj = parseObject();
            AvmAssert(obj != NULL);
            return obj->toAtom();
        }
        case '[': {
            ArrayObject* arr = parseArray();
            AvmAssert(arr != NULL);
            return arr->toAtom();
        }
        default:
            throwParseInputSyntaxError();
            return undefinedAtom; /*unreachable*/
        }
    }

    Atom JSONParser::parseFloat(String* s)
    {
        AvmAssert(s != NULL);
        double result;

        // Returning kNaN becuase that matches behavior of parseFloat.
        if (!MathUtils::convertStringToDouble(s, &result, false))
            result = MathUtils::kNaN;

        return m_toplevel->core()->doubleToAtom(result);
    }

    ArrayObject* JSONParser::parseArray()
    {
        advance();
        ArrayObject* a = m_toplevel->arrayClass()->newArray();
        if (m_token != ']') {
            for (;;) {
                uint32_t a_len = a->getLength();
                Atom val = parseValue();
                a->setUintProperty(a_len, val);
                if (m_token != ',')
                    break;
                advance();
            }
        }
        if (m_token != ']')
            throwParseInputSyntaxError();
        advance();
        return a;
    }

    ScriptObject* JSONParser::parseObject()
    {
        advance();
        ScriptObject* o =
            m_toplevel->core()->newObject(m_toplevel->object_ivtable,
                                          m_toplevel->objectClass->prototypePtr());
        if (m_token != '}') {
            for (;;) {
                // Could short-cut the string alloc when result is
                // parseIndex-able (e.g. specialized digits parser).
                Atom name = parseValue();

                if (! AvmCore::isString(name))
                    throwParseInputSyntaxError();
                if (m_token != ':')
                    throwParseInputSyntaxError();

                advance();

                Atom val = parseValue();

                // Avoid interning if property name is parseIndex-able.
                uint32_t recv;
                if (m_toplevel->core()->atomToString(name)->parseIndex(recv)) {
                    o->setUintProperty(recv, val);
                } else {
                    name = m_toplevel->core()->intern(name)->atom();
                    o->setAtomProperty(name, val);
                }
                if (m_token != ',')
                    break;
                advance();
            }
        }
        if (m_token != '}')
            throwParseInputSyntaxError();

        advance();
        return o;
    }


        // Scanner.
        // Consumes the next token, stores it in 'token' and 'value'.

    void JSONParser::advance()
    {
        // resetting token to '\0':
        // 1. avoids infinite regress on e.g. JSON.parse("["), and
        // 2. allows parse() to check that entire string has been processed.
        m_token = '\0';

        while (m_i < m_len) {
            char c = m_textUTF8.c_str()[m_i];
            switch (c) {
            case '\t':
            case '\n':
            case '\r':
            case ' ':
                m_i++;
                continue;
            case ',':
            case ':':
            case '{':
            case '}':
            case '[':
            case ']':
                m_i++;
                m_token = c;
                return;
            case '"':
                adv_string();
                return;
            case '-':
            case '.':
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                adv_number();
                return;
            case 'n':
                if (m_i+3 < m_len &&
                    m_textUTF8.c_str()[m_i+1] == 'u' &&
                    m_textUTF8.c_str()[m_i+2] == 'l' &&
                    m_textUTF8.c_str()[m_i+3] == 'l')
                {
                    m_i += 4;
                    m_token = 'n';
                    return;
                } else {
                    throwParseInputSyntaxError();
                }
            case 't':
                if (m_i+3 < m_len &&
                    m_textUTF8.c_str()[m_i+1] == 'r' &&
                    m_textUTF8.c_str()[m_i+2] == 'u' &&
                    m_textUTF8.c_str()[m_i+3] == 'e')
                {
                    m_i += 4;
                    m_token = 't';
                    return;
                } else {
                    throwParseInputSyntaxError();
                }
            case 'f':
                if (m_i+4 < m_len &&
                    m_textUTF8.c_str()[m_i+1] == 'a' &&
                    m_textUTF8.c_str()[m_i+2] == 'l' &&
                    m_textUTF8.c_str()[m_i+3] == 's' &&
                    m_textUTF8.c_str()[m_i+4] == 'e')
                {
                    m_i += 5;
                    m_token = 'f';
                    return;
                } else {
                    throwParseInputSyntaxError();
                }
            default:
                throwParseInputSyntaxError();
            }
        }
    }

    void JSONParser::adv_number() {
        uint32_t start = m_i;
        uint32_t c;

        if (m_textUTF8.c_str()[m_i] == '-')
            m_i++;

        adv_digits();

        if (m_i < m_len && m_textUTF8.c_str()[m_i] == '.') {
            m_i++;
            adv_digits();
        }

        if (m_i < m_len && ((c = m_textUTF8.c_str()[m_i]) == 'e' || c == 'E')) {
            m_i++;
            if (m_i < m_len && ((c = m_textUTF8.c_str()[m_i]) == '-' || c == '+'))
                m_i++;
            adv_digits();
        }

        m_value = m_text->substring(start, m_i);
        m_token = '0';
    }

    void JSONParser::adv_digits()
    {
        uint32_t start = m_i;
        uint32_t c;

        while (m_i < m_len && ((c = m_textUTF8.c_str()[m_i]) >= 48 && c <= 57))
            m_i++;

        if (m_i <= start)
            throwParseInputSyntaxError();
    }

    void JSONParser::adv_string()
    {
        uint32_t start;
        String* buffer = m_toplevel->core()->newStringLatin1("");

        // Consume the initial double-quote
        m_i++;

        start = m_i;
        while (m_i < m_len) {
            uint32_t c = m_textUTF8.c_str()[m_i];
            if (c < 32)
                throwParseInputSyntaxError();
            if (c == 34)    // double-quote
                break;
            if (c == 92)  { // backslash
                buffer = buffer->append(m_text->substring(start,m_i));
                m_i++;
                if (m_i == m_len)
                    throwParseInputSyntaxError();
                switch (c = m_textUTF8.c_str()[m_i]) {
                case  '"': buffer = buffer->appendLatin1("\""); break;
                case  '/': buffer = buffer->appendLatin1("/"); break;
                case '\\': buffer = buffer->appendLatin1("\\"); break;
                case  'b': buffer = buffer->appendLatin1("\b"); break;
                case  'f': buffer = buffer->appendLatin1("\f"); break;
                case  'n': buffer = buffer->appendLatin1("\n"); break;
                case  'r': buffer = buffer->appendLatin1("\r"); break;
                case  't': buffer = buffer->appendLatin1("\t"); break;
                case  'u':
                    m_i++;
                    wchar d1, d2, d3, d4;
                    if (m_i + 3 < m_len &&
                        isHexDigit(d1 = m_textUTF8.c_str()[m_i]) &&
                        isHexDigit(d2 = m_textUTF8.c_str()[m_i+1]) &&
                        isHexDigit(d3 = m_textUTF8.c_str()[m_i+2]) &&
                        isHexDigit(d4 = m_textUTF8.c_str()[m_i+3]) &&
                        (m_i + 3 == m_len ||
                         !isHexDigit(m_textUTF8.c_str()[m_i+4])))
                    {
                        wchar charcode =
                            wchar((hexDigitValue(d1) << 12)
                                  + (hexDigitValue(d2) << 8)
                                  + (hexDigitValue(d3) << 4)
                                  + (hexDigitValue(d4)));

                        // FSK: copied below from AS3_fromCharCode
                        // note: this code is allowed to construct a string
                        // containing illegal UTF16 sequences!
                        //
                        // FSK: I copied the note, but really should
                        // check if the example from AS3_fromCharCode also
                        // applies here, and if so, what does that imply
                        // about the JSON parser?
                        buffer = buffer->append16(&charcode, 1);

                        m_i += 3;
                        break;
                    }
                    else
                    {
                        throwParseInputSyntaxError();
                    }
                default:
                    throwParseInputSyntaxError();
                }
                m_i++;
                start = m_i;
            }
            else
                m_i++;
        }
        buffer = buffer->append(m_text->substring(start, m_i));

        if (m_i == m_len || m_textUTF8.c_str()[m_i] != 34)  // double-quote
            throwParseInputSyntaxError();
        m_i++;

        m_value = buffer;
        m_token = '\"';
    }

    bool JSONParser::isHexDigit(wchar c)
    {
        return (c >= '0' && c <= '9')
            || (c >= 'A' && c <= 'F')
            || (c >= 'a' && c <= 'f');
    }

    // adapted from MathUtils.cpp:parseIntDigit, but restricted to
    // hex; still, the general functionality could probably be
    // usefully lifted out of MathUtils.
    //
    // requires: isHexDigit(c)
    wchar JSONParser::hexDigitValue(wchar ch)
    {
        if (ch >= '0' && ch <= '9') {
            return (ch - '0');
        } else if (ch >= 'a' && ch <= 'f') {
            return (ch - 'a' + 10);
        } else if (ch >= 'A' && ch <= 'F') {
            return (ch - 'A' + 10);
        } else {
            AvmAssert(false); return ch;
        }
    }

    JSONSerializer::JSONSerializer(Toplevel* toplevel,
                                   Namespace *as3ns,
                                   ArrayObject* proplist,
                                   FunctionObject* replacer,
                                   String* gap)
        : m_toplevel(toplevel)
        , m_as3ns(as3ns)
        , m_proplist(proplist)
        , m_replacerFunction(replacer)
        , m_gap(gap)
        , m_fixedmalloc(MMgc::FixedMalloc::GetFixedMalloc())
        , m_rope(m_fixedmalloc)
    {
        m_indent = emptyString();
        m_activeValues = HeapHashtable::create(core()->GetGC());

        m_str_toJSON   = core()->internStringLatin1("toJSON");
        m_str_comma           = core()->newStringLatin1(",");
        m_str_newline         = core()->newStringLatin1("\n");
        m_str_commanewline    = core()->newStringLatin1(",\n");
    }

    REALLY_INLINE AvmCore* JSONSerializer::core() const
    {
        return m_toplevel->core();
    }

    REALLY_INLINE String* JSONSerializer::emptyString()
    {
        return core()->kEmptyString;
    }

    REALLY_INLINE Atom JSONSerializer::propName(char const* literal)
    {
        return core()->internStringLatin1(literal)->atom();
    }

    REALLY_INLINE ScriptObject* JSONSerializer::newobject()
    {
        // Would it be worthwhile to pass hashtable htCapacity param below?
        return ScriptObject::create(core()->GetGC(),
                                    m_toplevel->object_ivtable,
                                    m_toplevel->objectClass->prototypePtr());
    }

    REALLY_INLINE void JSONSerializer::emit(char c)
    {
        m_rope.emit(c);
    }

    REALLY_INLINE void JSONSerializer::emit(char const* buf, uint32_t len)
    {
        m_rope.emit((utf8_t const*)buf, len);
    }

    REALLY_INLINE void JSONSerializer::emit(utf8_t const* buf, uint32_t len)
    {
        m_rope.emit(buf, len);
    }

    REALLY_INLINE void JSONSerializer::emit(String* chars)
    {
        StUTF8String charsUTF8(chars);
        int32_t len = charsUTF8.length();
        m_rope.emit((const utf8_t*)charsUTF8.c_str(), len);
    }

    REALLY_INLINE void JSONSerializer::QuoteAtomKey(Atom key)
    {
        // keys can only be uint32_t or String.
        switch (atomKind(key)) {
        case kStringType:
            Quote(core()->atomToString(key));
            return;
        default:
            AvmAssert(atomKind(key) == kIntptrType);
            AvmAssert(atomCanBeUint32(key));
            Quote(core()->uintToString(uint32_t(atomGetIntptr(key))));
            return;
        }
    }

    REALLY_INLINE void JSONSerializer::committedToEmitFor(Atom key,
                                                          String* pending,
                                                          bool pendingPropnameColon)
    {
        emit(pending);
        if (pendingPropnameColon) {
            QuoteAtomKey(key);
            emit(':');
            if (!m_gap->isEmpty()) {
                // Lars has note in his reference implementation about
                // missing emph around "space" in ECMA-262 spec.
                // Felix has checked with ECMA committee, it was a typo.
                emit(' ');
            }
        }
    }

    bool JSONSerializer::valueActive(ScriptObject* value)
    {
        AvmAssert(value != NULL);
        Atom atom = value->atom();

        if (m_activeValues->contains(atom)) {
            return true;
        } else {
            m_activeValues->add(atom, trueAtom);
            return false;
        }
    }

    void JSONSerializer::valueNotActive(ScriptObject* value)
    {
        AvmAssert(value != NULL);
        Atom atom = value->atom();
        AvmAssert(m_activeValues->contains(atom));
        m_activeValues->remove(atom);
    }

    bool JSONSerializer::isXMLInstance(Atom atom)
    {
        return m_toplevel->builtinClasses()->get_XMLClass()->isType(atom) ||
            m_toplevel->builtinClasses()->get_XMLListClass()->isType(atom);
    }

    bool JSONSerializer::isVectorInstance(ScriptObject* o)
    {
        Traits* traits = o->vtable->traits;

        // Bug 652129:
        // A request for comment from others:
        //
        // Consider the AS3 code
        //   var vb : Vector.<Boolean> = new Vector.<Boolean>(5, true);
        // and let T_vb be its traits, ie. T_vb = vb_obj->vtable->traits
        //
        // For some reason,
        // in AS3:
        //   (vb is Vector)                     ==> false
        //   (vb is Vector.<*>)                 ==> true
        //   (vb is Vector.<Object>)            ==> false
        // in C++:
        //   T_vb->subtypeof(vector_itraits)    ==> false
        //   T_vb->subtypeof(vectorobj_itraits) ==> true
        //
        // Why is this?  Is it because Vector.<*> in AS3 corresponds
        // to vectorobj_itraits in C++?  Where is this documented?
        //
        // In any case, it seems from actual runs that to cover all of
        // the cases I need to do a subtypeof check for all the
        // variants of Vector.<>; (this is absolutely fine with me;
        // there is no reason for the variants of a parametric type to
        // share a common supertype.)
        //
        // But I would like to better understand what vector_itraits
        // is meant to denote.  From PoolObject.cpp, I infer that
        // vector_itraits might represent the type constructor Vector
        // itself, which is not a type (ie 'not of kind *' in PL
        // speak) but rather a type constructor (ie 'of kind * -> *')
        // If that is true, then I should not include vector_itraits
        // in the conditions below.  But it also might not matter
        // if one cannot actually construct an object whose traits
        // would ever say true for subtypeof(vector_itraits).
        return
            (traits->subtypeof(core()->traits.vectorobj_itraits)    ||
             traits->subtypeof(core()->traits.vectordouble_itraits) ||
             traits->subtypeof(core()->traits.vectorint_itraits)    ||
             traits->subtypeof(core()->traits.vectoruint_itraits)   ||
#if 0
             traits->subtypeof(core()->traits.vector_itraits)       ||
#endif
             false);
    }

    String* JSONSerializer::stringify(Atom value)
    {
        ScriptObject* wrapper = newobject();
        wrapper->setAtomProperty(emptyString()->atom(), value);
        ReturnCondition ret;
        ret = Str(emptyString(), wrapper, emptyString(), false);

        String* str;
        Exception* exn;

        switch (ret) {
        case kSomeOutput:
            str = m_rope.toStringViaConcat(core());
            m_rope.freeChunks();
            return str;
        case kThrewException:
            exn = m_exception;
            m_rope.freeChunks();
            core()->throwException(exn);
        case kCycleTraversed:
            m_rope.freeChunks();
            m_toplevel->typeErrorClass()->throwError(1129);
        case kNoOutput:
            m_rope.freeChunks();
        }
        return NULL;
    }

    ReturnCondition JSONSerializer::Str(uint32_t key,
                                        ScriptObject* holder,
                                        String* pendingPrefix,
                                        bool pendingPropnameColon)
    {
        Atom value;
        value = holder->getUintProperty(key);
        return StrFoundValue(value, core()->uintToAtom(key), holder,
                             pendingPrefix, pendingPropnameColon);
    }

    ReturnCondition JSONSerializer::Str(String* key,
                                        ScriptObject* holder,
                                        String* pendingPrefix,
                                        bool pendingPropnameColon)
    {
        Atom value;
        value = holder->getAtomProperty(key->atom());
        return StrFoundValue(value, key->atom(), holder,
                             pendingPrefix, pendingPropnameColon);
    }

    ReturnCondition JSONSerializer::Str(Multiname const* name,
                                        ScriptObject* holder,
                                        String* pendingPrefix,
                                        bool pendingPropnameColon)
    {
        // Atom value = holder->getMultinameProperty(name);
        Atom holderAtom = holder->atom();
        VTable* vtable = m_toplevel->toVTable(holderAtom);
        Atom value = m_toplevel->getproperty(holderAtom, name, vtable);

        return StrFoundValue(value, name->getName()->atom(), holder,
                             pendingPrefix, pendingPropnameColon);
    }

    bool JSONSerializer::stackOverflowCheck()
    {
        TRY(core(), kCatchAction_Rethrow)
        {
            core()->stackCheck(m_toplevel);
            return false;
        }
        CATCH(Exception* exception)
        {
            m_exception = exception;
            return true;
        }
        END_CATCH
        END_TRY
    }

    ReturnCondition JSONSerializer::StrFoundValue(Atom value,
                                                  Atom key,
                                                  ScriptObject* holder,
                                                  String* pendingPrefix,
                                                  bool pendingPropnameColon)
    {
        if (stackOverflowCheck())
            return kThrewException;

        if (!AvmCore::isNullOrUndefined(value)) {
            Atom probe = nullObjectAtom;
            VTable* vtable = m_toplevel->toVTable(value);

            // hasproperty tells us if toJSON is present even if it is a
            // method of an AS3 class; such a toJSON method does need to
            // be public to be visible to hasproperty.
            //
            // We cannot apply this simple pattern to everywhere else in
            // this file, though, because the name passed to hasproperty
            // must be a multiname, and the numeric indices that the JA
            // methods pass as names should be handled by the
            // getUintProperty method instead of the Toplevel hasproperty.

            // Bug 652200: can't use getprop to lookup the (potentially
            // overridden) toJSON property on XML objects directly,
            // because getprop has special behavior for public names on
            // XML objects.  We resolve this by adding another level of
            // indirection: JSON will lookup toJSON in the AS3 namespace
            // first, and the public namespace second.

            Multiname name;
            name.setNamespace(core()->findPublicNamespace());
            name.setName(m_str_toJSON);

            Multiname as3name;
            as3name.setNamespace(m_as3ns);
            as3name.setName(m_str_toJSON);

            if (m_toplevel->hasproperty(value, &as3name, vtable))
            {
                probe = m_toplevel->getproperty(value, &as3name, vtable);
            }
            else if (m_toplevel->hasproperty(value, &name, vtable))
            {
                probe = m_toplevel->getproperty(value, &name, vtable);
            }

            // toJSON dispatch only when probe is Callable value.
            if (core()->isFunction(probe)) {
                // toJSON takes only strings for key, regardless of what
                // form the properties take internally.
                if (atomIsIntptr(key)) {
                    AvmAssert(atomCanBeUint32(key));
                    uint32_t uintKey = uint32_t(atomGetIntptr(key));
                    key = core()->uintToString(uintKey)->atom();
                }

                Atom args[2] = { value, key };
                if (TryToplevelCall(&probe, 1, args, &value))
                    return kThrewException;
            }
        }

        if (m_replacerFunction != NULL) {
            const int argc = 2;

            // replacer takes only strings for key, regardless of what
            // form the properties take internally.
            if (atomIsIntptr(key)) {
                AvmAssert(atomCanBeUint32(key));
                uint32_t uintKey = uint32_t(atomGetIntptr(key));
                key = core()->uintToString(uintKey)->atom();
            }

            Atom holderAtom = holder->atom();
            Atom args[argc] = { key, value };
            if (TryAS3Call(m_replacerFunction, &holderAtom, args, argc, &value))
                return kThrewException;
        }

        // Note: potential AS3 callbacks are complete for the
        // remainder of this method when invoked from
        // JSON.stringify().  (Of course this is not true for
        // JSON.stringifyTo(), where every emit() is a callback into
        // an IDataOutput method)

        // Now emit the JSON string for the value.

        if (core()->isNull(value)) {
            committedToEmitFor(key, pendingPrefix, pendingPropnameColon);
            emit("null", 4);
            return kSomeOutput;
        }

        ScriptObject* obj;
        ArrayObject* array;
        double d;
        switch (atomKind(value)) {
        case kBooleanType:
            committedToEmitFor(key, pendingPrefix, pendingPropnameColon);
            if (value == trueAtom)
                emit("true", 4);
            else
                emit("false", 5);
            return kSomeOutput;
        case kStringType:
            committedToEmitFor(key, pendingPrefix, pendingPropnameColon);
            Quote(core()->atomToString(value));
            return kSomeOutput;
        case kIntptrType:
        case kDoubleType:
            committedToEmitFor(key, pendingPrefix, pendingPropnameColon);
            d = core()->number(value);
            if (m_toplevel->isFinite(NULL, d))
                emit(core()->doubleToString(d));
            else
                emit("null", 4);
            return kSomeOutput;
        case kObjectType:
            obj = AvmCore::atomToScriptObject(value);
            if (!core()->isFunction(value)) {
                committedToEmitFor(key, pendingPrefix, pendingPropnameColon);

                array = obj->toArrayObject();
                if (array != NULL)
                    return JAarray(array);
                if (isVectorInstance(obj)) {
                    // Bug 652129:
                    // Is there another option besides the cast below?
                    VectorBaseObject* vobj = (VectorBaseObject*)obj;
                    return JAvector(vobj);
                }

                return JO(obj);

            } else {
                return kNoOutput;
            }
        default:
            return kNoOutput;
        }
    }

    void JSONSerializer::Quote(String* value)
    {
        AvmAssert(value != NULL);
        uint32_t i = 0;

        StUTF8String valueUTF8(value);
        const utf8_t* value_src = (const utf8_t*) valueUTF8.c_str();

        uint32_t len = valueUTF8.length();
        uint32_t start;

        char int_conv_buf[8];
        char* ptr;
        int32_t buflen;

        emit('"');

        start = i;
        while (i < len) {
            utf8_t c = value_src[i];
            if (c >= 32 && c != 34 && c != 92) {
                i++;
                continue;
            }

            emit(&value_src[start], i-start);
            i++;
            start = i;

            emit('\\');
            switch (c) {
            case '\"': emit('\"'); break;
            case '\\': emit('\\'); break;
            case '\b': emit('b'); break;
            case '\t': emit('t'); break;
            case '\n': emit('n'); break;
            case '\f': emit('f'); break;
            case '\r': emit('r'); break;
            default: // control character => \u####
                buflen = sizeof(int_conv_buf);
                ptr =
                    MathUtils::
                    convertIntegerToStringBuffer(c+0x10000,
                                                 (char*)int_conv_buf,
                                                 buflen,
                                                 16,
                                                 MathUtils::kTreatAsUnsigned);

                emit('u');
                emit(ptr+1, 4);
                break;
            }
        }

        emit(&value_src[start], i-start);
        emit('\"');
    }

    bool JSONSerializer::hasTransientMetadata(ScriptObject* description)
    {
        Atom metadata =
            description->getAtomProperty(propName("metadata"));
        if (AvmCore::isNullOrUndefined(metadata))
            return false;
        ScriptObject* obj = AvmCore::atomToScriptObject(metadata);
        ArrayObject* array = obj->toArrayObject();
        if (array != NULL) {
            uint32_t len = array->getLength();
            for (uint32_t i=0; i < len; i++) {
                Atom entryAtom = array->getUintProperty(i);
                ScriptObject* entry =
                    AvmCore::atomToScriptObject(entryAtom);
                Atom entryNameAtom =
                    entry->getAtomProperty(propName("name"));
                if (AvmCore::isString(entryNameAtom)) {
                    String* entryNameString =
                        core()->atomToString(entryNameAtom);
                    if (entryNameString->equalsLatin1("Transient"))
                        return true;
                }
            }
        }
        return false;
    }

    ReturnCondition JSONSerializer::JODescriptions(ScriptObject* descs,
                                                   ScriptObject* value,
                                                   String* pendingPre,
                                                   String* connective)
    {
        int32_t index;

        bool emitted = false;

        if (descs != NULL) {

            for (index = descs->nextNameIndex(0);
                 index != 0;
                 index = descs->nextNameIndex(index))
            {
                Atom nextValue = descs->nextValue(index);
                ScriptObject* description =
                    AvmCore::atomToScriptObject(nextValue);
                Atom access =
                    description->getAtomProperty(propName("access"));
                if (core()->atomToString(access)->charAt(0) == 'r') {
                    if (hasTransientMetadata(description))
                        continue;
                    Atom name =
                        description->getAtomProperty(propName("name"));

                    ReturnCondition ret;
                    ret = JOPropMultiname(name, value, pendingPre);
                    RET_EXN_CHECK(ret);
                    if (ret == kSomeOutput) {
                        pendingPre = connective;
                        emitted = true;
                    }
                }
            }
        }
        if (emitted)
            return kSomeOutput;
        else
            return kNoOutput;
    }

    ReturnCondition JSONSerializer::JO(ScriptObject* value)
    {
        if (valueActive(value))
            return kCycleTraversed;

        String* stepback = m_indent;
        m_indent = m_indent->append(m_gap);

        String* pendingPre;
        String* connective;
        String* pendingPost;

        if (m_gap->isEmpty()) {
            pendingPre  = emptyString();   // ""
            connective  = m_str_comma;     // ","
            pendingPost = emptyString();   // ""
        } else {
            pendingPre  = m_str_newline->append(m_indent);
            connective  = m_str_commanewline->append(m_indent);
            pendingPost = m_str_newline->append(stepback);
        }

        emit('{');

        bool emittedSomething = false;

    if (m_proplist != NULL) {
        uint32_t len = m_proplist->getLength();
        for (uint32_t i = 0; i < len; i++) {
            Atom userprop = m_proplist->getUintProperty(i);
            ReturnCondition ret;

            // Cannot always use JOProp, because that would not cover
            // cases where user explicitly listed strings that
            // correspond to AS3 field names.
            //
            // Cannot always use JOPropMultiname, because that would
            // not cover cases where user provided a number instead of
            // a name.
            //
            // So dispatch on type of user provided property name.

            if (AvmCore::isString(userprop)) {
                ret = JOPropMultiname(userprop, value, pendingPre);
                RET_EXN_CHECK(ret);
            } else {
                ret = JOProp(userprop, value, pendingPre);
                RET_EXN_CHECK(ret);
            }

            if (ret == kSomeOutput) {
                pendingPre = connective;
                emittedSomething = true;
            }
        }
    } else {
        // We were not provided with a property list via the
        // replacer parameter; therefore we are obligated by
        // ECMA-262 to build up an internal copy of the own
        // propery names for value.

        // Bug 652117: nextNameIndex and nextName need better
        // documentation, both in headers and in avm2overview.

        int32_t ownDynPropCount = 0;
        int32_t index;

        index = value->nextNameIndex(0);
        while (index != 0) {
            ownDynPropCount++;
            index = value->nextNameIndex(index);
        }

        AutoDestructingAtomArray propNames(m_fixedmalloc, ownDynPropCount);

        index = value->nextNameIndex(0);
        int32_t propNamesIdx = 0;
        while (index != 0) {
            Atom name = value->nextName(index);
            propNames.m_atoms[propNamesIdx] = name;
            propNamesIdx++;
            index = value->nextNameIndex(index);
        }
        AvmAssert(propNamesIdx == ownDynPropCount);

        // In any case, if our object has Traits, then it is an
        // instance of an AS3 class, and we want to enumerate its
        // public non-method properties first.

        // Bug 652116: support for this traversal could be provided by
        // TypeDescriber, rather than allocating the intermediate
        // ScriptObject dictated by TypeDescriber's public interface.

        TypeDescriber typeDescriber(m_toplevel);
        uint32_t flags =
            (TypeDescriber::HIDE_OBJECT |
             TypeDescriber::INCLUDE_VARIABLES |
             TypeDescriber::INCLUDE_ACCESSORS |
             TypeDescriber::INCLUDE_TRAITS |
             TypeDescriber::INCLUDE_BASES |
             TypeDescriber::INCLUDE_METADATA );
        ScriptObject* valueDescription =
            typeDescriber.describeType(value->atom(), flags);

        if (valueDescription != NULL) {
            ScriptObject* traitsDescription =
                AvmCore::atomToScriptObject(
                    valueDescription->getAtomProperty(propName("traits")));

            ReturnCondition ret;

            // loops over descriptions (analogous to DescribeType.as)

            ScriptObject* varsDescriptions =
                AvmCore::atomToScriptObject(
                    traitsDescription->getAtomProperty(propName("variables")));
            ret = JODescriptions(varsDescriptions, value,
                                 pendingPre, connective);
            RET_EXN_CHECK(ret);
            if (ret == kSomeOutput) {
                pendingPre = connective;
                emittedSomething = true;
            }

            ScriptObject* gettersDescriptions =
                AvmCore::atomToScriptObject(
                    traitsDescription->getAtomProperty(propName("accessors")));
            ret = JODescriptions(gettersDescriptions, value,
                                 pendingPre, connective);
            RET_EXN_CHECK(ret);
            if (ret == kSomeOutput) {
                pendingPre = connective;
                emittedSomething = true;
            }
        }

        for (propNamesIdx = 0; propNamesIdx < ownDynPropCount; propNamesIdx++) {

            Atom name = propNames.m_atoms[propNamesIdx];

            // Is the getAtomPropertyIsEnumerable test necessary
            // (and if it is, is there potential for changing how
            // object nextNameIndex works)?  And what really is
            // the contract of getAtomPropertyIsEnumerable;
            // e.g. if it requires a string, does that imply that
            // non-string indices are always enumerable?
            if (!AvmCore::isString(name) ||
                value->getAtomPropertyIsEnumerable(name)) {
                ReturnCondition ret;
                ret = JOProp(name, value, pendingPre);
                RET_EXN_CHECK(ret);
                if (ret == kSomeOutput) {
                    pendingPre = connective;
                    emittedSomething = true;
                }
            }
        }
    }

        if (emittedSomething)
            emit(pendingPost);

        emit('}');

        valueNotActive(value);

        m_indent = stepback;

        return kSomeOutput;
    }

    ReturnCondition JSONSerializer::JOProp(Atom p,
                                           ScriptObject* value,
                                           String* pendingPrefix)
    {
        String* pName;
        ReturnCondition ret;
        if (AvmCore::isString(p)) {
            pName = core()->atomToString(p);
            ret = Str(pName, value, pendingPrefix, true);
            RET_EXN_CHECK(ret);
        } else if (atomIsIntptr(p) && atomCanBeUint32(p)) {
            uint32_t Puint = uint32_t(atomGetIntptr(p));
            pName = core()->uintToString(Puint);
            ret = Str(pName, value, pendingPrefix, true);
            RET_EXN_CHECK(ret);
        } else {
            // This case would arise if we inadvertantly attempt to loop
            // over the properties of a Dictionary object.  Only sane
            // options would be to throw an exception, convert to a
            // string, or discard by treated as an undefined result.
            // I am choosing the latter and returning kNoOutput.
            // (Current plan is to put an sophisticated handling of a
            //  Dictionary into its toJSON method, so users should
            //  not encounter this anyway.)
            // To test this case, Felix is currently guessing that the
            // best option is to remove Dictionary's toJSON method.
            ret = kNoOutput;
        }

        return ret;
    }

    ReturnCondition JSONSerializer::JOPropMultiname(Atom p,
                                                    ScriptObject* value,
                                                    String* pendingPrefix)
    {
        AvmAssert(AvmCore::isString(p));
        Multiname name;
        name.setNamespace(core()->findPublicNamespace());
        name.setName(core()->intern(p));
        ReturnCondition ret;
        ret = Str(&name, value, pendingPrefix, true);
        RET_EXN_CHECK(ret);
        return ret;
    }

    ReturnCondition JSONSerializer::JAarray(ArrayObject* value)
    {
        uint32_t len = value->getLength();
        return JAfinish(value, len);
    }

    ReturnCondition JSONSerializer::JAvector(VectorBaseObject* value)
    {
        uint32_t len = value->getLength();
        return JAfinish(value, len);
    }

    ReturnCondition JSONSerializer::JAfinish(ScriptObject* value, uint32_t len)
    {
        if (valueActive(value))
            return kCycleTraversed;

        String* stepback = m_indent;
        m_indent = m_indent->append(m_gap);

        String* pendingPre;
        String* connective;
        String* pendingPost;

        if (m_gap->isEmpty()) {
            pendingPre = emptyString();       // ""
            connective = m_str_comma;         // ","
            pendingPost = emptyString();      // ""
        } else {
            pendingPre  = m_str_newline->append(m_indent);
            connective  = m_str_commanewline->append(m_indent);
            pendingPost = m_str_newline->append(stepback);
        }

        emit('[');

        bool emitted = false;
        for (uint32_t index = 0; index < len; index++) {
            ReturnCondition justEmitted = Str(index, value, pendingPre, false);
            switch (justEmitted) {
            case kSomeOutput:
                pendingPre = connective;
                emitted = true;
                break;
            case kNoOutput:
                // Did not emit in the Str invocation above, but will
                // emit now; ensure pending string emitted first.
                emit(pendingPre);
                emit("null", 4);
                pendingPre = connective;
                emitted = true;
                break;
            case kCycleTraversed:
            case kThrewException:
                // percolate error cases up.
                return justEmitted;
            }
        }
        if (emitted)
            emit(pendingPost);

        emit(']');

        valueNotActive(value);

        m_indent = stepback;

        return kSomeOutput;
    }
}