Commit dc72037e authored by rossberg@chromium.org's avatar rossberg@chromium.org

ES6 symbols: extend V8 API to support symbols

Specifically:
- Introduce Symbol and SymbolObject classes.
- Generalise Object::Has and Object::Delete to arbitrary Value-typed keys.
- Generalise some places in the API implementation from String to Name.

It is not possible to intercept symbol-named properties. That is consistent with the idea that symbols are private and should not leak.

R=svenpanne@chromium.org
BUG=v8:2158

Review URL: https://codereview.chromium.org/13626002

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@14210 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent 5dfe7e68
......@@ -127,6 +127,8 @@ class StackFrame;
class StackTrace;
class String;
class StringObject;
class Symbol;
class SymbolObject;
class Uint32;
class Utils;
class Value;
......@@ -972,6 +974,12 @@ class V8EXPORT Value : public Data {
*/
V8_INLINE(bool IsString() const);
/**
* Returns true if this value is a symbol.
* This is an experimental feature.
*/
bool IsSymbol() const;
/**
* Returns true if this value is a function.
*/
......@@ -1032,6 +1040,12 @@ class V8EXPORT Value : public Data {
*/
bool IsStringObject() const;
/**
* Returns true if this value is a Symbol object.
* This is an experimental feature.
*/
bool IsSymbolObject() const;
/**
* Returns true if this value is a NativeError.
*/
......@@ -1311,7 +1325,11 @@ class V8EXPORT String : public Primitive {
/** Allocates a new string from 16-bit character codes.*/
static Local<String> New(const uint16_t* data, int length = -1);
/** Creates a symbol. Returns one if it exists already.*/
/**
* Creates an internalized string (historically called a "symbol",
* not to be confused with ES6 symbols). Returns one if it exists already.
* TODO(rossberg): Deprecate me when the new string API is here.
*/
static Local<String> NewSymbol(const char* data, int length = -1);
/**
......@@ -1449,6 +1467,29 @@ class V8EXPORT String : public Primitive {
};
/**
* A JavaScript symbol (ECMA-262 edition 6)
*
* This is an experimental feature. Use at your own risk.
*/
class V8EXPORT Symbol : public Primitive {
public:
// 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);
V8_INLINE(static Symbol* Cast(v8::Value* obj));
private:
Symbol();
static void CheckCast(v8::Value* obj);
};
/**
* A JavaScript number value (ECMA-262, 4.3.20)
*/
......@@ -1590,11 +1631,9 @@ class V8EXPORT Object : public Value {
*/
PropertyAttribute GetPropertyAttributes(Handle<Value> key);
// TODO(1245389): Replace the type-specific versions of these
// functions with generic ones that accept a Handle<Value> key.
bool Has(Handle<String> key);
bool Has(Handle<Value> key);
bool Delete(Handle<String> key);
bool Delete(Handle<Value> key);
// Delete a property on this object bypassing interceptors and
// ignoring dont-delete attributes.
......@@ -1981,6 +2020,27 @@ class V8EXPORT StringObject : public Object {
};
/**
* A Symbol object (ECMA-262 edition 6).
*
* This is an experimental feature. Use at your own risk.
*/
class V8EXPORT SymbolObject : public Object {
public:
static Local<Value> New(Isolate* isolate, Handle<Symbol> value);
/**
* Returns the Symbol held by the object.
*/
Local<Symbol> SymbolValue() const;
V8_INLINE(static SymbolObject* Cast(v8::Value* obj));
private:
static void CheckCast(v8::Value* obj);
};
/**
* An instance of the built-in RegExp constructor (ECMA-262, 15.10).
*/
......@@ -4859,6 +4919,14 @@ bool Value::QuickIsString() const {
}
Symbol* Symbol::Cast(v8::Value* value) {
#ifdef V8_ENABLE_CHECKS
CheckCast(value);
#endif
return static_cast<Symbol*>(value);
}
Number* Number::Cast(v8::Value* value) {
#ifdef V8_ENABLE_CHECKS
CheckCast(value);
......@@ -4891,6 +4959,14 @@ StringObject* StringObject::Cast(v8::Value* value) {
}
SymbolObject* SymbolObject::Cast(v8::Value* value) {
#ifdef V8_ENABLE_CHECKS
CheckCast(value);
#endif
return static_cast<SymbolObject*>(value);
}
NumberObject* NumberObject::Cast(v8::Value* value) {
#ifdef V8_ENABLE_CHECKS
CheckCast(value);
......
......@@ -2367,6 +2367,12 @@ bool Value::FullIsString() const {
}
bool Value::IsSymbol() const {
if (IsDeadCheck(i::Isolate::Current(), "v8::Value::IsSymbol()")) return false;
return Utils::OpenHandle(this)->IsSymbol();
}
bool Value::IsArray() const {
if (IsDeadCheck(i::Isolate::Current(), "v8::Value::IsArray()")) return false;
return Utils::OpenHandle(this)->IsJSArray();
......@@ -2451,6 +2457,16 @@ bool Value::IsStringObject() const {
}
bool Value::IsSymbolObject() const {
// TODO(svenpanne): these and other test functions should be written such
// that they do not use Isolate::Current().
i::Isolate* isolate = i::Isolate::Current();
if (IsDeadCheck(isolate, "v8::Value::IsSymbolObject()")) return false;
i::Handle<i::Object> obj = Utils::OpenHandle(this);
return obj->HasSpecificClassOf(isolate->heap()->Symbol_string());
}
bool Value::IsNumberObject() const {
i::Isolate* isolate = i::Isolate::Current();
if (IsDeadCheck(isolate, "v8::Value::IsNumberObject()")) return false;
......@@ -2664,6 +2680,15 @@ void v8::String::CheckCast(v8::Value* that) {
}
void v8::Symbol::CheckCast(v8::Value* that) {
if (IsDeadCheck(i::Isolate::Current(), "v8::Symbol::Cast()")) return;
i::Handle<i::Object> obj = Utils::OpenHandle(that);
ApiCheck(obj->IsSymbol(),
"v8::Symbol::Cast()",
"Could not convert to symbol");
}
void v8::Number::CheckCast(v8::Value* that) {
if (IsDeadCheck(i::Isolate::Current(), "v8::Number::Cast()")) return;
i::Handle<i::Object> obj = Utils::OpenHandle(that);
......@@ -2711,6 +2736,16 @@ void v8::StringObject::CheckCast(v8::Value* that) {
}
void v8::SymbolObject::CheckCast(v8::Value* that) {
i::Isolate* isolate = i::Isolate::Current();
if (IsDeadCheck(isolate, "v8::SymbolObject::Cast()")) return;
i::Handle<i::Object> obj = Utils::OpenHandle(that);
ApiCheck(obj->HasSpecificClassOf(isolate->heap()->Symbol_string()),
"v8::SymbolObject::Cast()",
"Could not convert to SymbolObject");
}
void v8::NumberObject::CheckCast(v8::Value* that) {
i::Isolate* isolate = i::Isolate::Current();
if (IsDeadCheck(isolate, "v8::NumberObject::Cast()")) return;
......@@ -3079,13 +3114,13 @@ PropertyAttribute v8::Object::GetPropertyAttributes(v8::Handle<Value> key) {
i::HandleScope scope(isolate);
i::Handle<i::JSObject> self = Utils::OpenHandle(this);
i::Handle<i::Object> key_obj = Utils::OpenHandle(*key);
if (!key_obj->IsString()) {
if (!key_obj->IsName()) {
EXCEPTION_PREAMBLE(isolate);
key_obj = i::Execution::ToString(key_obj, &has_pending_exception);
EXCEPTION_BAILOUT_CHECK(isolate, static_cast<PropertyAttribute>(NONE));
}
i::Handle<i::String> key_string = i::Handle<i::String>::cast(key_obj);
PropertyAttributes result = self->GetPropertyAttribute(*key_string);
i::Handle<i::Name> key_name = i::Handle<i::Name>::cast(key_obj);
PropertyAttributes result = self->GetPropertyAttribute(*key_name);
if (result == ABSENT) return static_cast<PropertyAttribute>(NONE);
return static_cast<PropertyAttribute>(result);
}
......@@ -3255,24 +3290,32 @@ Local<String> v8::Object::GetConstructorName() {
}
bool v8::Object::Delete(v8::Handle<String> key) {
bool v8::Object::Delete(v8::Handle<Value> key) {
i::Isolate* isolate = Utils::OpenHandle(this)->GetIsolate();
ON_BAILOUT(isolate, "v8::Object::Delete()", return false);
ENTER_V8(isolate);
i::HandleScope scope(isolate);
i::Handle<i::JSObject> self = Utils::OpenHandle(this);
i::Handle<i::String> key_obj = Utils::OpenHandle(*key);
return i::JSObject::DeleteProperty(self, key_obj)->IsTrue();
i::Handle<i::Object> key_obj = Utils::OpenHandle(*key);
EXCEPTION_PREAMBLE(isolate);
i::Handle<i::Object> obj = i::DeleteProperty(self, key_obj);
has_pending_exception = obj.is_null();
EXCEPTION_BAILOUT_CHECK(isolate, false);
return obj->IsTrue();
}
bool v8::Object::Has(v8::Handle<String> key) {
bool v8::Object::Has(v8::Handle<Value> key) {
i::Isolate* isolate = Utils::OpenHandle(this)->GetIsolate();
ON_BAILOUT(isolate, "v8::Object::Has()", return false);
ENTER_V8(isolate);
i::Handle<i::JSObject> self = Utils::OpenHandle(this);
i::Handle<i::String> key_obj = Utils::OpenHandle(*key);
return self->HasProperty(*key_obj);
i::Handle<i::JSReceiver> self = Utils::OpenHandle(this);
i::Handle<i::Object> key_obj = Utils::OpenHandle(*key);
EXCEPTION_PREAMBLE(isolate);
i::Handle<i::Object> obj = i::HasProperty(self, key_obj);
has_pending_exception = obj.is_null();
EXCEPTION_BAILOUT_CHECK(isolate, false);
return obj->IsTrue();
}
......@@ -4593,6 +4636,15 @@ const v8::String::ExternalAsciiStringResource*
}
Local<Value> Symbol::Name() const {
if (IsDeadCheck(i::Isolate::Current(), "v8::Symbol::Name()"))
return Local<Value>();
i::Handle<i::Symbol> sym = Utils::OpenHandle(this);
i::Handle<i::Object> name(sym->name(), sym->GetIsolate());
return Utils::ToLocal(name);
}
double Number::Value() const {
if (IsDeadCheck(i::Isolate::Current(), "v8::Number::Value()")) return 0;
i::Handle<i::Object> obj = Utils::OpenHandle(this);
......@@ -5457,6 +5509,29 @@ Local<v8::String> v8::StringObject::StringValue() const {
}
Local<v8::Value> v8::SymbolObject::New(Isolate* isolate, Handle<Symbol> value) {
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
EnsureInitializedForIsolate(i_isolate, "v8::SymbolObject::New()");
LOG_API(i_isolate, "SymbolObject::New");
ENTER_V8(i_isolate);
i::Handle<i::Object> obj =
i_isolate->factory()->ToObject(Utils::OpenHandle(*value));
return Utils::ToLocal(obj);
}
Local<v8::Symbol> v8::SymbolObject::SymbolValue() const {
i::Isolate* isolate = i::Isolate::Current();
if (IsDeadCheck(isolate, "v8::SymbolObject::SymbolValue()"))
return Local<v8::Symbol>();
LOG_API(isolate, "SymbolObject::SymbolValue");
i::Handle<i::Object> obj = Utils::OpenHandle(this);
i::Handle<i::JSValue> jsvalue = i::Handle<i::JSValue>::cast(obj);
return Utils::ToLocal(
i::Handle<i::Symbol>(i::Symbol::cast(jsvalue->value())));
}
Local<v8::Value> v8::Date::New(double time) {
i::Isolate* isolate = i::Isolate::Current();
EnsureInitializedForIsolate(isolate, "v8::Date::New()");
......@@ -5638,6 +5713,30 @@ Local<String> v8::String::NewSymbol(const char* data, int length) {
}
Local<Symbol> v8::Symbol::New(Isolate* isolate) {
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();
return Utils::ToLocal(result);
}
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(char)");
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);
}
Local<Number> v8::Number::New(double value) {
i::Isolate* isolate = i::Isolate::Current();
EnsureInitializedForIsolate(isolate, "v8::Number::New()");
......
......@@ -171,6 +171,7 @@ class RegisteredExtension {
V(Object, JSObject) \
V(Array, JSArray) \
V(String, String) \
V(Symbol, Symbol) \
V(Script, Object) \
V(Function, JSFunction) \
V(Message, JSObject) \
......@@ -196,6 +197,8 @@ class Utils {
v8::internal::Handle<v8::internal::JSFunction> obj);
static inline Local<String> ToLocal(
v8::internal::Handle<v8::internal::String> obj);
static inline Local<Symbol> ToLocal(
v8::internal::Handle<v8::internal::Symbol> obj);
static inline Local<RegExp> ToLocal(
v8::internal::Handle<v8::internal::JSRegExp> obj);
static inline Local<Object> ToLocal(
......@@ -268,6 +271,7 @@ MAKE_TO_LOCAL(ToLocal, Context, Context)
MAKE_TO_LOCAL(ToLocal, Object, Value)
MAKE_TO_LOCAL(ToLocal, JSFunction, Function)
MAKE_TO_LOCAL(ToLocal, String, String)
MAKE_TO_LOCAL(ToLocal, Symbol, Symbol)
MAKE_TO_LOCAL(ToLocal, JSRegExp, RegExp)
MAKE_TO_LOCAL(ToLocal, JSObject, Object)
MAKE_TO_LOCAL(ToLocal, JSArray, Array)
......
......@@ -252,15 +252,32 @@ Handle<Object> ForceSetProperty(Handle<JSObject> object,
}
Handle<Object> DeleteProperty(Handle<JSObject> object, Handle<Object> key) {
Isolate* isolate = object->GetIsolate();
CALL_HEAP_FUNCTION(isolate,
Runtime::DeleteObjectProperty(
isolate, object, key, JSReceiver::NORMAL_DELETION),
Object);
}
Handle<Object> ForceDeleteProperty(Handle<JSObject> object,
Handle<Object> key) {
Isolate* isolate = object->GetIsolate();
CALL_HEAP_FUNCTION(isolate,
Runtime::ForceDeleteObjectProperty(isolate, object, key),
Runtime::DeleteObjectProperty(
isolate, object, key, JSReceiver::FORCE_DELETION),
Object);
}
Handle<Object> HasProperty(Handle<JSReceiver> obj, Handle<Object> key) {
Isolate* isolate = obj->GetIsolate();
CALL_HEAP_FUNCTION(isolate,
Runtime::HasObjectProperty(isolate, obj, key), Object);
}
Handle<Object> GetProperty(Handle<JSReceiver> obj,
const char* name) {
Isolate* isolate = obj->GetIsolate();
......
......@@ -223,11 +223,13 @@ Handle<Object> ForceSetProperty(Handle<JSObject> object,
Handle<Object> value,
PropertyAttributes attributes);
Handle<Object> ForceDeleteProperty(Handle<JSObject> object,
Handle<Object> key);
Handle<Object> DeleteProperty(Handle<JSObject> object, Handle<Object> key);
Handle<Object> GetProperty(Handle<JSReceiver> obj,
const char* name);
Handle<Object> ForceDeleteProperty(Handle<JSObject> object, Handle<Object> key);
Handle<Object> HasProperty(Handle<JSReceiver> obj, Handle<Object> key);
Handle<Object> GetProperty(Handle<JSReceiver> obj, const char* name);
Handle<Object> GetProperty(Isolate* isolate,
Handle<Object> obj,
......
......@@ -3947,6 +3947,33 @@ MaybeObject* Runtime::GetElementOrCharAt(Isolate* isolate,
}
MaybeObject* Runtime::HasObjectProperty(Isolate* isolate,
Handle<JSReceiver> object,
Handle<Object> key) {
HandleScope scope(isolate);
// Check if the given key is an array index.
uint32_t index;
if (key->ToArrayIndex(&index)) {
return isolate->heap()->ToBoolean(object->HasElement(index));
}
// 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(key, &has_pending_exception);
if (has_pending_exception) return Failure::Exception();
name = Handle<Name>::cast(converted);
}
return isolate->heap()->ToBoolean(object->HasProperty(*name));
}
MaybeObject* Runtime::GetObjectProperty(Isolate* isolate,
Handle<Object> object,
Handle<Object> key) {
......@@ -4364,9 +4391,10 @@ MaybeObject* Runtime::ForceSetObjectProperty(Isolate* isolate,
}
MaybeObject* Runtime::ForceDeleteObjectProperty(Isolate* isolate,
Handle<JSReceiver> receiver,
Handle<Object> key) {
MaybeObject* Runtime::DeleteObjectProperty(Isolate* isolate,
Handle<JSReceiver> receiver,
Handle<Object> key,
JSReceiver::DeleteMode mode) {
HandleScope scope(isolate);
// Check if the given key is an array index.
......@@ -4382,7 +4410,7 @@ MaybeObject* Runtime::ForceDeleteObjectProperty(Isolate* isolate,
return isolate->heap()->true_value();
}
return receiver->DeleteElement(index, JSReceiver::FORCE_DELETION);
return receiver->DeleteElement(index, mode);
}
Handle<Name> name;
......@@ -4397,7 +4425,7 @@ MaybeObject* Runtime::ForceDeleteObjectProperty(Isolate* isolate,
}
if (name->IsString()) Handle<String>::cast(name)->TryFlatten();
return receiver->DeleteProperty(*name, JSReceiver::FORCE_DELETION);
return receiver->DeleteProperty(*name, mode);
}
......
......@@ -699,7 +699,13 @@ class Runtime : public AllStatic {
Handle<Object> value,
PropertyAttributes attr);
MUST_USE_RESULT static MaybeObject* ForceDeleteObjectProperty(
MUST_USE_RESULT static MaybeObject* DeleteObjectProperty(
Isolate* isolate,
Handle<JSReceiver> object,
Handle<Object> key,
JSReceiver::DeleteMode mode);
MUST_USE_RESULT static MaybeObject* HasObjectProperty(
Isolate* isolate,
Handle<JSReceiver> object,
Handle<Object> key);
......
......@@ -2178,6 +2178,88 @@ THREADED_TEST(IdentityHash) {
}
THREADED_TEST(SymbolProperties) {
i::FLAG_harmony_symbols = true;
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
v8::Local<v8::Object> obj = v8::Object::New();
v8::Local<v8::Symbol> sym1 = v8::Symbol::New(isolate);
v8::Local<v8::Symbol> sym2 = v8::Symbol::New(isolate, "my-symbol");
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
// Check basic symbol functionality.
CHECK(sym1->IsSymbol());
CHECK(sym2->IsSymbol());
CHECK(!obj->IsSymbol());
CHECK(sym1->Equals(sym1));
CHECK(sym2->Equals(sym2));
CHECK(!sym1->Equals(sym2));
CHECK(!sym2->Equals(sym1));
CHECK(sym1->StrictEquals(sym1));
CHECK(sym2->StrictEquals(sym2));
CHECK(!sym1->StrictEquals(sym2));
CHECK(!sym2->StrictEquals(sym1));
CHECK(sym2->Name()->Equals(v8::String::New("my-symbol")));
v8::Local<v8::Value> sym_val = sym2;
CHECK(sym_val->IsSymbol());
CHECK(sym_val->Equals(sym2));
CHECK(sym_val->StrictEquals(sym2));
CHECK(v8::Symbol::Cast(*sym_val)->Equals(sym2));
v8::Local<v8::Value> sym_obj = v8::SymbolObject::New(isolate, sym2);
CHECK(sym_obj->IsSymbolObject());
CHECK(!sym2->IsSymbolObject());
CHECK(!obj->IsSymbolObject());
CHECK(sym_obj->Equals(sym2));
CHECK(!sym_obj->StrictEquals(sym2));
CHECK(v8::SymbolObject::Cast(*sym_obj)->Equals(sym_obj));
CHECK(v8::SymbolObject::Cast(*sym_obj)->SymbolValue()->Equals(sym2));
// Make sure delete of a non-existent symbol property works.
CHECK(obj->Delete(sym1));
CHECK(!obj->Has(sym1));
CHECK(obj->Set(sym1, v8::Integer::New(1503)));
CHECK(obj->Has(sym1));
CHECK_EQ(1503, obj->Get(sym1)->Int32Value());
CHECK(obj->Set(sym1, v8::Integer::New(2002)));
CHECK(obj->Has(sym1));
CHECK_EQ(2002, obj->Get(sym1)->Int32Value());
CHECK_EQ(v8::None, obj->GetPropertyAttributes(sym1));
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());
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
// Add another property and delete it afterwards to force the object in
// slow case.
CHECK(obj->Set(sym2, v8::Integer::New(2008)));
CHECK_EQ(2002, obj->Get(sym1)->Int32Value());
CHECK_EQ(2008, obj->Get(sym2)->Int32Value());
CHECK_EQ(2002, obj->Get(sym1)->Int32Value());
CHECK_EQ(1, obj->GetOwnPropertyNames()->Length());
CHECK(obj->Has(sym1));
CHECK(obj->Has(sym2));
CHECK(obj->Delete(sym2));
CHECK(obj->Has(sym1));
CHECK(!obj->Has(sym2));
CHECK_EQ(2002, obj->Get(sym1)->Int32Value());
CHECK_EQ(1, obj->GetOwnPropertyNames()->Length());
}
THREADED_TEST(HiddenProperties) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment