From e50ee08ad66410d65d442f3f8bc772c6b29a80a1 Mon Sep 17 00:00:00 2001 From: "yangguo@chromium.org" Date: Mon, 22 Oct 2012 14:22:58 +0000 Subject: [PATCH] Reland JSON.stringify reimplementation. BUG= Review URL: https://chromiumcodereview.appspot.com/11189112 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@12790 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/json-stringifier.h | 659 ++++++++++++++++++++++ src/json.js | 4 +- src/runtime.cc | 9 + src/runtime.h | 1 + test/mjsunit/json-recursive.js | 44 ++ test/mjsunit/json.js | 36 ++ test/mjsunit/regress/regress-json-stringify-gc.js | 41 ++ 7 files changed, 793 insertions(+), 1 deletion(-) create mode 100644 src/json-stringifier.h create mode 100644 test/mjsunit/json-recursive.js create mode 100644 test/mjsunit/regress/regress-json-stringify-gc.js diff --git a/src/json-stringifier.h b/src/json-stringifier.h new file mode 100644 index 0000000..cd3053a --- /dev/null +++ b/src/json-stringifier.h @@ -0,0 +1,659 @@ +// Copyright 2012 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef V8_JSON_STRINGIFIER_H_ +#define V8_JSON_STRINGIFIER_H_ + +#include "v8.h" +#include "v8utils.h" +#include "v8conversions.h" + +namespace v8 { +namespace internal { + +class BasicJsonStringifier BASE_EMBEDDED { + public: + explicit BasicJsonStringifier(Isolate* isolate); + + MaybeObject* Stringify(Handle object); + + private: + static const int kInitialPartLength = 32; + static const int kMaxPartLength = 16 * 1024; + static const int kPartLengthGrowthFactor = 2; + static const int kStackLimit = 8 * 1024; + + enum Result { UNCHANGED, SUCCESS, BAILOUT, CIRCULAR, STACK_OVERFLOW }; + + template void Extend(); + + void ChangeEncoding(); + + void ShrinkCurrentPart(); + + template + INLINE(void Append_(Char c)); + + template + INLINE(void AppendUnchecked_(Char c)); + + template + INLINE(void Append_(const Char* chars)); + + template + INLINE(void AppendUnchecked_(const Char* chars)); + + INLINE(void Append(char c)) { + if (is_ascii_) { + Append_(c); + } else { + Append_(c); + } + } + + INLINE(void Append(const char* chars)) { + if (is_ascii_) { + Append_(chars); + } else { + Append_(chars); + } + } + + INLINE(Handle GetProperty(Handle object, + Handle key)); + + INLINE(bool MayHaveToJsonFunction(Handle object)); + + INLINE(Result Serialize(Handle object)) { + return Serialize_(object); + } + + INLINE(Result SerializeDeferred(Handle object, + bool deferred_comma, + Handle deferred_key)) { + ASSERT(!deferred_key.is_null()); + return Serialize_(object, deferred_comma, deferred_key); + } + + template + Result Serialize_(Handle object, + bool comma = false, + Handle key = Handle::null()); + + INLINE(void SerializeDeferredKey(bool deferred_comma, + Handle deferred_key)) { + if (deferred_comma) Append(','); + SerializeString(deferred_key); + Append(':'); + } + + INLINE(Result SerializeSmi(Smi* object)); + + INLINE(Result SerializeDouble(double number)); + INLINE(Result SerializeHeapNumber(Handle object)) { + return SerializeDouble(object->value()); + } + + Result SerializeArray(Handle object); + Result SerializeObject(Handle object); + + void SerializeString(Handle object); + + template + INLINE(void SerializeString_(Vector vector, + Handle string)); + + template + INLINE(Vector GetCharVector(Handle string)); + + INLINE(Result StackPush(Handle object)); + INLINE(void StackPop()); + + INLINE(Handle accumulator()) { + return Handle(String::cast(accumulator_store_->value())); + } + + INLINE(void set_accumulator(Handle string)) { + return accumulator_store_->set_value(*string); + } + + Isolate* isolate_; + // We use a value wrapper for the string accumulator to keep the + // (indirect) handle to it in the outermost handle scope. + Handle accumulator_store_; + Handle current_part_; + Handle tojson_symbol_; + Handle stack_; + int current_index_; + int part_length_; + bool is_ascii_; + + static const int kJsonQuotesCharactersPerEntry = 8; + static const char* const JsonQuotes; +}; + + +const char* const BasicJsonStringifier::JsonQuotes = + "\\u0000\0 \\u0001\0 \\u0002\0 \\u0003\0 " + "\\u0004\0 \\u0005\0 \\u0006\0 \\u0007\0 " + "\\b\0 \\t\0 \\n\0 \\u000b\0 " + "\\f\0 \\r\0 \\u000e\0 \\u000f\0 " + "\\u0010\0 \\u0011\0 \\u0012\0 \\u0013\0 " + "\\u0014\0 \\u0015\0 \\u0016\0 \\u0017\0 " + "\\u0018\0 \\u0019\0 \\u001a\0 \\u001b\0 " + "\\u001c\0 \\u001d\0 \\u001e\0 \\u001f\0 " + " \0 !\0 \\\"\0 #\0 " + "$\0 %\0 &\0 '\0 " + "(\0 )\0 *\0 +\0 " + ",\0 -\0 .\0 /\0 " + "0\0 1\0 2\0 3\0 " + "4\0 5\0 6\0 7\0 " + "8\0 9\0 :\0 ;\0 " + "<\0 =\0 >\0 ?\0 " + "@\0 A\0 B\0 C\0 " + "D\0 E\0 F\0 G\0 " + "H\0 I\0 J\0 K\0 " + "L\0 M\0 N\0 O\0 " + "P\0 Q\0 R\0 S\0 " + "T\0 U\0 V\0 W\0 " + "X\0 Y\0 Z\0 [\0 " + "\\\\\0 ]\0 ^\0 _\0 " + "`\0 a\0 b\0 c\0 " + "d\0 e\0 f\0 g\0 " + "h\0 i\0 j\0 k\0 " + "l\0 m\0 n\0 o\0 " + "p\0 q\0 r\0 s\0 " + "t\0 u\0 v\0 w\0 " + "x\0 y\0 z\0 {\0 " + "|\0 }\0 ~\0 \177\0 "; + + +BasicJsonStringifier::BasicJsonStringifier(Isolate* isolate) + : isolate_(isolate), current_index_(0), is_ascii_(true) { + accumulator_store_ = Handle::cast( + isolate_->factory()->ToObject(isolate_->factory()->empty_string())); + part_length_ = kInitialPartLength; + current_part_ = + isolate_->factory()->NewRawAsciiString(kInitialPartLength); + tojson_symbol_ = isolate_->factory()->LookupAsciiSymbol("toJSON"); + stack_ = isolate_->factory()->NewJSArray(8); +} + + +MaybeObject* BasicJsonStringifier::Stringify(Handle object) { + switch (Serialize(object)) { + case SUCCESS: + ShrinkCurrentPart(); + return *isolate_->factory()->NewConsString(accumulator(), current_part_); + case UNCHANGED: + return isolate_->heap()->undefined_value(); + case CIRCULAR: + return isolate_->Throw(*isolate_->factory()->NewTypeError( + "circular_structure", HandleVector(NULL, 0))); + case STACK_OVERFLOW: + return isolate_->StackOverflow(); + default: + return Smi::FromInt(0); + } +} + + +template +void BasicJsonStringifier::Append_(Char c) { + if (is_ascii) { + SeqAsciiString::cast(*current_part_)->SeqAsciiStringSet( + current_index_++, c); + } else { + SeqTwoByteString::cast(*current_part_)->SeqTwoByteStringSet( + current_index_++, c); + } + if (current_index_ == part_length_) Extend(); +} + + +template +void BasicJsonStringifier::AppendUnchecked_(Char c) { + if (is_ascii) { + SeqAsciiString::cast(*current_part_)->SeqAsciiStringSet( + current_index_++, c); + } else { + SeqTwoByteString::cast(*current_part_)->SeqTwoByteStringSet( + current_index_++, c); + } + ASSERT(current_index_ < part_length_); +} + + +template +void BasicJsonStringifier::Append_(const Char* chars) { + for ( ; *chars != '\0'; chars++) Append_(*chars); +} + + +template +void BasicJsonStringifier::AppendUnchecked_(const Char* chars) { + for ( ; *chars != '\0'; chars++) AppendUnchecked_(*chars); +} + + +Handle BasicJsonStringifier::GetProperty(Handle object, + Handle key) { + LookupResult lookup(isolate_); + object->LocalLookupRealNamedProperty(*key, &lookup); + if (!lookup.IsProperty()) return isolate_->factory()->undefined_value(); + switch (lookup.type()) { + case NORMAL: { + Object* value = lookup.holder()->GetNormalizedProperty(&lookup); + ASSERT(!value->IsTheHole()); + return Handle(value); + } + case FIELD: { + Object* value = lookup.holder()->FastPropertyAt(lookup.GetFieldIndex()); + ASSERT(!value->IsTheHole()); + return Handle(value); + } + case CONSTANT_FUNCTION: + return Handle(lookup.GetConstantFunction()); + case CALLBACKS: + case HANDLER: + case INTERCEPTOR: + return Handle::null(); + case TRANSITION: + case NONEXISTENT: + UNREACHABLE(); + break; + } + return Handle::null(); +} + + +bool BasicJsonStringifier::MayHaveToJsonFunction(Handle object) { + LookupResult lookup(isolate_); + object->LookupRealNamedProperty(*tojson_symbol_, &lookup); + if (!lookup.IsProperty()) return false; + Object* value; + switch (lookup.type()) { + case NORMAL: + value = lookup.holder()->GetNormalizedProperty(&lookup); + break; + case FIELD: + value = lookup.holder()->FastPropertyAt(lookup.GetFieldIndex()); + break; + default: + return true; + } + ASSERT(!value->IsTheHole()); + return value->IsSpecFunction(); +} + + +BasicJsonStringifier::Result BasicJsonStringifier::StackPush( + Handle object) { + int length = Smi::cast(stack_->length())->value(); + if (length > kStackLimit) return STACK_OVERFLOW; + FixedArray* elements = FixedArray::cast(stack_->elements()); + for (int i = 0; i < length; i++) { + if (elements->get(i) == *object) { + return CIRCULAR; + } + } + stack_->EnsureSize(length + 1); + FixedArray::cast(stack_->elements())->set(length, *object); + stack_->set_length(Smi::FromInt(length + 1)); + return SUCCESS; +} + + +void BasicJsonStringifier::StackPop() { + int length = Smi::cast(stack_->length())->value(); + stack_->set_length(Smi::FromInt(length - 1)); +} + + +template +BasicJsonStringifier::Result BasicJsonStringifier::Serialize_( + Handle object, bool comma, Handle key) { + if (object->IsJSObject()) { + // We don't deal with custom toJSON functions. + if (MayHaveToJsonFunction(Handle::cast(object))) return BAILOUT; + + if (object->IsJSFunction()) { + return UNCHANGED; + } else if (object->IsJSArray()) { + if (deferred_key) SerializeDeferredKey(comma, key); + return SerializeArray(Handle::cast(object)); + } else if (object->IsJSValue()) { + // JSValue with a custom prototype. + if (object->GetPrototype()->IsJSReceiver()) return BAILOUT; + // Unpack value wrapper and fall through. + object = Handle(JSValue::cast(*object)->value()); + } else { + if (deferred_key) SerializeDeferredKey(comma, key); + return SerializeObject(Handle::cast(object)); + } + } + + if (object->IsString()) { + if (deferred_key) SerializeDeferredKey(comma, key); + SerializeString(Handle::cast(object)); + return SUCCESS; + } else if (object->IsSmi()) { + if (deferred_key) SerializeDeferredKey(comma, key); + return SerializeSmi(Smi::cast(*object)); + } else if (object->IsHeapNumber()) { + if (deferred_key) SerializeDeferredKey(comma, key); + return SerializeHeapNumber(Handle::cast(object)); + } else if (object->IsOddball()) { + switch (Oddball::cast(*object)->kind()) { + case Oddball::kFalse: + if (deferred_key) SerializeDeferredKey(comma, key); + Append("false"); + return SUCCESS; + case Oddball::kTrue: + if (deferred_key) SerializeDeferredKey(comma, key); + Append("true"); + return SUCCESS; + case Oddball::kNull: + if (deferred_key) SerializeDeferredKey(comma, key); + Append("null"); + return SUCCESS; + } + } + + return UNCHANGED; +} + + +BasicJsonStringifier::Result BasicJsonStringifier::SerializeSmi(Smi* object) { + static const int kBufferSize = 100; + char chars[kBufferSize]; + Vector buffer(chars, kBufferSize); + Append(IntToCString(object->value(), buffer)); + return SUCCESS; +} + + +BasicJsonStringifier::Result BasicJsonStringifier::SerializeDouble( + double number) { + if (isinf(number) || isnan(number)) { + Append("null"); + return SUCCESS; + } + static const int kBufferSize = 100; + char chars[kBufferSize]; + Vector buffer(chars, kBufferSize); + Append(DoubleToCString(number, buffer)); + return SUCCESS; +} + + +BasicJsonStringifier::Result BasicJsonStringifier::SerializeArray( + Handle object) { + HandleScope handle_scope(isolate_); + if (StackPush(object) == CIRCULAR) return CIRCULAR; + int length = Smi::cast(object->length())->value(); + Append('['); + switch (object->GetElementsKind()) { + case FAST_SMI_ELEMENTS: { + Handle elements = Handle( + FixedArray::cast(object->elements())); + for (int i = 0; i < length; i++) { + if (i > 0) Append(','); + SerializeSmi(Smi::cast(elements->get(i))); + } + break; + } + case FAST_HOLEY_SMI_ELEMENTS: { + Handle elements = Handle( + FixedArray::cast(object->elements())); + for (int i = 0; i < length; i++) { + if (i > 0) Append(','); + if (elements->is_the_hole(i)) { + Append("null"); + } else { + SerializeSmi(Smi::cast(elements->get(i))); + } + } + break; + } + case FAST_HOLEY_DOUBLE_ELEMENTS: + case FAST_DOUBLE_ELEMENTS: { + Handle elements = Handle( + FixedDoubleArray::cast(object->elements())); + for (int i = 0; i < length; i++) { + if (i > 0) Append(','); + SerializeDouble(elements->get_scalar(i)); + } + break; + } + case FAST_HOLEY_ELEMENTS: + case FAST_ELEMENTS: { + Handle elements = Handle( + FixedArray::cast(object->elements())); + for (int i = 0; i < length; i++) { + if (i > 0) Append(','); + Result result = Serialize(Handle(elements->get(i))); + if (result == SUCCESS) continue; + if (result == UNCHANGED) { + Append("null"); + } else { + return result; + } + } + break; + } + default: + return BAILOUT; + } + Append(']'); + StackPop(); + current_part_ = handle_scope.CloseAndEscape(current_part_); + return SUCCESS; +} + + +BasicJsonStringifier::Result BasicJsonStringifier::SerializeObject( + Handle object) { + HandleScope handle_scope(isolate_); + Result stack_push = StackPush(object); + if (stack_push != SUCCESS) return stack_push; + if (object->IsJSGlobalProxy()) return BAILOUT; + bool threw = false; + Handle contents = + GetKeysInFixedArrayFor(object, LOCAL_ONLY, &threw); + if (threw) return BAILOUT; + Append('{'); + int length = contents->length(); + bool comma = false; + for (int i = 0; i < length; i++) { + Object* key = contents->get(i); + Handle key_handle; + Handle property; + if (key->IsString()) { + key_handle = Handle(String::cast(key)); + property = GetProperty(object, key_handle); + } else { + ASSERT(key->IsNumber()); + key_handle = isolate_->factory()->NumberToString(Handle(key)); + uint32_t index; + if (key->IsSmi()) { + property = Object::GetElement(object, Smi::cast(key)->value()); + } else if (key_handle->AsArrayIndex(&index)) { + property = Object::GetElement(object, index); + } else { + property = GetProperty(object, key_handle); + } + } + if (property.is_null()) return BAILOUT; + Result result = SerializeDeferred(property, comma, key_handle); + if (!comma && result == SUCCESS) comma = true; + if (result >= BAILOUT) return result; + } + Append('}'); + StackPop(); + current_part_ = handle_scope.CloseAndEscape(current_part_); + return SUCCESS; +} + + +void BasicJsonStringifier::ShrinkCurrentPart() { + ASSERT(current_index_ < part_length_); + if (current_index_ == 0) { + current_part_ = isolate_->factory()->empty_string(); + return; + } + + int string_size, allocated_string_size; + if (is_ascii_) { + allocated_string_size = SeqAsciiString::SizeFor(part_length_); + string_size = SeqAsciiString::SizeFor(current_index_); + } else { + allocated_string_size = SeqTwoByteString::SizeFor(part_length_); + string_size = SeqTwoByteString::SizeFor(current_index_); + } + + int delta = allocated_string_size - string_size; + current_part_->set_length(current_index_); + + // String sizes are pointer size aligned, so that we can use filler objects + // that are a multiple of pointer size. + Address end_of_string = current_part_->address() + string_size; + isolate_->heap()->CreateFillerObjectAt(end_of_string, delta); + if (Marking::IsBlack(Marking::MarkBitFrom(*current_part_))) { + MemoryChunk::IncrementLiveBytesFromMutator( + current_part_->address(), -delta); + } +} + + +template +void BasicJsonStringifier::Extend() { + set_accumulator( + isolate_->factory()->NewConsString(accumulator(), current_part_)); + if (part_length_ <= kMaxPartLength / kPartLengthGrowthFactor) { + part_length_ *= kPartLengthGrowthFactor; + } + if (is_ascii) { + current_part_ = + isolate_->factory()->NewRawAsciiString(part_length_); + } else { + current_part_ = + isolate_->factory()->NewRawTwoByteString(part_length_); + } + current_index_ = 0; +} + + +void BasicJsonStringifier::ChangeEncoding() { + ShrinkCurrentPart(); + set_accumulator( + isolate_->factory()->NewConsString(accumulator(), current_part_)); + current_part_ = + isolate_->factory()->NewRawTwoByteString(part_length_); + current_index_ = 0; + is_ascii_ = false; +} + + +template +void BasicJsonStringifier::SerializeString_(Vector vector, + Handle string) { + int length = vector.length(); + if (current_index_ + (length << 3) < (part_length_ - 2)) { + AssertNoAllocation no_allocation_scope; + AppendUnchecked_('"'); + for (int i = 0; i < length; i++) { + Char c = vector[i]; + if ((c >= '#' && c <= '~' && c != '\\') || + (!is_ascii && ((c & 0xFF80) != 0))) { + AppendUnchecked_(c); + } else { + AppendUnchecked_( + &JsonQuotes[c * kJsonQuotesCharactersPerEntry]); + } + } + AppendUnchecked_('"'); + } else { + Append_('"'); + String* string_location = *string; + for (int i = 0; i < length; i++) { + Char c = vector[i]; + if ((c >= '#' && c <= '~' && c != '\\') || + (!is_ascii && ((c & 0xFF80) != 0))) { + Append_(c); + } else { + Append_(&JsonQuotes[c * kJsonQuotesCharactersPerEntry]); + } + // If GC moved the string, we need to refresh the vector. + if (*string != string_location) { + vector = GetCharVector(string); + string_location = *string; + } + } + Append_('"'); + } +} + + +template <> +Vector BasicJsonStringifier::GetCharVector(Handle string) { + String::FlatContent flat = string->GetFlatContent(); + ASSERT(flat.IsAscii()); + return flat.ToAsciiVector(); +} + + +template <> +Vector BasicJsonStringifier::GetCharVector(Handle string) { + String::FlatContent flat = string->GetFlatContent(); + ASSERT(flat.IsTwoByte()); + return flat.ToUC16Vector(); +} + + +void BasicJsonStringifier::SerializeString(Handle object) { + FlattenString(object); + String::FlatContent flat = object->GetFlatContent(); + if (is_ascii_) { + if (flat.IsAscii()) { + SerializeString_(flat.ToAsciiVector(), object); + } else { + ChangeEncoding(); + SerializeString(object); + } + } else { + if (flat.IsAscii()) { + SerializeString_(flat.ToAsciiVector(), object); + } else { + SerializeString_(flat.ToUC16Vector(), object); + } + } +} + +} } // namespace v8::internal + +#endif // V8_JSON_STRINGIFIER_H_ diff --git a/src/json.js b/src/json.js index 85224b0..608cb71 100644 --- a/src/json.js +++ b/src/json.js @@ -307,10 +307,12 @@ function BasicJSONSerialize(key, value, stack, builder) { function JSONStringify(value, replacer, space) { if (%_ArgumentsLength() == 1) { + var result = %BasicJSONStringify(value); + if (result != 0) return result; var builder = new InternalArray(); BasicJSONSerialize('', value, new InternalArray(), builder); if (builder.length == 0) return; - var result = %_FastAsciiArrayJoin(builder, ""); + result = %_FastAsciiArrayJoin(builder, ""); if (!IS_UNDEFINED(result)) return result; return %StringBuilderConcat(builder, builder.length, ""); } diff --git a/src/runtime.cc b/src/runtime.cc index 19d9a3f..b0a6b5e 100644 --- a/src/runtime.cc +++ b/src/runtime.cc @@ -46,6 +46,7 @@ #include "isolate-inl.h" #include "jsregexp.h" #include "json-parser.h" +#include "json-stringifier.h" #include "liveedit.h" #include "liveobjectlist-inl.h" #include "misc-intrinsics.h" @@ -5760,6 +5761,14 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_QuoteJSONStringArray) { } +RUNTIME_FUNCTION(MaybeObject*, Runtime_BasicJSONStringify) { + ASSERT(args.length() == 1); + HandleScope scope(isolate); + BasicJsonStringifier stringifier(isolate); + return stringifier.Stringify(Handle(args[0])); +} + + RUNTIME_FUNCTION(MaybeObject*, Runtime_StringParseInt) { NoHandleAllocation ha; diff --git a/src/runtime.h b/src/runtime.h index c9939d0..c802906 100644 --- a/src/runtime.h +++ b/src/runtime.h @@ -120,6 +120,7 @@ namespace internal { F(CharFromCode, 1, 1) \ F(URIEscape, 1, 1) \ F(URIUnescape, 1, 1) \ + F(BasicJSONStringify, 1, 1) \ F(QuoteJSONString, 1, 1) \ F(QuoteJSONStringComma, 1, 1) \ F(QuoteJSONStringArray, 1, 1) \ diff --git a/test/mjsunit/json-recursive.js b/test/mjsunit/json-recursive.js new file mode 100644 index 0000000..f28cce8 --- /dev/null +++ b/test/mjsunit/json-recursive.js @@ -0,0 +1,44 @@ +// Copyright 2012 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +var a = {}; +for (i = 0; i < 10000; i++) { + var current = {}; + current.a = a; + a = current; +} + +function rec(a,b,c,d,e,f,g,h,i,j,k,l,m,n) { + JSON.stringify(a); + rec(a,b,c,d,e,f,g,h,i,j,k,l,m,n); +} + +assertThrows( + function() { rec(1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4) }, + RangeError); + diff --git a/test/mjsunit/json.js b/test/mjsunit/json.js index 54fa185..6e91725 100644 --- a/test/mjsunit/json.js +++ b/test/mjsunit/json.js @@ -257,6 +257,42 @@ assertEquals("[1,2,[3,[4],5],6,7]", assertEquals("[2,4,[6,[8],10],12,14]", JSON.stringify([1, 2, [3, [4], 5], 6, 7], DoubleNumbers)); assertEquals('["a","ab","abc"]', JSON.stringify(["a","ab","abc"])); +assertEquals('{"a":1,"c":true}', + JSON.stringify({ a : 1, + b : function() { 1 }, + c : true, + d : function() { 2 } })); +assertEquals('[1,null,true,null]', + JSON.stringify([1, function() { 1 }, true, function() { 2 }])); +assertEquals('"toJSON 123"', + JSON.stringify({ toJSON : function() { return 'toJSON 123'; } })); +assertEquals('{"a":321}', + JSON.stringify({ a : { toJSON : function() { return 321; } } })); +var counter = 0; +assertEquals('{"getter":123}', + JSON.stringify({ get getter() { counter++; return 123; } })); +assertEquals(1, counter); +assertEquals('{"a":"abc","b":"\u1234bc"}', + JSON.stringify({ a : "abc", b : "\u1234bc" })); + + +var a = { a : 1, b : 2 }; +delete a.a; +assertEquals('{"b":2}', JSON.stringify(a)); + +var b = {}; +b.__proto__ = { toJSON : function() { return 321;} }; +assertEquals("321", JSON.stringify(b)); + +var array = [""]; +var expected = '""'; +for (var i = 0; i < 10000; i++) { + array.push(""); + expected = '"",' + expected; +} +expected = '[' + expected + ']'; +assertEquals(expected, JSON.stringify(array)); + var circular = [1, 2, 3]; circular[2] = circular; diff --git a/test/mjsunit/regress/regress-json-stringify-gc.js b/test/mjsunit/regress/regress-json-stringify-gc.js new file mode 100644 index 0000000..4208716 --- /dev/null +++ b/test/mjsunit/regress/regress-json-stringify-gc.js @@ -0,0 +1,41 @@ +// Copyright 2012 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +var a = []; +var new_space_string = ""; +for (var i = 0; i < 128; i++) { + new_space_string += String.fromCharCode((Math.random() * 26 + 65) | 0); +} +for (var i = 0; i < 10000; i++) a.push(new_space_string); + +// At some point during the first stringify, allocation causes a GC and +// new_space_string is moved to old space. Make sure that this does not +// screw up reading from the correct location. +json1 = JSON.stringify(a); +json2 = JSON.stringify(a); +assertEquals(json1, json2); + -- 2.7.4