From cec8383cff10f620e93a51a5a6c22bec048950dc Mon Sep 17 00:00:00 2001 From: "rossberg@chromium.org" Date: Wed, 13 Nov 2013 10:34:06 +0000 Subject: [PATCH] Provide private symbols through internal APIs Adds a notion of private symbols, mainly intended for internal use, especially, self-hosting of built-in types that would otherwise require new C++ classes. On the JS side (i.e., in built-ins), private properties can be created and accessed through a set of macros: NEW_PRIVATE(print_name) HAS_PRIVATE(obj, sym) GET_PRIVATE(obj, sym) SET_PRIVATE(obj, sym, val) DELETE_PRIVATE(obj, sym) In the V8 API, they are accessible via a new class Private, and respective HasPrivate/Get/Private/SetPrivate/DeletePrivate methods on calss Object. These APIs are designed and restricted such that their implementation can later be replaced by whatever ES7+ will officially provide. R=yangguo@chromium.org BUG= Review URL: https://codereview.chromium.org/48923002 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@17683 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- include/v8.h | 39 ++++- src/api.cc | 61 ++++++-- src/array-iterator.js | 22 +-- src/factory.cc | 8 + src/factory.h | 1 + src/heap.cc | 15 +- src/heap.h | 1 + src/macros.py | 7 + src/messages.js | 71 +++++---- src/objects-debug.cc | 1 + src/objects-inl.h | 2 + src/objects-printer.cc | 1 + src/objects.h | 16 +- src/parser.cc | 3 +- src/runtime.cc | 58 ++++--- src/runtime.h | 2 + test/cctest/test-api.cc | 64 ++++++++ test/mjsunit/harmony/private.js | 324 ++++++++++++++++++++++++++++++++++++++++ test/mjsunit/harmony/symbols.js | 1 + 19 files changed, 611 insertions(+), 86 deletions(-) create mode 100644 test/mjsunit/harmony/private.js diff --git a/include/v8.h b/include/v8.h index cb105ac..6d97e19 100644 --- a/include/v8.h +++ b/include/v8.h @@ -114,6 +114,7 @@ class String; class StringObject; class Symbol; class SymbolObject; +class Private; class Uint32; class Utils; class Value; @@ -1926,11 +1927,9 @@ class V8_EXPORT Symbol : public Primitive { // Returns the print name string of the symbol, or undefined if none. Local Name() const; - // Create a symbol without a print name. - static Local New(Isolate* isolate); - - // Create a symbol with a print name. - static Local New(Isolate *isolate, const char* data, int length = -1); + // Create a symbol. If data is not NULL, it will be used as a print name. + static Local New( + Isolate *isolate, const char* data = NULL, int length = -1); V8_INLINE static Symbol* Cast(v8::Value* obj); private: @@ -1940,6 +1939,25 @@ class V8_EXPORT Symbol : public Primitive { /** + * A private symbol + * + * This is an experimental feature. Use at your own risk. + */ +class V8_EXPORT Private : public Data { + public: + // Returns the print name string of the private symbol, or undefined if none. + Local Name() const; + + // Create a private symbol. If data is not NULL, it will be the print name. + static Local New( + Isolate *isolate, const char* data = NULL, int length = -1); + + private: + Private(); +}; + + +/** * A JavaScript number value (ECMA-262, 4.3.20) */ class V8_EXPORT Number : public Primitive { @@ -2109,6 +2127,17 @@ class V8_EXPORT Object : public Value { AccessControl settings = DEFAULT); /** + * Functionality for private properties. + * This is an experimental feature, use at your own risk. + * Note: Private properties are inherited. Do not rely on this, since it may + * change. + */ + bool HasPrivate(Handle key); + bool SetPrivate(Handle key, Handle value); + bool DeletePrivate(Handle key); + Local GetPrivate(Handle key); + + /** * Returns an array containing the names of the enumerable properties * of this object, including properties from prototype objects. The * array returned by this method contains the same values as would diff --git a/src/api.cc b/src/api.cc index c1d3d1b..e55e0aa 100644 --- a/src/api.cc +++ b/src/api.cc @@ -3191,6 +3191,12 @@ bool v8::Object::ForceSet(v8::Handle key, } +bool v8::Object::SetPrivate(v8::Handle key, v8::Handle value) { + v8::Handle* key_as_value = reinterpret_cast*>(&key); + return Set(*key_as_value, value, DontEnum); +} + + bool v8::Object::ForceDelete(v8::Handle key) { i::Isolate* isolate = Utils::OpenHandle(this)->GetIsolate(); ON_BAILOUT(isolate, "v8::Object::ForceDelete()", return false); @@ -3242,6 +3248,12 @@ Local v8::Object::Get(uint32_t index) { } +Local v8::Object::GetPrivate(v8::Handle key) { + v8::Handle* key_as_value = reinterpret_cast*>(&key); + return Get(*key_as_value); +} + + PropertyAttribute v8::Object::GetPropertyAttributes(v8::Handle key) { i::Isolate* isolate = Utils::OpenHandle(this)->GetIsolate(); ON_BAILOUT(isolate, "v8::Object::GetPropertyAttribute()", @@ -3441,6 +3453,12 @@ bool v8::Object::Delete(v8::Handle key) { } +bool v8::Object::DeletePrivate(v8::Handle key) { + v8::Handle* key_as_value = reinterpret_cast*>(&key); + return Delete(*key_as_value); +} + + bool v8::Object::Has(v8::Handle key) { i::Isolate* isolate = Utils::OpenHandle(this)->GetIsolate(); ON_BAILOUT(isolate, "v8::Object::Has()", return false); @@ -3455,6 +3473,12 @@ bool v8::Object::Has(v8::Handle key) { } +bool v8::Object::HasPrivate(v8::Handle key) { + v8::Handle* key_as_value = reinterpret_cast*>(&key); + return Has(*key_as_value); +} + + bool v8::Object::Delete(uint32_t index) { i::Isolate* isolate = Utils::OpenHandle(this)->GetIsolate(); ON_BAILOUT(isolate, "v8::Object::DeleteProperty()", @@ -4876,6 +4900,11 @@ Local Symbol::Name() const { } +Local Private::Name() const { + return reinterpret_cast(this)->Name(); +} + + double Number::Value() const { i::Handle obj = Utils::OpenHandle(this); return obj->Number(); @@ -6138,27 +6167,39 @@ Local DataView::New(Handle array_buffer, } -Local v8::Symbol::New(Isolate* isolate) { +Local v8::Symbol::New(Isolate* isolate, const char* data, int length) { i::Isolate* i_isolate = reinterpret_cast(isolate); EnsureInitializedForIsolate(i_isolate, "v8::Symbol::New()"); LOG_API(i_isolate, "Symbol::New()"); ENTER_V8(i_isolate); i::Handle result = i_isolate->factory()->NewSymbol(); + if (data != NULL) { + if (length == -1) length = i::StrLength(data); + i::Handle name = i_isolate->factory()->NewStringFromUtf8( + i::Vector(data, length)); + result->set_name(*name); + } return Utils::ToLocal(result); } -Local v8::Symbol::New(Isolate* isolate, const char* data, int length) { +Local v8::Private::New( + Isolate* isolate, const char* data, int length) { i::Isolate* i_isolate = reinterpret_cast(isolate); - EnsureInitializedForIsolate(i_isolate, "v8::Symbol::New()"); - LOG_API(i_isolate, "Symbol::New(char)"); + EnsureInitializedForIsolate(i_isolate, "v8::Private::New()"); + LOG_API(i_isolate, "Private::New()"); ENTER_V8(i_isolate); - if (length == -1) length = i::StrLength(data); - i::Handle name = i_isolate->factory()->NewStringFromUtf8( - i::Vector(data, length)); - i::Handle result = i_isolate->factory()->NewSymbol(); - result->set_name(*name); - return Utils::ToLocal(result); + i::Handle symbol = i_isolate->factory()->NewPrivateSymbol(); + if (data != NULL) { + if (length == -1) length = i::StrLength(data); + i::Handle name = i_isolate->factory()->NewStringFromUtf8( + i::Vector(data, length)); + symbol->set_name(*name); + } + Local result = Utils::ToLocal(symbol); + v8::Handle* result_as_private = + reinterpret_cast*>(&result); + return *result_as_private; } diff --git a/src/array-iterator.js b/src/array-iterator.js index e734986..a8c5e00 100644 --- a/src/array-iterator.js +++ b/src/array-iterator.js @@ -36,9 +36,9 @@ var ARRAY_ITERATOR_KIND_VALUES = 2; var ARRAY_ITERATOR_KIND_ENTRIES = 3; // The spec draft also has "sparse" but it is never used. -var iteratorObjectSymbol = %CreateSymbol(UNDEFINED); -var arrayIteratorNextIndexSymbol = %CreateSymbol(UNDEFINED); -var arrayIterationKindSymbol = %CreateSymbol(UNDEFINED); +var iteratorObjectSymbol = NEW_PRIVATE("iterator_object"); +var arrayIteratorNextIndexSymbol = NEW_PRIVATE("iterator_next"); +var arrayIterationKindSymbol = NEW_PRIVATE("iterator_kind"); function ArrayIterator() {} @@ -46,9 +46,9 @@ function ArrayIterator() {} function CreateArrayIterator(array, kind) { var object = ToObject(array); var iterator = new ArrayIterator; - iterator[iteratorObjectSymbol] = object; - iterator[arrayIteratorNextIndexSymbol] = 0; - iterator[arrayIterationKindSymbol] = kind; + SET_PRIVATE(iterator, iteratorObjectSymbol, object); + SET_PRIVATE(iterator, arrayIteratorNextIndexSymbol, 0); + SET_PRIVATE(iterator, arrayIterationKindSymbol, kind); return iterator; } @@ -60,24 +60,24 @@ function CreateIteratorResultObject(value, done) { // 15.4.5.2.2 ArrayIterator.prototype.next( ) function ArrayIteratorNext() { var iterator = ToObject(this); - var array = iterator[iteratorObjectSymbol]; + var array = GET_PRIVATE(iterator, iteratorObjectSymbol); if (!array) { throw MakeTypeError('incompatible_method_receiver', ['Array Iterator.prototype.next']); } - var index = iterator[arrayIteratorNextIndexSymbol]; - var itemKind = iterator[arrayIterationKindSymbol]; + var index = GET_PRIVATE(iterator, arrayIteratorNextIndexSymbol); + var itemKind = GET_PRIVATE(iterator, arrayIterationKindSymbol); var length = TO_UINT32(array.length); // "sparse" is never used. if (index >= length) { - iterator[arrayIteratorNextIndexSymbol] = 1 / 0; // Infinity + SET_PRIVATE(iterator, arrayIteratorNextIndexSymbol, INFINITY); return CreateIteratorResultObject(UNDEFINED, true); } - iterator[arrayIteratorNextIndexSymbol] = index + 1; + SET_PRIVATE(iterator, arrayIteratorNextIndexSymbol, index + 1); if (itemKind == ARRAY_ITERATOR_KIND_VALUES) return CreateIteratorResultObject(array[index], false); diff --git a/src/factory.cc b/src/factory.cc index da85b12..6da9a2e 100644 --- a/src/factory.cc +++ b/src/factory.cc @@ -365,6 +365,14 @@ Handle Factory::NewSymbol() { } +Handle Factory::NewPrivateSymbol() { + CALL_HEAP_FUNCTION( + isolate(), + isolate()->heap()->AllocatePrivateSymbol(), + Symbol); +} + + Handle Factory::NewNativeContext() { CALL_HEAP_FUNCTION( isolate(), diff --git a/src/factory.h b/src/factory.h index 2b4ae7b..663f56f 100644 --- a/src/factory.h +++ b/src/factory.h @@ -186,6 +186,7 @@ class Factory { // Create a symbol. Handle NewSymbol(); + Handle NewPrivateSymbol(); // Create a global (but otherwise uninitialized) context. Handle NewNativeContext(); diff --git a/src/heap.cc b/src/heap.cc index 2ba3919..b5d03bc 100644 --- a/src/heap.cc +++ b/src/heap.cc @@ -3311,11 +3311,13 @@ bool Heap::CreateInitialObjects() { { MaybeObject* maybe_obj = AllocateSymbol(); if (!maybe_obj->ToObject(&obj)) return false; } + Symbol::cast(obj)->set_is_private(true); set_frozen_symbol(Symbol::cast(obj)); { MaybeObject* maybe_obj = AllocateSymbol(); if (!maybe_obj->ToObject(&obj)) return false; } + Symbol::cast(obj)->set_is_private(true); set_elements_transition_symbol(Symbol::cast(obj)); { MaybeObject* maybe_obj = SeededNumberDictionary::Allocate(this, 0, TENURED); @@ -3327,6 +3329,7 @@ bool Heap::CreateInitialObjects() { { MaybeObject* maybe_obj = AllocateSymbol(); if (!maybe_obj->ToObject(&obj)) return false; } + Symbol::cast(obj)->set_is_private(true); set_observed_symbol(Symbol::cast(obj)); // Handling of script id generation is in Factory::NewScript. @@ -5516,12 +5519,22 @@ MaybeObject* Heap::AllocateSymbol() { Symbol::cast(result)->set_hash_field( Name::kIsNotArrayIndexMask | (hash << Name::kHashShift)); Symbol::cast(result)->set_name(undefined_value()); + Symbol::cast(result)->set_flags(Smi::FromInt(0)); - ASSERT(result->IsSymbol()); + ASSERT(!Symbol::cast(result)->is_private()); return result; } +MaybeObject* Heap::AllocatePrivateSymbol() { + MaybeObject* maybe = AllocateSymbol(); + Symbol* symbol; + if (!maybe->To(&symbol)) return maybe; + symbol->set_is_private(true); + return symbol; +} + + MaybeObject* Heap::AllocateNativeContext() { Object* result; { MaybeObject* maybe_result = diff --git a/src/heap.h b/src/heap.h index eea94f8..a0c85e5 100644 --- a/src/heap.h +++ b/src/heap.h @@ -881,6 +881,7 @@ class Heap { // failed. // Please note this does not perform a garbage collection. MUST_USE_RESULT MaybeObject* AllocateSymbol(); + MUST_USE_RESULT MaybeObject* AllocatePrivateSymbol(); // Allocate a tenured AllocationSite. It's payload is null MUST_USE_RESULT MaybeObject* AllocateAllocationSite(); diff --git a/src/macros.py b/src/macros.py index 1785d44..7bad23b 100644 --- a/src/macros.py +++ b/src/macros.py @@ -157,6 +157,13 @@ macro TO_NUMBER_INLINE(arg) = (IS_NUMBER(%IS_VAR(arg)) ? arg : NonNumberToNumber macro TO_OBJECT_INLINE(arg) = (IS_SPEC_OBJECT(%IS_VAR(arg)) ? arg : ToObject(arg)); macro JSON_NUMBER_TO_STRING(arg) = ((%_IsSmi(%IS_VAR(arg)) || arg - arg == 0) ? %_NumberToString(arg) : "null"); +# Private names. +macro NEW_PRIVATE(name) = (%CreatePrivateSymbol(name)); +macro HAS_PRIVATE(obj, sym) = (sym in obj); +macro GET_PRIVATE(obj, sym) = (obj[sym]); +macro SET_PRIVATE(obj, sym, val) = (obj[sym] = val); +macro DELETE_PRIVATE(obj, sym) = (delete obj[sym]); + # Constants. The compiler constant folds them. const NAN = $NaN; const INFINITY = (1/0); diff --git a/src/messages.js b/src/messages.js index bfd42de..926f294 100644 --- a/src/messages.js +++ b/src/messages.js @@ -783,64 +783,67 @@ function GetStackTraceLine(recv, fun, pos, isGlobal) { // ---------------------------------------------------------------------------- // Error implementation -var CallSiteReceiverKey = %CreateSymbol("receiver"); -var CallSiteFunctionKey = %CreateSymbol("function"); -var CallSitePositionKey = %CreateSymbol("position"); -var CallSiteStrictModeKey = %CreateSymbol("strict mode"); +//TODO(rossberg) +var CallSiteReceiverKey = NEW_PRIVATE("receiver"); +var CallSiteFunctionKey = NEW_PRIVATE("function"); +var CallSitePositionKey = NEW_PRIVATE("position"); +var CallSiteStrictModeKey = NEW_PRIVATE("strict mode"); function CallSite(receiver, fun, pos, strict_mode) { - this[CallSiteReceiverKey] = receiver; - this[CallSiteFunctionKey] = fun; - this[CallSitePositionKey] = pos; - this[CallSiteStrictModeKey] = strict_mode; + SET_PRIVATE(this, CallSiteReceiverKey, receiver); + SET_PRIVATE(this, CallSiteFunctionKey, fun); + SET_PRIVATE(this, CallSitePositionKey, pos); + SET_PRIVATE(this, CallSiteStrictModeKey, strict_mode); } function CallSiteGetThis() { - return this[CallSiteStrictModeKey] ? UNDEFINED : this[CallSiteReceiverKey]; + return GET_PRIVATE(this, CallSiteStrictModeKey) + ? UNDEFINED : GET_PRIVATE(this, CallSiteReceiverKey); } function CallSiteGetTypeName() { - return GetTypeName(this[CallSiteReceiverKey], false); + return GetTypeName(GET_PRIVATE(this, CallSiteReceiverKey), false); } function CallSiteIsToplevel() { - if (this[CallSiteReceiverKey] == null) { + if (GET_PRIVATE(this, CallSiteReceiverKey) == null) { return true; } - return IS_GLOBAL(this[CallSiteReceiverKey]); + return IS_GLOBAL(GET_PRIVATE(this, CallSiteReceiverKey)); } function CallSiteIsEval() { - var script = %FunctionGetScript(this[CallSiteFunctionKey]); + var script = %FunctionGetScript(GET_PRIVATE(this, CallSiteFunctionKey)); return script && script.compilation_type == COMPILATION_TYPE_EVAL; } function CallSiteGetEvalOrigin() { - var script = %FunctionGetScript(this[CallSiteFunctionKey]); + var script = %FunctionGetScript(GET_PRIVATE(this, CallSiteFunctionKey)); return FormatEvalOrigin(script); } function CallSiteGetScriptNameOrSourceURL() { - var script = %FunctionGetScript(this[CallSiteFunctionKey]); + var script = %FunctionGetScript(GET_PRIVATE(this, CallSiteFunctionKey)); return script ? script.nameOrSourceURL() : null; } function CallSiteGetFunction() { - return this[CallSiteStrictModeKey] ? UNDEFINED : this[CallSiteFunctionKey]; + return GET_PRIVATE(this, CallSiteStrictModeKey) + ? UNDEFINED : GET_PRIVATE(this, CallSiteFunctionKey); } function CallSiteGetFunctionName() { // See if the function knows its own name - var name = this[CallSiteFunctionKey].name; + var name = GET_PRIVATE(this, CallSiteFunctionKey).name; if (name) { return name; } - name = %FunctionGetInferredName(this[CallSiteFunctionKey]); + name = %FunctionGetInferredName(GET_PRIVATE(this, CallSiteFunctionKey)); if (name) { return name; } // Maybe this is an evaluation? - var script = %FunctionGetScript(this[CallSiteFunctionKey]); + var script = %FunctionGetScript(GET_PRIVATE(this, CallSiteFunctionKey)); if (script && script.compilation_type == COMPILATION_TYPE_EVAL) { return "eval"; } @@ -850,8 +853,8 @@ function CallSiteGetFunctionName() { function CallSiteGetMethodName() { // See if we can find a unique property on the receiver that holds // this function. - var receiver = this[CallSiteReceiverKey]; - var fun = this[CallSiteFunctionKey]; + var receiver = GET_PRIVATE(this, CallSiteReceiverKey); + var fun = GET_PRIVATE(this, CallSiteFunctionKey); var ownName = fun.name; if (ownName && receiver && (%_CallFunction(receiver, ownName, ObjectLookupGetter) === fun || @@ -880,49 +883,51 @@ function CallSiteGetMethodName() { } function CallSiteGetFileName() { - var script = %FunctionGetScript(this[CallSiteFunctionKey]); + var script = %FunctionGetScript(GET_PRIVATE(this, CallSiteFunctionKey)); return script ? script.name : null; } function CallSiteGetLineNumber() { - if (this[CallSitePositionKey] == -1) { + if (GET_PRIVATE(this, CallSitePositionKey) == -1) { return null; } - var script = %FunctionGetScript(this[CallSiteFunctionKey]); + var script = %FunctionGetScript(GET_PRIVATE(this, CallSiteFunctionKey)); var location = null; if (script) { - location = script.locationFromPosition(this[CallSitePositionKey], true); + location = script.locationFromPosition( + GET_PRIVATE(this, CallSitePositionKey), true); } return location ? location.line + 1 : null; } function CallSiteGetColumnNumber() { - if (this[CallSitePositionKey] == -1) { + if (GET_PRIVATE(this, CallSitePositionKey) == -1) { return null; } - var script = %FunctionGetScript(this[CallSiteFunctionKey]); + var script = %FunctionGetScript(GET_PRIVATE(this, CallSiteFunctionKey)); var location = null; if (script) { - location = script.locationFromPosition(this[CallSitePositionKey], true); + location = script.locationFromPosition( + GET_PRIVATE(this, CallSitePositionKey), true); } return location ? location.column + 1: null; } function CallSiteIsNative() { - var script = %FunctionGetScript(this[CallSiteFunctionKey]); + var script = %FunctionGetScript(GET_PRIVATE(this, CallSiteFunctionKey)); return script ? (script.type == TYPE_NATIVE) : false; } function CallSiteGetPosition() { - return this[CallSitePositionKey]; + return GET_PRIVATE(this, CallSitePositionKey); } function CallSiteIsConstructor() { - var receiver = this[CallSiteReceiverKey]; + var receiver = GET_PRIVATE(this, CallSiteReceiverKey); var constructor = (receiver != null && IS_OBJECT(receiver)) ? %GetDataProperty(receiver, "constructor") : null; if (!constructor) return false; - return this[CallSiteFunctionKey] === constructor; + return GET_PRIVATE(this, CallSiteFunctionKey) === constructor; } function CallSiteToString() { @@ -965,7 +970,7 @@ function CallSiteToString() { var isConstructor = this.isConstructor(); var isMethodCall = !(this.isToplevel() || isConstructor); if (isMethodCall) { - var typeName = GetTypeName(this[CallSiteReceiverKey], true); + var typeName = GetTypeName(GET_PRIVATE(this, CallSiteReceiverKey), true); var methodName = this.getMethodName(); if (functionName) { if (typeName && diff --git a/src/objects-debug.cc b/src/objects-debug.cc index 6ab2ddf..f59687a 100644 --- a/src/objects-debug.cc +++ b/src/objects-debug.cc @@ -243,6 +243,7 @@ void Symbol::SymbolVerify() { CHECK(HasHashCode()); CHECK_GT(Hash(), 0); CHECK(name()->IsUndefined() || name()->IsString()); + CHECK(flags()->IsSmi()); } diff --git a/src/objects-inl.h b/src/objects-inl.h index 92f52b3..a89c049 100644 --- a/src/objects-inl.h +++ b/src/objects-inl.h @@ -2692,6 +2692,8 @@ bool Name::Equals(Name* other) { ACCESSORS(Symbol, name, Object, kNameOffset) +ACCESSORS(Symbol, flags, Smi, kFlagsOffset) +BOOL_ACCESSORS(Symbol, flags, is_private, kPrivateBit) bool String::Equals(String* other) { diff --git a/src/objects-printer.cc b/src/objects-printer.cc index 37ae9b6..6d2811b 100644 --- a/src/objects-printer.cc +++ b/src/objects-printer.cc @@ -523,6 +523,7 @@ void Symbol::SymbolPrint(FILE* out) { PrintF(out, " - hash: %d\n", Hash()); PrintF(out, " - name: "); name()->ShortPrint(); + PrintF(out, " - private: %d\n", is_private()); PrintF(out, "\n"); } diff --git a/src/objects.h b/src/objects.h index d7fda9c..2fc1d23 100644 --- a/src/objects.h +++ b/src/objects.h @@ -352,6 +352,7 @@ const int kStubMinorKeyBits = kBitsPerInt - kSmiTagSize - kStubMajorKeyBits; V(SHORT_EXTERNAL_INTERNALIZED_STRING_WITH_ONE_BYTE_DATA_TYPE) \ \ V(SYMBOL_TYPE) \ + \ V(MAP_TYPE) \ V(CODE_TYPE) \ V(ODDBALL_TYPE) \ @@ -684,7 +685,7 @@ enum InstanceType { | kNotInternalizedTag, // Non-string names - SYMBOL_TYPE = kNotStringTag, // LAST_NAME_TYPE, FIRST_NONSTRING_TYPE + SYMBOL_TYPE = kNotStringTag, // FIRST_NONSTRING_TYPE, LAST_NAME_TYPE // Objects allocated in their own spaces (never in new space). MAP_TYPE, @@ -8380,6 +8381,11 @@ class Symbol: public Name { // [name]: the print name of a symbol, or undefined if none. DECL_ACCESSORS(name, Object) + DECL_ACCESSORS(flags, Smi) + + // [is_private]: whether this is a private symbol. + DECL_BOOLEAN_ACCESSORS(is_private) + // Casting. static inline Symbol* cast(Object* obj); @@ -8389,12 +8395,14 @@ class Symbol: public Name { // Layout description. static const int kNameOffset = Name::kSize; - static const int kSize = kNameOffset + kPointerSize; + static const int kFlagsOffset = kNameOffset + kPointerSize; + static const int kSize = kFlagsOffset + kPointerSize; - typedef FixedBodyDescriptor - BodyDescriptor; + typedef FixedBodyDescriptor BodyDescriptor; private: + static const int kPrivateBit = 0; + DISALLOW_IMPLICIT_CONSTRUCTORS(Symbol); }; diff --git a/src/parser.cc b/src/parser.cc index 71c6f67..b168919 100644 --- a/src/parser.cc +++ b/src/parser.cc @@ -4114,8 +4114,7 @@ FunctionLiteral* Parser::ParseFunctionLiteral( while (!done) { bool is_strict_reserved = false; Handle param_name = - ParseIdentifierOrStrictReservedWord(&is_strict_reserved, - CHECK_OK); + ParseIdentifierOrStrictReservedWord(&is_strict_reserved, CHECK_OK); // Store locations for possible future error reports. if (!name_loc.IsValid() && IsEvalOrArguments(param_name)) { diff --git a/src/runtime.cc b/src/runtime.cc index cd2c123..9d317ca 100644 --- a/src/runtime.cc +++ b/src/runtime.cc @@ -588,6 +588,19 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_CreateSymbol) { } +RUNTIME_FUNCTION(MaybeObject*, Runtime_CreatePrivateSymbol) { + HandleScope scope(isolate); + ASSERT(args.length() == 1); + Handle name(args[0], isolate); + RUNTIME_ASSERT(name->IsString() || name->IsUndefined()); + Symbol* symbol; + MaybeObject* maybe = isolate->heap()->AllocatePrivateSymbol(); + if (!maybe->To(&symbol)) return maybe; + if (name->IsString()) symbol->set_name(*name); + return symbol; +} + + RUNTIME_FUNCTION(MaybeObject*, Runtime_SymbolName) { SealHandleScope shs(isolate); ASSERT(args.length() == 1); @@ -596,6 +609,14 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_SymbolName) { } +RUNTIME_FUNCTION(MaybeObject*, Runtime_SymbolIsPrivate) { + SealHandleScope shs(isolate); + ASSERT(args.length() == 1); + CONVERT_ARG_CHECKED(Symbol, symbol, 0); + return isolate->heap()->ToBoolean(symbol->is_private()); +} + + RUNTIME_FUNCTION(MaybeObject*, Runtime_CreateJSProxy) { SealHandleScope shs(isolate); ASSERT(args.length() == 2); @@ -4790,6 +4811,19 @@ MaybeObject* Runtime::GetElementOrCharAt(Isolate* isolate, } +static Handle ToName(Isolate* isolate, Handle key) { + if (key->IsName()) { + return Handle::cast(key); + } else { + bool has_pending_exception = false; + Handle converted = + Execution::ToString(isolate, key, &has_pending_exception); + if (has_pending_exception) return Handle(); + return Handle::cast(converted); + } +} + + MaybeObject* Runtime::HasObjectProperty(Isolate* isolate, Handle object, Handle key) { @@ -4802,16 +4836,8 @@ MaybeObject* Runtime::HasObjectProperty(Isolate* isolate, } // Convert the key to a name - possibly by calling back into JavaScript. - Handle name; - if (key->IsName()) { - name = Handle::cast(key); - } else { - bool has_pending_exception = false; - Handle converted = - Execution::ToString(isolate, key, &has_pending_exception); - if (has_pending_exception) return Failure::Exception(); - name = Handle::cast(converted); - } + Handle name = ToName(isolate, key); + RETURN_IF_EMPTY_HANDLE(isolate, name); return isolate->heap()->ToBoolean(JSReceiver::HasProperty(object, name)); } @@ -4844,16 +4870,8 @@ MaybeObject* Runtime::GetObjectProperty(Isolate* isolate, } // Convert the key to a name - possibly by calling back into JavaScript. - Handle name; - if (key->IsName()) { - name = Handle::cast(key); - } else { - bool has_pending_exception = false; - Handle converted = - Execution::ToString(isolate, key, &has_pending_exception); - if (has_pending_exception) return Failure::Exception(); - name = Handle::cast(converted); - } + Handle name = ToName(isolate, key); + RETURN_IF_EMPTY_HANDLE(isolate, name); // Check if the name is trivially convertible to an index and get // the element if so. diff --git a/src/runtime.h b/src/runtime.h index 2254ed1..734875f 100644 --- a/src/runtime.h +++ b/src/runtime.h @@ -317,7 +317,9 @@ namespace internal { \ /* Harmony symbols */ \ F(CreateSymbol, 1, 1) \ + F(CreatePrivateSymbol, 1, 1) \ F(SymbolName, 1, 1) \ + F(SymbolIsPrivate, 1, 1) \ \ /* Harmony proxies */ \ F(CreateJSProxy, 2, 1) \ diff --git a/test/cctest/test-api.cc b/test/cctest/test-api.cc index 55cdfe6..15c7263 100644 --- a/test/cctest/test-api.cc +++ b/test/cctest/test-api.cc @@ -2744,6 +2744,70 @@ THREADED_TEST(SymbolProperties) { CHECK(!obj->Has(sym2)); CHECK_EQ(2002, obj->Get(sym1)->Int32Value()); CHECK_EQ(1, obj->GetOwnPropertyNames()->Length()); + + // Symbol properties are inherited. + v8::Local child = v8::Object::New(); + child->SetPrototype(obj); + CHECK(child->Has(sym1)); + CHECK_EQ(2002, child->Get(sym1)->Int32Value()); + CHECK_EQ(0, child->GetOwnPropertyNames()->Length()); +} + + +THREADED_TEST(PrivateProperties) { + LocalContext env; + v8::Isolate* isolate = env->GetIsolate(); + v8::HandleScope scope(isolate); + + v8::Local obj = v8::Object::New(); + v8::Local priv1 = v8::Private::New(isolate); + v8::Local priv2 = v8::Private::New(isolate, "my-private"); + + CcTest::heap()->CollectAllGarbage(i::Heap::kNoGCFlags); + + CHECK(priv2->Name()->Equals(v8::String::New("my-private"))); + + // Make sure delete of a non-existent private symbol property works. + CHECK(obj->DeletePrivate(priv1)); + CHECK(!obj->HasPrivate(priv1)); + + CHECK(obj->SetPrivate(priv1, v8::Integer::New(1503))); + CHECK(obj->HasPrivate(priv1)); + CHECK_EQ(1503, obj->GetPrivate(priv1)->Int32Value()); + CHECK(obj->SetPrivate(priv1, v8::Integer::New(2002))); + CHECK(obj->HasPrivate(priv1)); + CHECK_EQ(2002, obj->GetPrivate(priv1)->Int32Value()); + + CHECK_EQ(0, obj->GetOwnPropertyNames()->Length()); + int num_props = obj->GetPropertyNames()->Length(); + CHECK(obj->Set(v8::String::New("bla"), v8::Integer::New(20))); + CHECK_EQ(1, obj->GetOwnPropertyNames()->Length()); + CHECK_EQ(num_props + 1, obj->GetPropertyNames()->Length()); + + CcTest::heap()->CollectAllGarbage(i::Heap::kNoGCFlags); + + // Add another property and delete it afterwards to force the object in + // slow case. + CHECK(obj->SetPrivate(priv2, v8::Integer::New(2008))); + CHECK_EQ(2002, obj->GetPrivate(priv1)->Int32Value()); + CHECK_EQ(2008, obj->GetPrivate(priv2)->Int32Value()); + CHECK_EQ(2002, obj->GetPrivate(priv1)->Int32Value()); + CHECK_EQ(1, obj->GetOwnPropertyNames()->Length()); + + CHECK(obj->HasPrivate(priv1)); + CHECK(obj->HasPrivate(priv2)); + CHECK(obj->DeletePrivate(priv2)); + CHECK(obj->HasPrivate(priv1)); + CHECK(!obj->HasPrivate(priv2)); + CHECK_EQ(2002, obj->GetPrivate(priv1)->Int32Value()); + CHECK_EQ(1, obj->GetOwnPropertyNames()->Length()); + + // Private properties are inherited (for the time being). + v8::Local child = v8::Object::New(); + child->SetPrototype(obj); + CHECK(child->HasPrivate(priv1)); + CHECK_EQ(2002, child->GetPrivate(priv1)->Int32Value()); + CHECK_EQ(0, child->GetOwnPropertyNames()->Length()); } diff --git a/test/mjsunit/harmony/private.js b/test/mjsunit/harmony/private.js new file mode 100644 index 0000000..884e31b --- /dev/null +++ b/test/mjsunit/harmony/private.js @@ -0,0 +1,324 @@ +// Copyright 2013 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. + +// Flags: --harmony-symbols --harmony-collections +// Flags: --expose-gc --allow-natives-syntax + +var symbols = [] + +// Test different forms of constructor calls, all equivalent. +function TestNew() { + for (var i = 0; i < 2; ++i) { + for (var j = 0; j < 5; ++j) { + symbols.push(%CreatePrivateSymbol("66")) + symbols.push(Object(%CreatePrivateSymbol("66")).valueOf()) + } + gc() // Promote existing symbols and then allocate some more. + } +} +TestNew() + + +function TestType() { + for (var i in symbols) { + assertEquals("symbol", typeof symbols[i]) + assertTrue(typeof symbols[i] === "symbol") + assertTrue(%SymbolIsPrivate(symbols[i])) + assertEquals(null, %_ClassOf(symbols[i])) + assertEquals("Symbol", %_ClassOf(new Symbol(symbols[i]))) + assertEquals("Symbol", %_ClassOf(Object(symbols[i]))) + } +} +TestType() + + +function TestPrototype() { + for (var i in symbols) { + assertSame(Symbol.prototype, symbols[i].__proto__) + } +} +TestPrototype() + + +function TestConstructor() { + for (var i in symbols) { + assertSame(Symbol, symbols[i].__proto__.constructor) + } +} +TestConstructor() + + +function TestName() { + for (var i in symbols) { + var name = symbols[i].name + assertTrue(name === "66") + } +} +TestName() + + +function TestToString() { + for (var i in symbols) { + assertThrows(function() { String(symbols[i]) }, TypeError) + assertThrows(function() { symbols[i] + "" }, TypeError) + assertThrows(function() { symbols[i].toString() }, TypeError) + assertThrows(function() { (new Symbol(symbols[i])).toString() }, TypeError) + assertThrows(function() { Object(symbols[i]).toString() }, TypeError) + assertEquals("[object Symbol]", Object.prototype.toString.call(symbols[i])) + } +} +TestToString() + + +function TestToBoolean() { + for (var i in symbols) { + assertTrue(Boolean(symbols[i]).valueOf()) + assertFalse(!symbols[i]) + assertTrue(!!symbols[i]) + assertTrue(symbols[i] && true) + assertFalse(!symbols[i] && false) + assertTrue(!symbols[i] || true) + assertEquals(1, symbols[i] ? 1 : 2) + assertEquals(2, !symbols[i] ? 1 : 2) + if (!symbols[i]) assertUnreachable(); + if (symbols[i]) {} else assertUnreachable(); + } +} +TestToBoolean() + + +function TestToNumber() { + for (var i in symbols) { + assertSame(NaN, Number(symbols[i]).valueOf()) + assertSame(NaN, symbols[i] + 0) + } +} +TestToNumber() + + +function TestEquality() { + // Every symbol should equal itself, and non-strictly equal its wrapper. + for (var i in symbols) { + assertSame(symbols[i], symbols[i]) + assertEquals(symbols[i], symbols[i]) + assertTrue(Object.is(symbols[i], symbols[i])) + assertTrue(symbols[i] === symbols[i]) + assertTrue(symbols[i] == symbols[i]) + assertFalse(symbols[i] === new Symbol(symbols[i])) + assertFalse(new Symbol(symbols[i]) === symbols[i]) + assertTrue(symbols[i] == new Symbol(symbols[i])) + assertTrue(new Symbol(symbols[i]) == symbols[i]) + } + + // All symbols should be distinct. + for (var i = 0; i < symbols.length; ++i) { + for (var j = i + 1; j < symbols.length; ++j) { + assertFalse(Object.is(symbols[i], symbols[j])) + assertFalse(symbols[i] === symbols[j]) + assertFalse(symbols[i] == symbols[j]) + } + } + + // Symbols should not be equal to any other value (and the test terminates). + var values = [347, 1.275, NaN, "string", null, undefined, {}, function() {}] + for (var i in symbols) { + for (var j in values) { + assertFalse(symbols[i] === values[j]) + assertFalse(values[j] === symbols[i]) + assertFalse(symbols[i] == values[j]) + assertFalse(values[j] == symbols[i]) + } + } +} +TestEquality() + + +function TestGet() { + for (var i in symbols) { + assertThrows(function() { symbols[i].toString() }, TypeError) + assertEquals(symbols[i], symbols[i].valueOf()) + assertEquals(undefined, symbols[i].a) + assertEquals(undefined, symbols[i]["a" + "b"]) + assertEquals(undefined, symbols[i]["" + "1"]) + assertEquals(undefined, symbols[i][62]) + } +} +TestGet() + + +function TestSet() { + for (var i in symbols) { + symbols[i].toString = 0 + assertThrows(function() { symbols[i].toString() }, TypeError) + symbols[i].valueOf = 0 + assertEquals(symbols[i], symbols[i].valueOf()) + symbols[i].a = 0 + assertEquals(undefined, symbols[i].a) + symbols[i]["a" + "b"] = 0 + assertEquals(undefined, symbols[i]["a" + "b"]) + symbols[i][62] = 0 + assertEquals(undefined, symbols[i][62]) + } +} +TestSet() + + +function TestCollections() { + var set = new Set + var map = new Map + var weakmap = new WeakMap + for (var i in symbols) { + set.add(symbols[i]) + map.set(symbols[i], i) + weakmap.set(symbols[i], i) + } + assertEquals(symbols.length, set.size) + assertEquals(symbols.length, map.size) + for (var i in symbols) { + assertTrue(set.has(symbols[i])) + assertTrue(map.has(symbols[i])) + assertTrue(weakmap.has(symbols[i])) + assertEquals(i, map.get(symbols[i])) + assertEquals(i, weakmap.get(symbols[i])) + } + for (var i in symbols) { + assertTrue(set.delete(symbols[i])) + assertTrue(map.delete(symbols[i])) + assertTrue(weakmap.delete(symbols[i])) + } + assertEquals(0, set.size) + assertEquals(0, map.size) +} +TestCollections() + + + +function TestKeySet(obj) { + assertTrue(%HasFastProperties(obj)) + // Set the even symbols via assignment. + for (var i = 0; i < symbols.length; i += 2) { + obj[symbols[i]] = i + // Object should remain in fast mode until too many properties were added. + assertTrue(%HasFastProperties(obj) || i >= 30) + } +} + + +function TestKeyDefine(obj) { + // Set the odd symbols via defineProperty (as non-enumerable). + for (var i = 1; i < symbols.length; i += 2) { + Object.defineProperty(obj, symbols[i], {value: i, configurable: true}) + } +} + + +function TestKeyGet(obj) { + var obj2 = Object.create(obj) + for (var i in symbols) { + assertEquals(i|0, obj[symbols[i]]) + assertEquals(i|0, obj2[symbols[i]]) + } +} + + +function TestKeyHas() { + for (var i in symbols) { + assertTrue(symbols[i] in obj) + assertTrue(Object.hasOwnProperty.call(obj, symbols[i])) + } +} + + +function TestKeyEnum(obj) { + for (var name in obj) { + assertEquals("string", typeof name) + } +} + + +function TestKeyNames(obj) { + assertEquals(0, Object.keys(obj).length) + + var names = Object.getOwnPropertyNames(obj) + for (var i in names) { + assertEquals("string", typeof names[i]) + } +} + + +function TestKeyDescriptor(obj) { + for (var i in symbols) { + var desc = Object.getOwnPropertyDescriptor(obj, symbols[i]); + assertEquals(i|0, desc.value) + assertTrue(desc.configurable) + assertEquals(i % 2 == 0, desc.writable) + assertEquals(i % 2 == 0, desc.enumerable) + assertEquals(i % 2 == 0, + Object.prototype.propertyIsEnumerable.call(obj, symbols[i])) + } +} + + +function TestKeyDelete(obj) { + for (var i in symbols) { + delete obj[symbols[i]] + } + for (var i in symbols) { + assertEquals(undefined, Object.getOwnPropertyDescriptor(obj, symbols[i])) + } +} + + +var objs = [{}, [], Object.create(null), Object(1), new Map, function(){}] + +for (var i in objs) { + var obj = objs[i] + TestKeySet(obj) + TestKeyDefine(obj) + TestKeyGet(obj) + TestKeyHas(obj) + TestKeyEnum(obj) + TestKeyNames(obj) + TestKeyDescriptor(obj) + TestKeyDelete(obj) +} + + +function TestCachedKeyAfterScavenge() { + gc(); + // Keyed property lookup are cached. Hereby we assume that the keys are + // tenured, so that we only have to clear the cache between mark compacts, + // but not between scavenges. This must also apply for symbol keys. + var key = Symbol("key"); + var a = {}; + a[key] = "abc"; + + for (var i = 0; i < 1000000; i++) { + a[key] += "a"; // Allocations cause a scavenge. + } +} +TestCachedKeyAfterScavenge(); diff --git a/test/mjsunit/harmony/symbols.js b/test/mjsunit/harmony/symbols.js index 5eaa1a3..1fc3945 100644 --- a/test/mjsunit/harmony/symbols.js +++ b/test/mjsunit/harmony/symbols.js @@ -59,6 +59,7 @@ function TestType() { for (var i in symbols) { assertEquals("symbol", typeof symbols[i]) assertTrue(typeof symbols[i] === "symbol") + assertFalse(%SymbolIsPrivate(symbols[i])) assertEquals(null, %_ClassOf(symbols[i])) assertEquals("Symbol", %_ClassOf(new Symbol(symbols[i]))) assertEquals("Symbol", %_ClassOf(Object(symbols[i]))) -- 2.7.4