Introduce an API to force the deletion of a property ignoring

interceptors and dont-delete attributes.

Minor change to the behavior of eval: throw exception when calling
eval in a context for which the global has been detached.  This
matches the behavior of both Firefox and Safari post navigation in the
browser.
Review URL: http://codereview.chromium.org/118374

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@2118 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
ager@chromium.org 2009-06-08 09:46:09 +00:00
parent f04220d25a
commit 24d22b567a
12 changed files with 248 additions and 46 deletions

View File

@ -1056,7 +1056,7 @@ class V8EXPORT Object : public Value {
Handle<Value> value,
PropertyAttribute attribs = None);
// Sets a local property on this object, bypassing interceptors and
// Sets a local property on this object bypassing interceptors and
// overriding accessors or read-only properties.
//
// Note that if the object has an interceptor the property will be set
@ -1067,13 +1067,21 @@ class V8EXPORT Object : public Value {
bool ForceSet(Handle<Value> key,
Handle<Value> value,
PropertyAttribute attribs = None);
Local<Value> Get(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 Delete(Handle<String> key);
// Delete a property on this object bypassing interceptors and
// ignoring dont-delete attributes.
bool ForceDelete(Handle<Value> key);
bool Has(uint32_t index);
bool Delete(uint32_t index);
/**

View File

@ -1892,6 +1892,19 @@ bool v8::Object::ForceSet(v8::Handle<Value> key,
}
bool v8::Object::ForceDelete(v8::Handle<Value> key) {
ON_BAILOUT("v8::Object::ForceDelete()", return false);
ENTER_V8;
i::Handle<i::JSObject> self = Utils::OpenHandle(this);
i::Handle<i::Object> key_obj = Utils::OpenHandle(*key);
EXCEPTION_PREAMBLE();
i::Handle<i::Object> obj = i::ForceDeleteProperty(self, key_obj);
has_pending_exception = obj.is_null();
EXCEPTION_BAILOUT_CHECK(false);
return obj->IsTrue();
}
Local<Value> v8::Object::Get(v8::Handle<Value> key) {
ON_BAILOUT("v8::Object::Get()", return Local<v8::Value>());
ENTER_V8;

View File

@ -222,6 +222,12 @@ Handle<Object> ForceSetProperty(Handle<JSObject> object,
}
Handle<Object> ForceDeleteProperty(Handle<JSObject> object,
Handle<Object> key) {
CALL_HEAP_FUNCTION(Runtime::ForceDeleteObjectProperty(object, key), Object);
}
Handle<Object> IgnoreAttributesAndSetLocalProperty(
Handle<JSObject> object,
Handle<String> key,
@ -231,6 +237,7 @@ Handle<Object> IgnoreAttributesAndSetLocalProperty(
IgnoreAttributesAndSetLocalProperty(*key, *value, attributes), Object);
}
Handle<Object> SetPropertyWithInterceptor(Handle<JSObject> object,
Handle<String> key,
Handle<Object> value,
@ -308,13 +315,15 @@ Handle<Object> GetHiddenProperties(Handle<JSObject> obj,
Handle<Object> DeleteElement(Handle<JSObject> obj,
uint32_t index) {
CALL_HEAP_FUNCTION(obj->DeleteElement(index), Object);
CALL_HEAP_FUNCTION(obj->DeleteElement(index, JSObject::NORMAL_DELETION),
Object);
}
Handle<Object> DeleteProperty(Handle<JSObject> obj,
Handle<String> prop) {
CALL_HEAP_FUNCTION(obj->DeleteProperty(*prop), Object);
CALL_HEAP_FUNCTION(obj->DeleteProperty(*prop, JSObject::NORMAL_DELETION),
Object);
}

View File

@ -202,6 +202,9 @@ Handle<Object> ForceSetProperty(Handle<JSObject> object,
Handle<Object> value,
PropertyAttributes attributes);
Handle<Object> ForceDeleteProperty(Handle<JSObject> object,
Handle<Object> key);
Handle<Object> IgnoreAttributesAndSetLocalProperty(Handle<JSObject> object,
Handle<String> key,
Handle<Object> value,

View File

@ -561,8 +561,8 @@ Register MacroAssembler::CheckMaps(JSObject* object, Register object_reg,
void MacroAssembler::CheckAccessGlobalProxy(Register holder_reg,
Register scratch,
Label* miss) {
Register scratch,
Label* miss) {
Label same_contexts;
ASSERT(!holder_reg.is(scratch));

View File

@ -385,7 +385,9 @@ Object* JSObject::SetLazyProperty(LookupResult* result,
}
Object* JSObject::DeleteLazyProperty(LookupResult* result, String* name) {
Object* JSObject::DeleteLazyProperty(LookupResult* result,
String* name,
DeleteMode mode) {
HandleScope scope;
Handle<JSObject> this_handle(this);
Handle<String> name_handle(name);
@ -393,7 +395,7 @@ Object* JSObject::DeleteLazyProperty(LookupResult* result, String* name) {
LoadLazy(Handle<JSObject>(JSObject::cast(result->GetLazyValue())),
&pending_exception);
if (pending_exception) return Failure::Exception();
return this_handle->DeleteProperty(*name_handle);
return this_handle->DeleteProperty(*name_handle, mode);
}
@ -2120,7 +2122,7 @@ Object* JSObject::NormalizeElements() {
}
Object* JSObject::DeletePropertyPostInterceptor(String* name) {
Object* JSObject::DeletePropertyPostInterceptor(String* name, DeleteMode mode) {
// Check local property, ignore interceptor.
LookupResult result;
LocalLookupRealNamedProperty(name, &result);
@ -2134,7 +2136,7 @@ Object* JSObject::DeletePropertyPostInterceptor(String* name) {
// Attempt to remove the property from the property dictionary.
Dictionary* dictionary = property_dictionary();
int entry = dictionary->FindStringEntry(name);
if (entry != -1) return dictionary->DeleteProperty(entry);
if (entry != -1) return dictionary->DeleteProperty(entry, mode);
return Heap::true_value();
}
@ -2164,13 +2166,15 @@ Object* JSObject::DeletePropertyWithInterceptor(String* name) {
return *v8::Utils::OpenHandle(*result);
}
}
Object* raw_result = this_handle->DeletePropertyPostInterceptor(*name_handle);
Object* raw_result =
this_handle->DeletePropertyPostInterceptor(*name_handle, NORMAL_DELETION);
RETURN_IF_SCHEDULED_EXCEPTION();
return raw_result;
}
Object* JSObject::DeleteElementPostInterceptor(uint32_t index) {
Object* JSObject::DeleteElementPostInterceptor(uint32_t index,
DeleteMode mode) {
if (HasFastElements()) {
uint32_t length = IsJSArray() ?
static_cast<uint32_t>(Smi::cast(JSArray::cast(this)->length())->value()) :
@ -2183,7 +2187,7 @@ Object* JSObject::DeleteElementPostInterceptor(uint32_t index) {
ASSERT(!HasFastElements());
Dictionary* dictionary = element_dictionary();
int entry = dictionary->FindNumberEntry(index);
if (entry != -1) return dictionary->DeleteProperty(entry);
if (entry != -1) return dictionary->DeleteProperty(entry, mode);
return Heap::true_value();
}
@ -2214,13 +2218,14 @@ Object* JSObject::DeleteElementWithInterceptor(uint32_t index) {
ASSERT(result->IsBoolean());
return *v8::Utils::OpenHandle(*result);
}
Object* raw_result = this_handle->DeleteElementPostInterceptor(index);
Object* raw_result =
this_handle->DeleteElementPostInterceptor(index, NORMAL_DELETION);
RETURN_IF_SCHEDULED_EXCEPTION();
return raw_result;
}
Object* JSObject::DeleteElement(uint32_t index) {
Object* JSObject::DeleteElement(uint32_t index, DeleteMode mode) {
// Check access rights if needed.
if (IsAccessCheckNeeded() &&
!Top::MayIndexedAccess(this, index, v8::ACCESS_DELETE)) {
@ -2232,10 +2237,14 @@ Object* JSObject::DeleteElement(uint32_t index) {
Object* proto = GetPrototype();
if (proto->IsNull()) return Heap::false_value();
ASSERT(proto->IsJSGlobalObject());
return JSGlobalObject::cast(proto)->DeleteElement(index);
return JSGlobalObject::cast(proto)->DeleteElement(index, mode);
}
if (HasIndexedInterceptor()) {
// Skip interceptor if forcing deletion.
if (mode == FORCE_DELETION) {
return DeleteElementPostInterceptor(index, mode);
}
return DeleteElementWithInterceptor(index);
}
@ -2250,13 +2259,13 @@ Object* JSObject::DeleteElement(uint32_t index) {
} else {
Dictionary* dictionary = element_dictionary();
int entry = dictionary->FindNumberEntry(index);
if (entry != -1) return dictionary->DeleteProperty(entry);
if (entry != -1) return dictionary->DeleteProperty(entry, mode);
}
return Heap::true_value();
}
Object* JSObject::DeleteProperty(String* name) {
Object* JSObject::DeleteProperty(String* name, DeleteMode mode) {
// ECMA-262, 3rd, 8.6.2.5
ASSERT(name->IsString());
@ -2271,23 +2280,32 @@ Object* JSObject::DeleteProperty(String* name) {
Object* proto = GetPrototype();
if (proto->IsNull()) return Heap::false_value();
ASSERT(proto->IsJSGlobalObject());
return JSGlobalObject::cast(proto)->DeleteProperty(name);
return JSGlobalObject::cast(proto)->DeleteProperty(name, mode);
}
uint32_t index = 0;
if (name->AsArrayIndex(&index)) {
return DeleteElement(index);
return DeleteElement(index, mode);
} else {
LookupResult result;
LocalLookup(name, &result);
if (!result.IsValid()) return Heap::true_value();
if (result.IsDontDelete()) return Heap::false_value();
// Ignore attributes if forcing a deletion.
if (result.IsDontDelete() && mode != FORCE_DELETION) {
return Heap::false_value();
}
// Check for interceptor.
if (result.type() == INTERCEPTOR) {
// Skip interceptor if forcing a deletion.
if (mode == FORCE_DELETION) {
return DeletePropertyPostInterceptor(name, mode);
}
return DeletePropertyWithInterceptor(name);
}
if (!result.IsLoaded()) {
return JSObject::cast(this)->DeleteLazyProperty(&result, name);
return JSObject::cast(this)->DeleteLazyProperty(&result,
name,
mode);
}
// Normalize object if needed.
Object* obj = NormalizeProperties(CLEAR_INOBJECT_PROPERTIES);
@ -2295,7 +2313,7 @@ Object* JSObject::DeleteProperty(String* name) {
// Make sure the properties are normalized before removing the entry.
Dictionary* dictionary = property_dictionary();
int entry = dictionary->FindStringEntry(name);
if (entry != -1) return dictionary->DeleteProperty(entry);
if (entry != -1) return dictionary->DeleteProperty(entry, mode);
return Heap::true_value();
}
}
@ -6943,9 +6961,12 @@ void Dictionary::RemoveNumberEntries(uint32_t from, uint32_t to) {
}
Object* Dictionary::DeleteProperty(int entry) {
Object* Dictionary::DeleteProperty(int entry, JSObject::DeleteMode mode) {
PropertyDetails details = DetailsAt(entry);
if (details.IsDontDelete()) return Heap::false_value();
// Ignore attributes if forcing a deletion.
if (details.IsDontDelete() && mode == JSObject::NORMAL_DELETION) {
return Heap::false_value();
}
SetEntry(entry, Heap::null_value(), Heap::null_value(), Smi::FromInt(0));
ElementRemoved();
return Heap::true_value();

View File

@ -1275,9 +1275,12 @@ class JSObject: public HeapObject {
return GetLocalPropertyAttribute(name) != ABSENT;
}
Object* DeleteProperty(String* name);
Object* DeleteElement(uint32_t index);
Object* DeleteLazyProperty(LookupResult* result, String* name);
enum DeleteMode { NORMAL_DELETION, FORCE_DELETION };
Object* DeleteProperty(String* name, DeleteMode mode);
Object* DeleteElement(uint32_t index, DeleteMode mode);
Object* DeleteLazyProperty(LookupResult* result,
String* name,
DeleteMode mode);
// Tests for the fast common case for property enumeration.
bool IsSimpleEnum();
@ -1519,10 +1522,10 @@ class JSObject: public HeapObject {
Object* GetElementPostInterceptor(JSObject* receiver, uint32_t index);
Object* DeletePropertyPostInterceptor(String* name);
Object* DeletePropertyPostInterceptor(String* name, DeleteMode mode);
Object* DeletePropertyWithInterceptor(String* name);
Object* DeleteElementPostInterceptor(uint32_t index);
Object* DeleteElementPostInterceptor(uint32_t index, DeleteMode mode);
Object* DeleteElementWithInterceptor(uint32_t index);
PropertyAttributes GetPropertyAttributePostInterceptor(JSObject* receiver,
@ -2057,7 +2060,7 @@ class Dictionary: public DictionaryBase {
int FindNumberEntry(uint32_t index);
// Delete a property from the dictionary.
Object* DeleteProperty(int entry);
Object* DeleteProperty(int entry, JSObject::DeleteMode mode);
// Type specific at put (default NONE attributes is used when adding).
Object* AtStringPut(String* key, Object* value);
@ -2628,7 +2631,7 @@ class Map: public HeapObject {
static const int kHasInstanceCallHandler = 6;
static const int kIsAccessCheckNeeded = 7;
// Bit positions for but field 2
// Bit positions for bit field 2
static const int kNeedsLoading = 0;
private:

View File

@ -2780,6 +2780,42 @@ Object* Runtime::ForceSetObjectProperty(Handle<JSObject> js_object,
}
Object* Runtime::ForceDeleteObjectProperty(Handle<JSObject> js_object,
Handle<Object> key) {
HandleScope scope;
// Check if the given key is an array index.
uint32_t index;
if (Array::IndexFromObject(*key, &index)) {
// In Firefox/SpiderMonkey, Safari and Opera you can access the
// characters of a string using [] notation. In the case of a
// String object we just need to redirect the deletion to the
// underlying string if the index is in range. Since the
// underlying string does nothing with the deletion, we can ignore
// such deletions.
if (js_object->IsStringObjectWithCharacterAt(index)) {
return Heap::true_value();
}
return js_object->DeleteElement(index, JSObject::FORCE_DELETION);
}
Handle<String> key_string;
if (key->IsString()) {
key_string = Handle<String>::cast(key);
} else {
// Call-back into JavaScript to convert the key to a string.
bool has_pending_exception = false;
Handle<Object> converted = Execution::ToString(key, &has_pending_exception);
if (has_pending_exception) return Failure::Exception();
key_string = Handle<String>::cast(converted);
}
key_string->TryFlattenIfNotFlat();
return js_object->DeleteProperty(*key_string, JSObject::FORCE_DELETION);
}
static Object* Runtime_SetProperty(Arguments args) {
NoHandleAllocation ha;
RUNTIME_ASSERT(args.length() == 3 || args.length() == 4);
@ -2831,7 +2867,7 @@ static Object* Runtime_DeleteProperty(Arguments args) {
CONVERT_CHECKED(JSObject, object, args[0]);
CONVERT_CHECKED(String, key, args[1]);
return object->DeleteProperty(key);
return object->DeleteProperty(key, JSObject::NORMAL_DELETION);
}

View File

@ -387,6 +387,9 @@ class Runtime : public AllStatic {
Handle<Object> value,
PropertyAttributes attr);
static Object* ForceDeleteObjectProperty(Handle<JSObject> object,
Handle<Object> key);
static Object* GetObjectProperty(Handle<Object> object, Handle<Object> key);
// This function is used in FunctionNameUsing* tests.

View File

@ -115,11 +115,15 @@ function GlobalParseFloat(string) {
function GlobalEval(x) {
if (!IS_STRING(x)) return x;
if (this !== global && this !== %GlobalReceiver(global)) {
throw new $EvalError('The "this" object passed to eval must ' +
var global_receiver = %GlobalReceiver(global);
var this_is_global_receiver = (this === global_receiver);
var global_is_detached = (global === global_receiver);
if (!this_is_global_receiver || global_is_detached) {
throw new $EvalError('The "this" object passed to eval must ' +
'be the global object from which eval originated');
}
var f = %CompileString(x, false);
if (!IS_FUNCTION(f)) return f;

View File

@ -4515,10 +4515,6 @@ THREADED_TEST(EvalAliasedDynamic) {
v8::HandleScope scope;
LocalContext current;
// This sets 'global' to the real global object (as opposed to the
// proxy). It is highly implementation dependent, so take care.
current->Global()->Set(v8_str("global"), current->Global()->GetPrototype());
// Tests where aliased eval can only be resolved dynamically.
Local<Script> script =
Script::Compile(v8_str("function f(x) { "
@ -4527,7 +4523,7 @@ THREADED_TEST(EvalAliasedDynamic) {
"}"
"foo = 0;"
"result1 = f(new Object());"
"result2 = f(global);"
"result2 = f(this);"
"var x = new Object();"
"x.eval = function(x) { return 1; };"
"result3 = f(x);"));
@ -4542,7 +4538,7 @@ THREADED_TEST(EvalAliasedDynamic) {
" var bar = 2;"
" with (x) { return eval('bar'); }"
"}"
"f(global)"));
"f(this)"));
script->Run();
CHECK(try_catch.HasCaught());
try_catch.Reset();
@ -4629,6 +4625,44 @@ THREADED_TEST(CrossEval) {
}
// Test that calling eval in a context which has been detached from
// its global throws an exception. This behavior is consistent with
// other JavaScript implementations.
THREADED_TEST(EvalInDetachedGlobal) {
v8::HandleScope scope;
v8::Persistent<Context> context0 = Context::New();
v8::Persistent<Context> context1 = Context::New();
// Setup function in context0 that uses eval from context0.
context0->Enter();
v8::Handle<v8::Value> fun =
CompileRun("var x = 42;"
"(function() {"
" var e = eval;"
" return function(s) { return e(s); }"
"})()");
context0->Exit();
// Put the function into context1 and call it before and after
// detaching the global. Before detaching, the call succeeds and
// after detaching and exception is thrown.
context1->Enter();
context1->Global()->Set(v8_str("fun"), fun);
v8::Handle<v8::Value> x_value = CompileRun("fun('x')");
CHECK_EQ(42, x_value->Int32Value());
context0->DetachGlobal();
v8::TryCatch catcher;
x_value = CompileRun("fun('x')");
CHECK(x_value.IsEmpty());
CHECK(catcher.HasCaught());
context1->Exit();
context1.Dispose();
context0.Dispose();
}
THREADED_TEST(CrossLazyLoad) {
v8::HandleScope scope;
LocalContext other;
@ -6723,6 +6757,74 @@ TEST(ForceSetWithInterceptor) {
}
THREADED_TEST(ForceDelete) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ = v8::ObjectTemplate::New();
LocalContext context(NULL, templ);
v8::Handle<v8::Object> global = context->Global();
// Ordinary properties
v8::Handle<v8::String> simple_property = v8::String::New("p");
global->Set(simple_property, v8::Int32::New(4), v8::DontDelete);
CHECK_EQ(4, global->Get(simple_property)->Int32Value());
// This should fail because the property is dont-delete.
CHECK(!global->Delete(simple_property));
CHECK_EQ(4, global->Get(simple_property)->Int32Value());
// This should succeed even though the property is dont-delete.
CHECK(global->ForceDelete(simple_property));
CHECK(global->Get(simple_property)->IsUndefined());
}
static int force_delete_interceptor_count = 0;
static bool pass_on_delete = false;
static v8::Handle<v8::Boolean> ForceDeleteDeleter(
v8::Local<v8::String> name,
const v8::AccessorInfo& info) {
force_delete_interceptor_count++;
if (pass_on_delete) {
return v8::Handle<v8::Boolean>();
} else {
return v8::True();
}
}
THREADED_TEST(ForceDeleteWithInterceptor) {
force_delete_interceptor_count = 0;
pass_on_delete = false;
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> templ = v8::ObjectTemplate::New();
templ->SetNamedPropertyHandler(0, 0, 0, ForceDeleteDeleter);
LocalContext context(NULL, templ);
v8::Handle<v8::Object> global = context->Global();
v8::Handle<v8::String> some_property = v8::String::New("a");
global->Set(some_property, v8::Integer::New(42), v8::DontDelete);
// Deleting a property should get intercepted and nothing should
// happen.
CHECK_EQ(0, force_delete_interceptor_count);
CHECK(global->Delete(some_property));
CHECK_EQ(1, force_delete_interceptor_count);
CHECK_EQ(42, global->Get(some_property)->Int32Value());
// Deleting the property when the interceptor returns an empty
// handle should not delete the property since it is DontDelete.
pass_on_delete = true;
CHECK(!global->Delete(some_property));
CHECK_EQ(2, force_delete_interceptor_count);
CHECK_EQ(42, global->Get(some_property)->Int32Value());
// Forcing the property to be deleted should delete the value
// without calling the interceptor.
CHECK(global->ForceDelete(some_property));
CHECK(global->Get(some_property)->IsUndefined());
CHECK_EQ(2, force_delete_interceptor_count);
}
v8::Persistent<Context> calling_context0;
v8::Persistent<Context> calling_context1;
v8::Persistent<Context> calling_context2;

View File

@ -553,7 +553,7 @@ TEST(ObjectProperties) {
CHECK(obj->HasLocalProperty(first));
// delete first
CHECK(obj->DeleteProperty(first));
CHECK(obj->DeleteProperty(first, JSObject::NORMAL_DELETION));
CHECK(!obj->HasLocalProperty(first));
// add first and then second
@ -563,9 +563,9 @@ TEST(ObjectProperties) {
CHECK(obj->HasLocalProperty(second));
// delete first and then second
CHECK(obj->DeleteProperty(first));
CHECK(obj->DeleteProperty(first, JSObject::NORMAL_DELETION));
CHECK(obj->HasLocalProperty(second));
CHECK(obj->DeleteProperty(second));
CHECK(obj->DeleteProperty(second, JSObject::NORMAL_DELETION));
CHECK(!obj->HasLocalProperty(first));
CHECK(!obj->HasLocalProperty(second));
@ -576,9 +576,9 @@ TEST(ObjectProperties) {
CHECK(obj->HasLocalProperty(second));
// delete second and then first
CHECK(obj->DeleteProperty(second));
CHECK(obj->DeleteProperty(second, JSObject::NORMAL_DELETION));
CHECK(obj->HasLocalProperty(first));
CHECK(obj->DeleteProperty(first));
CHECK(obj->DeleteProperty(first, JSObject::NORMAL_DELETION));
CHECK(!obj->HasLocalProperty(first));
CHECK(!obj->HasLocalProperty(second));