Files
avmplus/core/JSONClass.cpp
T
dmcallis 65a0592776 Initial source code drop
Source code drop for the Actionscript virtual machine in the Adobe
Flash Player for December 2013
2013-12-02 16:04:04 -08:00

1830 lines
66 KiB
C++

/* -*- 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) */
/* 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/. */
#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 'm_textUTF8'
uint32_t const m_len; // Length of 'm_textUTF8'
bool m_indexValidForText; // Implies m_i indexes m_text correctly
char m_token; // The current token
String* m_value; // Any value associated with
// the current token (for
// numbers and strings)
};
// The purpose of ReturnCondition is to signal what end-result
// we have ended up in from the stringify call.
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 = avmplus::op_call(m_toplevel, *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 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);
// The purpose of AutoDestructingAtomArray is to build up a
// worklist of property names to traverse during stringify.
// (A hypothetical direct traversal that recursively processes
// the properties easily hits stack overflow problems.)
//
// Note that it allocates the worklist via the given FixedMalloc
// allocator and automatically frees the worklist during destruction;
// thus the stringify kernel must be careful to catch any AS3
// exception from a callback and unroll our stack properly
// before rethrowing the exception.
//
// Note also that it is an anti-pattern to be storing Atoms in
// the worklist (a FixedMalloc-allocated array) in this
// fashion, because the worklist will not be traced by the
// garbage collector. The argument justifying it in this case
// is that all of these atoms are property names that are kept
// alive by the object we are stringifying, but that is not
// a great argument. Also this code would completely break
// in the presence of a copying garbage collector.
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;
};
// The purpose of Rope is to allow the stringify code to emit
// its output incrementally. Since we do not know a priori
// how much output may be associated with a particular input,
// we cannot preallocate the output buffer. So we avoid the
// O(n lg n) time blowup (where n is output size) associated
// with automatically resizing (and recopying) the target
// buffer every time the emission hits the string's capacity
// threshold. Instead we emit to a sequence of fixed length
// buffers, and then after emission is complete, we assemble
// the final output string in one pass, thus maintaining a
// O(n) time bound. (Unlike some other rope structures, this
// uses a list rather than a tree; this works because the
// use-case is quite specialized here.)
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(m_textUTF8.length()))
, m_indexValidForText(true)
, 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();
Atom oa = m_toplevel->objectClass->constructObject();
ScriptObject* o = AvmCore::atomToScriptObject(oa);
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();
}
if (m_indexValidForText)
m_value = m_text->substring(start, m_i);
else
m_value = m_toplevel->core()->newStringUTF8(&m_textUTF8.c_str()[start], m_i - start);
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 >= 128)
m_indexValidForText = false;
if (c == 34) // double-quote
break;
if (c == 92) { // backslash
if (m_indexValidForText)
buffer = buffer->append(m_text->substring(start, m_i));
else
buffer = buffer->append
(m_toplevel->core()->newStringUTF8
(&m_textUTF8.c_str()[start], m_i - start));
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]))
{
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++;
}
if (m_indexValidForText)
buffer = buffer->append(m_text->substring(start, m_i));
else
buffer = buffer->append
(m_toplevel->core()->newStringUTF8
(&m_textUTF8.c_str()[start], m_i - start));
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_exception(NULL)
, 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?
Atom oa = m_toplevel->objectClass->constructObject();
return AvmCore::atomToScriptObject(oa);
}
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::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) ||
#ifdef VMCFG_FLOAT
traits->subtypeof(core()->traits.vectorfloat_itraits) ||
traits->subtypeof(core()->traits.vectorfloat4_itraits) ||
#endif
#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;
}
#ifdef VMCFG_FLOAT
case kSpecialBibopType:
if (value != undefinedAtom)
{
if (AvmCore::isFloat(value))
{
committedToEmitFor(key, pendingPrefix, pendingPropnameColon);
d = AvmCore::atomToFloat(value);
if (m_toplevel->isFinite(NULL, d))
emit(core()->doubleToString(d));
else
emit("null", 4);
return kSomeOutput;
}
else if (AvmCore::isFloat4(value))
{
committedToEmitFor(key, pendingPrefix, pendingPropnameColon);
// OPTIMIZEME: It would be better if this didn't cons a new object with a new
// dynamic property table every time it needed to format a float4.
float4_t f = AvmCore::atomToFloat4(value);
const float* floats = reinterpret_cast<const float*>(&f);
ScriptObject* obj = m_toplevel->builtinClasses()->get_ObjectClass()->construct();
const String *names[] = { core()->kx, core()->ky, core()->kz, core()->kw };
for ( int i=0 ; i < 4 ; i++ )
{
Atom val = m_toplevel->isFinite(NULL, floats[i]) ? core()->floatToAtom(floats[i]) : core()->knull->atom();
obj->setAtomProperty(names[i]->atom(), val);
}
return JO(obj);
}
else
return kNoOutput;
}
#endif
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;
}
}