Provide private symbols through internal APIs
authorrossberg@chromium.org <rossberg@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Wed, 13 Nov 2013 10:34:06 +0000 (10:34 +0000)
committerrossberg@chromium.org <rossberg@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Wed, 13 Nov 2013 10:34:06 +0000 (10:34 +0000)
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

19 files changed:
include/v8.h
src/api.cc
src/array-iterator.js
src/factory.cc
src/factory.h
src/heap.cc
src/heap.h
src/macros.py
src/messages.js
src/objects-debug.cc
src/objects-inl.h
src/objects-printer.cc
src/objects.h
src/parser.cc
src/runtime.cc
src/runtime.h
test/cctest/test-api.cc
test/mjsunit/harmony/private.js [new file with mode: 0644]
test/mjsunit/harmony/symbols.js

index cb105ac..6d97e19 100644 (file)
@@ -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<Value> Name() const;
 
-  // Create a symbol without a print name.
-  static Local<Symbol> New(Isolate* isolate);
-
-  // Create a symbol with a print name.
-  static Local<Symbol> 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<Symbol> 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<Value> Name() const;
+
+  // Create a private symbol. If data is not NULL, it will be the print name.
+  static Local<Private> 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<Private> key);
+  bool SetPrivate(Handle<Private> key, Handle<Value> value);
+  bool DeletePrivate(Handle<Private> key);
+  Local<Value> GetPrivate(Handle<Private> 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
index c1d3d1b..e55e0aa 100644 (file)
@@ -3191,6 +3191,12 @@ bool v8::Object::ForceSet(v8::Handle<Value> key,
 }
 
 
+bool v8::Object::SetPrivate(v8::Handle<Private> key, v8::Handle<Value> value) {
+  v8::Handle<Value>* key_as_value = reinterpret_cast<v8::Handle<Value>*>(&key);
+  return Set(*key_as_value, value, DontEnum);
+}
+
+
 bool v8::Object::ForceDelete(v8::Handle<Value> key) {
   i::Isolate* isolate = Utils::OpenHandle(this)->GetIsolate();
   ON_BAILOUT(isolate, "v8::Object::ForceDelete()", return false);
@@ -3242,6 +3248,12 @@ Local<Value> v8::Object::Get(uint32_t index) {
 }
 
 
+Local<Value> v8::Object::GetPrivate(v8::Handle<Private> key) {
+  v8::Handle<Value>* key_as_value = reinterpret_cast<v8::Handle<Value>*>(&key);
+  return Get(*key_as_value);
+}
+
+
 PropertyAttribute v8::Object::GetPropertyAttributes(v8::Handle<Value> key) {
   i::Isolate* isolate = Utils::OpenHandle(this)->GetIsolate();
   ON_BAILOUT(isolate, "v8::Object::GetPropertyAttribute()",
@@ -3441,6 +3453,12 @@ bool v8::Object::Delete(v8::Handle<Value> key) {
 }
 
 
+bool v8::Object::DeletePrivate(v8::Handle<Private> key) {
+  v8::Handle<Value>* key_as_value = reinterpret_cast<v8::Handle<Value>*>(&key);
+  return Delete(*key_as_value);
+}
+
+
 bool v8::Object::Has(v8::Handle<Value> 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<Value> key) {
 }
 
 
+bool v8::Object::HasPrivate(v8::Handle<Private> key) {
+  v8::Handle<Value>* key_as_value = reinterpret_cast<v8::Handle<Value>*>(&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<Value> Symbol::Name() const {
 }
 
 
+Local<Value> Private::Name() const {
+  return reinterpret_cast<const Symbol*>(this)->Name();
+}
+
+
 double Number::Value() const {
   i::Handle<i::Object> obj = Utils::OpenHandle(this);
   return obj->Number();
@@ -6138,27 +6167,39 @@ Local<DataView> DataView::New(Handle<ArrayBuffer> array_buffer,
 }
 
 
-Local<Symbol> v8::Symbol::New(Isolate* isolate) {
+Local<Symbol> v8::Symbol::New(Isolate* isolate, const char* data, int length) {
   i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
   EnsureInitializedForIsolate(i_isolate, "v8::Symbol::New()");
   LOG_API(i_isolate, "Symbol::New()");
   ENTER_V8(i_isolate);
   i::Handle<i::Symbol> result = i_isolate->factory()->NewSymbol();
+  if (data != NULL) {
+    if (length == -1) length = i::StrLength(data);
+    i::Handle<i::String> name = i_isolate->factory()->NewStringFromUtf8(
+        i::Vector<const char>(data, length));
+    result->set_name(*name);
+  }
   return Utils::ToLocal(result);
 }
 
 
-Local<Symbol> v8::Symbol::New(Isolate* isolate, const char* data, int length) {
+Local<Private> v8::Private::New(
+    Isolate* isolate, const char* data, int length) {
   i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(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<i::String> name = i_isolate->factory()->NewStringFromUtf8(
-      i::Vector<const char>(data, length));
-  i::Handle<i::Symbol> result = i_isolate->factory()->NewSymbol();
-  result->set_name(*name);
-  return Utils::ToLocal(result);
+  i::Handle<i::Symbol> symbol = i_isolate->factory()->NewPrivateSymbol();
+  if (data != NULL) {
+    if (length == -1) length = i::StrLength(data);
+    i::Handle<i::String> name = i_isolate->factory()->NewStringFromUtf8(
+        i::Vector<const char>(data, length));
+    symbol->set_name(*name);
+  }
+  Local<Symbol> result = Utils::ToLocal(symbol);
+  v8::Handle<Private>* result_as_private =
+      reinterpret_cast<v8::Handle<Private>*>(&result);
+  return *result_as_private;
 }
 
 
index e734986..a8c5e00 100644 (file)
@@ -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);
index da85b12..6da9a2e 100644 (file)
@@ -365,6 +365,14 @@ Handle<Symbol> Factory::NewSymbol() {
 }
 
 
+Handle<Symbol> Factory::NewPrivateSymbol() {
+  CALL_HEAP_FUNCTION(
+      isolate(),
+      isolate()->heap()->AllocatePrivateSymbol(),
+      Symbol);
+}
+
+
 Handle<Context> Factory::NewNativeContext() {
   CALL_HEAP_FUNCTION(
       isolate(),
index 2b4ae7b..663f56f 100644 (file)
@@ -186,6 +186,7 @@ class Factory {
 
   // Create a symbol.
   Handle<Symbol> NewSymbol();
+  Handle<Symbol> NewPrivateSymbol();
 
   // Create a global (but otherwise uninitialized) context.
   Handle<Context> NewNativeContext();
index 2ba3919..b5d03bc 100644 (file)
@@ -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 =
index eea94f8..a0c85e5 100644 (file)
@@ -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();
index 1785d44..7bad23b 100644 (file)
@@ -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);
index bfd42de..926f294 100644 (file)
@@ -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 &&
index 6ab2ddf..f59687a 100644 (file)
@@ -243,6 +243,7 @@ void Symbol::SymbolVerify() {
   CHECK(HasHashCode());
   CHECK_GT(Hash(), 0);
   CHECK(name()->IsUndefined() || name()->IsString());
+  CHECK(flags()->IsSmi());
 }
 
 
index 92f52b3..a89c049 100644 (file)
@@ -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) {
index 37ae9b6..6d2811b 100644 (file)
@@ -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");
 }
 
index d7fda9c..2fc1d23 100644 (file)
@@ -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<kNameOffset, kNameOffset + kPointerSize, kSize>
-          BodyDescriptor;
+  typedef FixedBodyDescriptor<kNameOffset, kFlagsOffset, kSize> BodyDescriptor;
 
  private:
+  static const int kPrivateBit = 0;
+
   DISALLOW_IMPLICIT_CONSTRUCTORS(Symbol);
 };
 
index 71c6f67..b168919 100644 (file)
@@ -4114,8 +4114,7 @@ FunctionLiteral* Parser::ParseFunctionLiteral(
     while (!done) {
       bool is_strict_reserved = false;
       Handle<String> 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)) {
index cd2c123..9d317ca 100644 (file)
@@ -588,6 +588,19 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_CreateSymbol) {
 }
 
 
+RUNTIME_FUNCTION(MaybeObject*, Runtime_CreatePrivateSymbol) {
+  HandleScope scope(isolate);
+  ASSERT(args.length() == 1);
+  Handle<Object> 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<Name> ToName(Isolate* isolate, Handle<Object> key) {
+  if (key->IsName()) {
+    return Handle<Name>::cast(key);
+  } else {
+    bool has_pending_exception = false;
+    Handle<Object> converted =
+        Execution::ToString(isolate, key, &has_pending_exception);
+    if (has_pending_exception) return Handle<Name>();
+    return Handle<Name>::cast(converted);
+  }
+}
+
+
 MaybeObject* Runtime::HasObjectProperty(Isolate* isolate,
                                         Handle<JSReceiver> object,
                                         Handle<Object> key) {
@@ -4802,16 +4836,8 @@ MaybeObject* Runtime::HasObjectProperty(Isolate* isolate,
   }
 
   // Convert the key to a name - possibly by calling back into JavaScript.
-  Handle<Name> name;
-  if (key->IsName()) {
-    name = Handle<Name>::cast(key);
-  } else {
-    bool has_pending_exception = false;
-    Handle<Object> converted =
-        Execution::ToString(isolate, key, &has_pending_exception);
-    if (has_pending_exception) return Failure::Exception();
-    name = Handle<Name>::cast(converted);
-  }
+  Handle<Name> 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> name;
-  if (key->IsName()) {
-    name = Handle<Name>::cast(key);
-  } else {
-    bool has_pending_exception = false;
-    Handle<Object> converted =
-        Execution::ToString(isolate, key, &has_pending_exception);
-    if (has_pending_exception) return Failure::Exception();
-    name = Handle<Name>::cast(converted);
-  }
+  Handle<Name> 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.
index 2254ed1..734875f 100644 (file)
@@ -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) \
index 55cdfe6..15c7263 100644 (file)
@@ -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<v8::Object> 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<v8::Object> obj = v8::Object::New();
+  v8::Local<v8::Private> priv1 = v8::Private::New(isolate);
+  v8::Local<v8::Private> 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<v8::Object> 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 (file)
index 0000000..884e31b
--- /dev/null
@@ -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();
index 5eaa1a3..1fc3945 100644 (file)
@@ -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])))