Implement correct checking for inherited readonliness on assignment.
Removes 6 out of 8 of our remaining unintentional failures on test262. Also fixes treatment of inherited setters added after the fact. Specifically: - In the runtime, when looking for setter callbacks in the prototype chain, also look for read-only properties. If one is found, reject (exception in strict mode). If a proxy is found, invoke proper trap. Note: this folds in the CanPut function from the spec and avoids an extra lookup over the prototype chain. - In generated code for stores, insert a test for the maps from the prototype chain, but only up to the object where the property already exists (which may be the object itself). In Hydrogen, if the found property is read-only or not cacheable (e.g. a proxy), bail out; in a stub, generate an unconditional miss (to get an exception in strict mode). - Add test cases and adapt existing test expectations. R=mstarzinger@chromium.org BUG= TEST= Review URL: https://chromiumcodereview.appspot.com/10388047 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@11694 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
parent
dd54b0acf7
commit
e4c472a7af
@ -435,22 +435,59 @@ void StubCompiler::GenerateStoreField(MacroAssembler* masm,
|
||||
Handle<JSObject> object,
|
||||
int index,
|
||||
Handle<Map> transition,
|
||||
Handle<String> name,
|
||||
Register receiver_reg,
|
||||
Register name_reg,
|
||||
Register scratch,
|
||||
Register scratch1,
|
||||
Register scratch2,
|
||||
Label* miss_label) {
|
||||
// r0 : value
|
||||
Label exit;
|
||||
|
||||
LookupResult lookup(masm->isolate());
|
||||
object->Lookup(*name, &lookup);
|
||||
if (lookup.IsFound() && (lookup.IsReadOnly() || !lookup.IsCacheable())) {
|
||||
// In sloppy mode, we could just return the value and be done. However, we
|
||||
// might be in strict mode, where we have to throw. Since we cannot tell,
|
||||
// go into slow case unconditionally.
|
||||
__ jmp(miss_label);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that the map of the object hasn't changed.
|
||||
CompareMapMode mode = transition.is_null() ? ALLOW_ELEMENT_TRANSITION_MAPS
|
||||
: REQUIRE_EXACT_MAP;
|
||||
__ CheckMap(receiver_reg, scratch, Handle<Map>(object->map()), miss_label,
|
||||
__ CheckMap(receiver_reg, scratch1, Handle<Map>(object->map()), miss_label,
|
||||
DO_SMI_CHECK, mode);
|
||||
|
||||
// Perform global security token check if needed.
|
||||
if (object->IsJSGlobalProxy()) {
|
||||
__ CheckAccessGlobalProxy(receiver_reg, scratch, miss_label);
|
||||
__ CheckAccessGlobalProxy(receiver_reg, scratch1, miss_label);
|
||||
}
|
||||
|
||||
// Check that we are allowed to write this.
|
||||
if (!transition.is_null() && object->GetPrototype()->IsJSObject()) {
|
||||
JSObject* holder;
|
||||
if (lookup.IsFound()) {
|
||||
holder = lookup.holder();
|
||||
} else {
|
||||
// Find the top object.
|
||||
holder = *object;
|
||||
do {
|
||||
holder = JSObject::cast(holder->GetPrototype());
|
||||
} while (holder->GetPrototype()->IsJSObject());
|
||||
}
|
||||
// We need an extra register, push
|
||||
__ push(name_reg);
|
||||
Label miss_pop, done_check;
|
||||
CheckPrototypes(object, receiver_reg, Handle<JSObject>(holder), name_reg,
|
||||
scratch1, scratch2, name, &miss_pop);
|
||||
__ jmp(&done_check);
|
||||
__ bind(&miss_pop);
|
||||
__ pop(name_reg);
|
||||
__ jmp(miss_label);
|
||||
__ bind(&done_check);
|
||||
__ pop(name_reg);
|
||||
}
|
||||
|
||||
// Stub never generated for non-global objects that require access
|
||||
@ -508,15 +545,16 @@ void StubCompiler::GenerateStoreField(MacroAssembler* masm,
|
||||
__ RecordWriteField(receiver_reg,
|
||||
offset,
|
||||
name_reg,
|
||||
scratch,
|
||||
scratch1,
|
||||
kLRHasNotBeenSaved,
|
||||
kDontSaveFPRegs);
|
||||
} else {
|
||||
// Write to the properties array.
|
||||
int offset = index * kPointerSize + FixedArray::kHeaderSize;
|
||||
// Get the properties array
|
||||
__ ldr(scratch, FieldMemOperand(receiver_reg, JSObject::kPropertiesOffset));
|
||||
__ str(r0, FieldMemOperand(scratch, offset));
|
||||
__ ldr(scratch1,
|
||||
FieldMemOperand(receiver_reg, JSObject::kPropertiesOffset));
|
||||
__ str(r0, FieldMemOperand(scratch1, offset));
|
||||
|
||||
// Skip updating write barrier if storing a smi.
|
||||
__ JumpIfSmi(r0, &exit);
|
||||
@ -524,7 +562,7 @@ void StubCompiler::GenerateStoreField(MacroAssembler* masm,
|
||||
// Update the write barrier for the array address.
|
||||
// Ok to clobber receiver_reg and name_reg, since we return.
|
||||
__ mov(name_reg, r0);
|
||||
__ RecordWriteField(scratch,
|
||||
__ RecordWriteField(scratch1,
|
||||
offset,
|
||||
name_reg,
|
||||
receiver_reg,
|
||||
@ -2568,7 +2606,13 @@ Handle<Code> StoreStubCompiler::CompileStoreField(Handle<JSObject> object,
|
||||
// -----------------------------------
|
||||
Label miss;
|
||||
|
||||
GenerateStoreField(masm(), object, index, transition, r1, r2, r3, &miss);
|
||||
GenerateStoreField(masm(),
|
||||
object,
|
||||
index,
|
||||
transition,
|
||||
name,
|
||||
r1, r2, r3, r4,
|
||||
&miss);
|
||||
__ bind(&miss);
|
||||
Handle<Code> ic = masm()->isolate()->builtins()->StoreIC_Miss();
|
||||
__ Jump(ic, RelocInfo::CODE_TARGET);
|
||||
@ -3114,7 +3158,13 @@ Handle<Code> KeyedStoreStubCompiler::CompileStoreField(Handle<JSObject> object,
|
||||
|
||||
// r3 is used as scratch register. r1 and r2 keep their values if a jump to
|
||||
// the miss label is generated.
|
||||
GenerateStoreField(masm(), object, index, transition, r2, r1, r3, &miss);
|
||||
GenerateStoreField(masm(),
|
||||
object,
|
||||
index,
|
||||
transition,
|
||||
name,
|
||||
r2, r1, r3, r4,
|
||||
&miss);
|
||||
__ bind(&miss);
|
||||
|
||||
__ DecrementCounter(counters->keyed_store_field(), 1, r3, r4);
|
||||
|
@ -4359,7 +4359,8 @@ void HGraphBuilder::VisitObjectLiteral(ObjectLiteral* expr) {
|
||||
property->RecordTypeFeedback(oracle());
|
||||
CHECK_ALIVE(VisitForValue(value));
|
||||
HValue* value = Pop();
|
||||
HInstruction* store = BuildStoreNamed(literal, value, property);
|
||||
HInstruction* store;
|
||||
CHECK_ALIVE(store = BuildStoreNamed(literal, value, property));
|
||||
AddInstruction(store);
|
||||
if (store->HasObservableSideEffects()) AddSimulate(key->id());
|
||||
} else {
|
||||
@ -4526,11 +4527,38 @@ HInstruction* HGraphBuilder::BuildStoreNamedField(HValue* object,
|
||||
Handle<Map> type,
|
||||
LookupResult* lookup,
|
||||
bool smi_and_map_check) {
|
||||
ASSERT(lookup->IsFound());
|
||||
if (smi_and_map_check) {
|
||||
AddInstruction(new(zone()) HCheckNonSmi(object));
|
||||
AddInstruction(HCheckMaps::NewWithTransitions(object, type));
|
||||
}
|
||||
|
||||
// If the property does not exist yet, we have to check that it wasn't made
|
||||
// readonly or turned into a setter by some meanwhile modifications on the
|
||||
// prototype chain.
|
||||
if (!lookup->IsProperty()) {
|
||||
Object* proto = type->prototype();
|
||||
// First check that the prototype chain isn't affected already.
|
||||
LookupResult proto_result(isolate());
|
||||
proto->Lookup(*name, &proto_result);
|
||||
if (proto_result.IsProperty()) {
|
||||
// If the inherited property could induce readonly-ness, bail out.
|
||||
if (proto_result.IsReadOnly() || !proto_result.IsCacheable()) {
|
||||
Bailout("improper object on prototype chain for store");
|
||||
return NULL;
|
||||
}
|
||||
// We only need to check up to the preexisting property.
|
||||
proto = proto_result.holder();
|
||||
} else {
|
||||
// Otherwise, find the top prototype.
|
||||
while (proto->GetPrototype()->IsJSObject()) proto = proto->GetPrototype();
|
||||
}
|
||||
ASSERT(proto->IsJSObject());
|
||||
AddInstruction(new(zone()) HCheckPrototypeMaps(
|
||||
Handle<JSObject>(JSObject::cast(type->prototype())),
|
||||
Handle<JSObject>(JSObject::cast(proto))));
|
||||
}
|
||||
|
||||
int index = ComputeLoadStoreFieldIndex(type, name, lookup);
|
||||
bool is_in_object = index < 0;
|
||||
int offset = index * kPointerSize;
|
||||
@ -4687,8 +4715,9 @@ void HGraphBuilder::HandlePolymorphicStoreNamedField(Assignment* expr,
|
||||
current_block()->Finish(compare);
|
||||
|
||||
set_current_block(if_true);
|
||||
HInstruction* instr =
|
||||
BuildStoreNamedField(object, name, value, map, &lookup, false);
|
||||
HInstruction* instr;
|
||||
CHECK_ALIVE(instr =
|
||||
BuildStoreNamedField(object, name, value, map, &lookup, false));
|
||||
instr->set_position(expr->position());
|
||||
// Goto will add the HSimulate for the store.
|
||||
AddInstruction(instr);
|
||||
@ -4756,10 +4785,8 @@ void HGraphBuilder::HandlePropertyAssignment(Assignment* expr) {
|
||||
ASSERT(!name.is_null());
|
||||
|
||||
SmallMapList* types = expr->GetReceiverTypes();
|
||||
LookupResult lookup(isolate());
|
||||
|
||||
if (expr->IsMonomorphic()) {
|
||||
instr = BuildStoreNamed(object, value, expr);
|
||||
CHECK_ALIVE(instr = BuildStoreNamed(object, value, expr));
|
||||
|
||||
} else if (types != NULL && types->length() > 1) {
|
||||
HandlePolymorphicStoreNamedField(expr, object, value, types, name);
|
||||
@ -4938,7 +4965,8 @@ void HGraphBuilder::HandleCompoundAssignment(Assignment* expr) {
|
||||
PushAndAdd(instr);
|
||||
if (instr->HasObservableSideEffects()) AddSimulate(operation->id());
|
||||
|
||||
HInstruction* store = BuildStoreNamed(obj, instr, prop);
|
||||
HInstruction* store;
|
||||
CHECK_ALIVE(store = BuildStoreNamed(obj, instr, prop));
|
||||
AddInstruction(store);
|
||||
// Drop the simulated receiver and value. Return the value.
|
||||
Drop(2);
|
||||
@ -7213,7 +7241,8 @@ void HGraphBuilder::VisitCountOperation(CountOperation* expr) {
|
||||
after = BuildIncrement(returns_original_input, expr);
|
||||
input = Pop();
|
||||
|
||||
HInstruction* store = BuildStoreNamed(obj, after, prop);
|
||||
HInstruction* store;
|
||||
CHECK_ALIVE(store = BuildStoreNamed(obj, after, prop));
|
||||
AddInstruction(store);
|
||||
|
||||
// Overwrite the receiver in the bailout environment with the result
|
||||
|
@ -745,10 +745,22 @@ void StubCompiler::GenerateStoreField(MacroAssembler* masm,
|
||||
Handle<JSObject> object,
|
||||
int index,
|
||||
Handle<Map> transition,
|
||||
Handle<String> name,
|
||||
Register receiver_reg,
|
||||
Register name_reg,
|
||||
Register scratch,
|
||||
Register scratch1,
|
||||
Register scratch2,
|
||||
Label* miss_label) {
|
||||
LookupResult lookup(masm->isolate());
|
||||
object->Lookup(*name, &lookup);
|
||||
if (lookup.IsFound() && (lookup.IsReadOnly() || !lookup.IsCacheable())) {
|
||||
// In sloppy mode, we could just return the value and be done. However, we
|
||||
// might be in strict mode, where we have to throw. Since we cannot tell,
|
||||
// go into slow case unconditionally.
|
||||
__ jmp(miss_label);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that the map of the object hasn't changed.
|
||||
CompareMapMode mode = transition.is_null() ? ALLOW_ELEMENT_TRANSITION_MAPS
|
||||
: REQUIRE_EXACT_MAP;
|
||||
@ -757,7 +769,32 @@ void StubCompiler::GenerateStoreField(MacroAssembler* masm,
|
||||
|
||||
// Perform global security token check if needed.
|
||||
if (object->IsJSGlobalProxy()) {
|
||||
__ CheckAccessGlobalProxy(receiver_reg, scratch, miss_label);
|
||||
__ CheckAccessGlobalProxy(receiver_reg, scratch1, miss_label);
|
||||
}
|
||||
|
||||
// Check that we are allowed to write this.
|
||||
if (!transition.is_null() && object->GetPrototype()->IsJSObject()) {
|
||||
JSObject* holder;
|
||||
if (lookup.IsFound()) {
|
||||
holder = lookup.holder();
|
||||
} else {
|
||||
// Find the top object.
|
||||
holder = *object;
|
||||
do {
|
||||
holder = JSObject::cast(holder->GetPrototype());
|
||||
} while (holder->GetPrototype()->IsJSObject());
|
||||
}
|
||||
// We need an extra register, push
|
||||
__ push(name_reg);
|
||||
Label miss_pop, done_check;
|
||||
CheckPrototypes(object, receiver_reg, Handle<JSObject>(holder), name_reg,
|
||||
scratch1, scratch2, name, &miss_pop);
|
||||
__ jmp(&done_check);
|
||||
__ bind(&miss_pop);
|
||||
__ pop(name_reg);
|
||||
__ jmp(miss_label);
|
||||
__ bind(&done_check);
|
||||
__ pop(name_reg);
|
||||
}
|
||||
|
||||
// Stub never generated for non-global objects that require access
|
||||
@ -768,11 +805,11 @@ void StubCompiler::GenerateStoreField(MacroAssembler* masm,
|
||||
if (!transition.is_null() && (object->map()->unused_property_fields() == 0)) {
|
||||
// The properties must be extended before we can store the value.
|
||||
// We jump to a runtime call that extends the properties array.
|
||||
__ pop(scratch); // Return address.
|
||||
__ pop(scratch1); // Return address.
|
||||
__ push(receiver_reg);
|
||||
__ push(Immediate(transition));
|
||||
__ push(eax);
|
||||
__ push(scratch);
|
||||
__ push(scratch1);
|
||||
__ TailCallExternalReference(
|
||||
ExternalReference(IC_Utility(IC::kSharedStoreIC_ExtendStorage),
|
||||
masm->isolate()),
|
||||
@ -783,14 +820,14 @@ void StubCompiler::GenerateStoreField(MacroAssembler* masm,
|
||||
|
||||
if (!transition.is_null()) {
|
||||
// Update the map of the object.
|
||||
__ mov(scratch, Immediate(transition));
|
||||
__ mov(FieldOperand(receiver_reg, HeapObject::kMapOffset), scratch);
|
||||
__ mov(scratch1, Immediate(transition));
|
||||
__ mov(FieldOperand(receiver_reg, HeapObject::kMapOffset), scratch1);
|
||||
|
||||
// Update the write barrier for the map field and pass the now unused
|
||||
// name_reg as scratch register.
|
||||
__ RecordWriteField(receiver_reg,
|
||||
HeapObject::kMapOffset,
|
||||
scratch,
|
||||
scratch1,
|
||||
name_reg,
|
||||
kDontSaveFPRegs,
|
||||
OMIT_REMEMBERED_SET,
|
||||
@ -813,19 +850,19 @@ void StubCompiler::GenerateStoreField(MacroAssembler* masm,
|
||||
__ RecordWriteField(receiver_reg,
|
||||
offset,
|
||||
name_reg,
|
||||
scratch,
|
||||
scratch1,
|
||||
kDontSaveFPRegs);
|
||||
} else {
|
||||
// Write to the properties array.
|
||||
int offset = index * kPointerSize + FixedArray::kHeaderSize;
|
||||
// Get the properties array (optimistically).
|
||||
__ mov(scratch, FieldOperand(receiver_reg, JSObject::kPropertiesOffset));
|
||||
__ mov(FieldOperand(scratch, offset), eax);
|
||||
__ mov(scratch1, FieldOperand(receiver_reg, JSObject::kPropertiesOffset));
|
||||
__ mov(FieldOperand(scratch1, offset), eax);
|
||||
|
||||
// Update the write barrier for the array address.
|
||||
// Pass the value being stored in the now unused name_reg.
|
||||
__ mov(name_reg, eax);
|
||||
__ RecordWriteField(scratch,
|
||||
__ RecordWriteField(scratch1,
|
||||
offset,
|
||||
name_reg,
|
||||
receiver_reg,
|
||||
@ -2484,8 +2521,13 @@ Handle<Code> StoreStubCompiler::CompileStoreField(Handle<JSObject> object,
|
||||
Label miss;
|
||||
|
||||
// Generate store field code. Trashes the name register.
|
||||
GenerateStoreField(masm(), object, index, transition, edx, ecx, ebx, &miss);
|
||||
|
||||
GenerateStoreField(masm(),
|
||||
object,
|
||||
index,
|
||||
transition,
|
||||
name,
|
||||
edx, ecx, ebx, edi,
|
||||
&miss);
|
||||
// Handle store cache miss.
|
||||
__ bind(&miss);
|
||||
__ mov(ecx, Immediate(name)); // restore name
|
||||
@ -2658,7 +2700,13 @@ Handle<Code> KeyedStoreStubCompiler::CompileStoreField(Handle<JSObject> object,
|
||||
__ j(not_equal, &miss);
|
||||
|
||||
// Generate store field code. Trashes the name register.
|
||||
GenerateStoreField(masm(), object, index, transition, edx, ecx, ebx, &miss);
|
||||
GenerateStoreField(masm(),
|
||||
object,
|
||||
index,
|
||||
transition,
|
||||
name,
|
||||
edx, ecx, ebx, edi,
|
||||
&miss);
|
||||
|
||||
// Handle store cache miss.
|
||||
__ bind(&miss);
|
||||
|
266
src/objects.cc
266
src/objects.cc
@ -1762,14 +1762,11 @@ MaybeObject* JSObject::SetPropertyPostInterceptor(
|
||||
// found. Use set property to handle all these cases.
|
||||
return SetProperty(&result, name, value, attributes, strict_mode);
|
||||
}
|
||||
bool found = false;
|
||||
bool done = false;
|
||||
MaybeObject* result_object;
|
||||
result_object = SetPropertyWithCallbackSetterInPrototypes(name,
|
||||
value,
|
||||
attributes,
|
||||
&found,
|
||||
strict_mode);
|
||||
if (found) return result_object;
|
||||
result_object =
|
||||
SetPropertyViaPrototypes(name, value, attributes, strict_mode, &done);
|
||||
if (done) return result_object;
|
||||
// Add a new real property.
|
||||
return AddProperty(name, value, attributes, strict_mode);
|
||||
}
|
||||
@ -2049,26 +2046,6 @@ MaybeObject* JSReceiver::SetPropertyWithDefinedSetter(JSReceiver* setter,
|
||||
}
|
||||
|
||||
|
||||
void JSObject::LookupCallbackSetterInPrototypes(String* name,
|
||||
LookupResult* result) {
|
||||
Heap* heap = GetHeap();
|
||||
for (Object* pt = GetPrototype();
|
||||
pt != heap->null_value();
|
||||
pt = pt->GetPrototype()) {
|
||||
if (pt->IsJSProxy()) {
|
||||
return result->HandlerResult(JSProxy::cast(pt));
|
||||
}
|
||||
JSObject::cast(pt)->LocalLookupRealNamedProperty(name, result);
|
||||
if (result->IsProperty()) {
|
||||
if (result->type() == CALLBACKS && !result->IsReadOnly()) return;
|
||||
// Found non-callback or read-only callback, stop looking.
|
||||
break;
|
||||
}
|
||||
}
|
||||
result->NotFound();
|
||||
}
|
||||
|
||||
|
||||
MaybeObject* JSObject::SetElementWithCallbackSetterInPrototypes(
|
||||
uint32_t index,
|
||||
Object* value,
|
||||
@ -2085,7 +2062,7 @@ MaybeObject* JSObject::SetElementWithCallbackSetterInPrototypes(
|
||||
*found = true; // Force abort
|
||||
return maybe;
|
||||
}
|
||||
return JSProxy::cast(pt)->SetPropertyWithHandlerIfDefiningSetter(
|
||||
return JSProxy::cast(pt)->SetPropertyViaPrototypesWithHandler(
|
||||
this, name, value, NONE, strict_mode, found);
|
||||
}
|
||||
if (!JSObject::cast(pt)->HasDictionaryElements()) {
|
||||
@ -2110,45 +2087,59 @@ MaybeObject* JSObject::SetElementWithCallbackSetterInPrototypes(
|
||||
return heap->the_hole_value();
|
||||
}
|
||||
|
||||
MaybeObject* JSObject::SetPropertyWithCallbackSetterInPrototypes(
|
||||
MaybeObject* JSObject::SetPropertyViaPrototypes(
|
||||
String* name,
|
||||
Object* value,
|
||||
PropertyAttributes attributes,
|
||||
bool* found,
|
||||
StrictModeFlag strict_mode) {
|
||||
StrictModeFlag strict_mode,
|
||||
bool* done) {
|
||||
Heap* heap = GetHeap();
|
||||
Isolate* isolate = heap->isolate();
|
||||
|
||||
*done = false;
|
||||
// We could not find a local property so let's check whether there is an
|
||||
// accessor that wants to handle the property.
|
||||
LookupResult accessor_result(heap->isolate());
|
||||
LookupCallbackSetterInPrototypes(name, &accessor_result);
|
||||
if (accessor_result.IsFound()) {
|
||||
*found = true;
|
||||
if (accessor_result.type() == CALLBACKS) {
|
||||
return SetPropertyWithCallback(accessor_result.GetCallbackObject(),
|
||||
name,
|
||||
value,
|
||||
accessor_result.holder(),
|
||||
strict_mode);
|
||||
} else if (accessor_result.type() == HANDLER) {
|
||||
// There is a proxy in the prototype chain. Invoke its
|
||||
// getPropertyDescriptor trap.
|
||||
bool found = false;
|
||||
// SetPropertyWithHandlerIfDefiningSetter can cause GC,
|
||||
// make sure to use the handlified references after calling
|
||||
// the function.
|
||||
Handle<JSObject> self(this);
|
||||
Handle<String> hname(name);
|
||||
Handle<Object> hvalue(value);
|
||||
MaybeObject* result =
|
||||
accessor_result.proxy()->SetPropertyWithHandlerIfDefiningSetter(
|
||||
this, name, value, attributes, strict_mode, &found);
|
||||
if (found) return result;
|
||||
// The proxy does not define the property as an accessor.
|
||||
// Consequently, it has no effect on setting the receiver.
|
||||
return self->AddProperty(*hname, *hvalue, attributes, strict_mode);
|
||||
// accessor that wants to handle the property, or whether the property is
|
||||
// read-only on the prototype chain.
|
||||
LookupResult result(isolate);
|
||||
LookupRealNamedPropertyInPrototypes(name, &result);
|
||||
if (result.IsFound()) {
|
||||
switch (result.type()) {
|
||||
case NORMAL:
|
||||
case FIELD:
|
||||
case CONSTANT_FUNCTION:
|
||||
*done = result.IsReadOnly();
|
||||
break;
|
||||
case INTERCEPTOR: {
|
||||
PropertyAttributes attr =
|
||||
result.holder()->GetPropertyAttributeWithInterceptor(
|
||||
this, name, true);
|
||||
*done = !!(attr & READ_ONLY);
|
||||
break;
|
||||
}
|
||||
case CALLBACKS: {
|
||||
*done = true;
|
||||
return SetPropertyWithCallback(result.GetCallbackObject(),
|
||||
name, value, result.holder(), strict_mode);
|
||||
}
|
||||
case HANDLER: {
|
||||
return result.proxy()->SetPropertyViaPrototypesWithHandler(
|
||||
this, name, value, attributes, strict_mode, done);
|
||||
}
|
||||
case MAP_TRANSITION:
|
||||
case CONSTANT_TRANSITION:
|
||||
case NULL_DESCRIPTOR:
|
||||
case ELEMENTS_TRANSITION:
|
||||
break;
|
||||
}
|
||||
}
|
||||
*found = false;
|
||||
|
||||
// If we get here with *done true, we have encountered a read-only property.
|
||||
if (*done) {
|
||||
if (strict_mode == kNonStrictMode) return value;
|
||||
Handle<Object> args[] = { Handle<Object>(name), Handle<Object>(this)};
|
||||
return isolate->Throw(*isolate->factory()->NewTypeError(
|
||||
"strict_read_only_property", HandleVector(args, ARRAY_SIZE(args))));
|
||||
}
|
||||
return heap->the_hole_value();
|
||||
}
|
||||
|
||||
@ -2553,9 +2544,12 @@ void JSObject::LookupRealNamedPropertyInPrototypes(String* name,
|
||||
Heap* heap = GetHeap();
|
||||
for (Object* pt = GetPrototype();
|
||||
pt != heap->null_value();
|
||||
pt = JSObject::cast(pt)->GetPrototype()) {
|
||||
pt = pt->GetPrototype()) {
|
||||
if (pt->IsJSProxy()) {
|
||||
return result->HandlerResult(JSProxy::cast(pt));
|
||||
}
|
||||
JSObject::cast(pt)->LocalLookupRealNamedProperty(name, result);
|
||||
if (result->IsProperty() && (result->type() != INTERCEPTOR)) return;
|
||||
if (result->IsProperty()) return;
|
||||
}
|
||||
result->NotFound();
|
||||
}
|
||||
@ -2569,7 +2563,7 @@ MaybeObject* JSObject::SetPropertyWithFailedAccessCheck(
|
||||
bool check_prototype,
|
||||
StrictModeFlag strict_mode) {
|
||||
if (check_prototype && !result->IsProperty()) {
|
||||
LookupCallbackSetterInPrototypes(name, result);
|
||||
LookupRealNamedPropertyInPrototypes(name, result);
|
||||
}
|
||||
|
||||
if (result->IsProperty()) {
|
||||
@ -2642,7 +2636,7 @@ bool JSProxy::HasPropertyWithHandler(String* name_raw) {
|
||||
Handle<Object> args[] = { name };
|
||||
Handle<Object> result = CallTrap(
|
||||
"has", isolate->derived_has_trap(), ARRAY_SIZE(args), args);
|
||||
if (isolate->has_pending_exception()) return Failure::Exception();
|
||||
if (isolate->has_pending_exception()) return false;
|
||||
|
||||
return result->ToBoolean()->IsTrue();
|
||||
}
|
||||
@ -2668,79 +2662,92 @@ MUST_USE_RESULT MaybeObject* JSProxy::SetPropertyWithHandler(
|
||||
}
|
||||
|
||||
|
||||
MUST_USE_RESULT MaybeObject* JSProxy::SetPropertyWithHandlerIfDefiningSetter(
|
||||
MUST_USE_RESULT MaybeObject* JSProxy::SetPropertyViaPrototypesWithHandler(
|
||||
JSReceiver* receiver_raw,
|
||||
String* name_raw,
|
||||
Object* value_raw,
|
||||
PropertyAttributes attributes,
|
||||
StrictModeFlag strict_mode,
|
||||
bool* found) {
|
||||
*found = true; // except where defined otherwise...
|
||||
Isolate* isolate = GetHeap()->isolate();
|
||||
Handle<JSReceiver> receiver(receiver_raw);
|
||||
bool* done) {
|
||||
Isolate* isolate = GetIsolate();
|
||||
Handle<JSProxy> proxy(this);
|
||||
Handle<Object> handler(this->handler()); // Trap might morph proxy.
|
||||
Handle<JSReceiver> receiver(receiver_raw);
|
||||
Handle<String> name(name_raw);
|
||||
Handle<Object> value(value_raw);
|
||||
Handle<Object> handler(this->handler()); // Trap might morph proxy.
|
||||
|
||||
*done = true; // except where redefined...
|
||||
Handle<Object> args[] = { name };
|
||||
Handle<Object> result = proxy->CallTrap(
|
||||
"getPropertyDescriptor", Handle<Object>(), ARRAY_SIZE(args), args);
|
||||
if (isolate->has_pending_exception()) return Failure::Exception();
|
||||
|
||||
if (!result->IsUndefined()) {
|
||||
// The proxy handler cares about this property.
|
||||
// Check whether it is virtualized as an accessor.
|
||||
// Emulate [[GetProperty]] semantics for proxies.
|
||||
bool has_pending_exception;
|
||||
Handle<Object> argv[] = { result };
|
||||
Handle<Object> desc =
|
||||
Execution::Call(isolate->to_complete_property_descriptor(), result,
|
||||
ARRAY_SIZE(argv), argv, &has_pending_exception);
|
||||
if (has_pending_exception) return Failure::Exception();
|
||||
|
||||
Handle<String> conf_name =
|
||||
isolate->factory()->LookupAsciiSymbol("configurable_");
|
||||
Handle<Object> configurable(v8::internal::GetProperty(desc, conf_name));
|
||||
ASSERT(!isolate->has_pending_exception());
|
||||
if (configurable->IsFalse()) {
|
||||
Handle<String> trap =
|
||||
isolate->factory()->LookupAsciiSymbol("getPropertyDescriptor");
|
||||
Handle<Object> args[] = { handler, trap, name };
|
||||
Handle<Object> error = isolate->factory()->NewTypeError(
|
||||
"proxy_prop_not_configurable", HandleVector(args, ARRAY_SIZE(args)));
|
||||
return isolate->Throw(*error);
|
||||
}
|
||||
ASSERT(configurable->IsTrue());
|
||||
|
||||
// Check for AccessorDescriptor.
|
||||
Handle<String> set_name = isolate->factory()->LookupAsciiSymbol("set_");
|
||||
Handle<Object> setter(v8::internal::GetProperty(desc, set_name));
|
||||
ASSERT(!isolate->has_pending_exception());
|
||||
if (!setter->IsUndefined()) {
|
||||
// We have a setter -- invoke it.
|
||||
// TODO(rossberg): nicer would be to cast to some JSCallable here...
|
||||
return receiver->SetPropertyWithDefinedSetter(
|
||||
JSReceiver::cast(*setter), *value);
|
||||
} else {
|
||||
Handle<String> get_name = isolate->factory()->LookupAsciiSymbol("get_");
|
||||
Handle<Object> getter(v8::internal::GetProperty(desc, get_name));
|
||||
ASSERT(!isolate->has_pending_exception());
|
||||
if (!getter->IsUndefined()) {
|
||||
// We have a getter but no setter -- the property may not be
|
||||
// written. In strict mode, throw an error.
|
||||
if (strict_mode == kNonStrictMode) return *value;
|
||||
Handle<Object> args[] = { name, proxy };
|
||||
Handle<Object> error = isolate->factory()->NewTypeError(
|
||||
"no_setter_in_callback", HandleVector(args, ARRAY_SIZE(args)));
|
||||
return isolate->Throw(*error);
|
||||
}
|
||||
}
|
||||
// Fall-through.
|
||||
if (result->IsUndefined()) {
|
||||
*done = false;
|
||||
return GetHeap()->the_hole_value();
|
||||
}
|
||||
|
||||
// The proxy does not define the property as an accessor.
|
||||
*found = false;
|
||||
return *value;
|
||||
// Emulate [[GetProperty]] semantics for proxies.
|
||||
bool has_pending_exception;
|
||||
Handle<Object> argv[] = { result };
|
||||
Handle<Object> desc =
|
||||
Execution::Call(isolate->to_complete_property_descriptor(), result,
|
||||
ARRAY_SIZE(argv), argv, &has_pending_exception);
|
||||
if (has_pending_exception) return Failure::Exception();
|
||||
|
||||
// [[GetProperty]] requires to check that all properties are configurable.
|
||||
Handle<String> configurable_name =
|
||||
isolate->factory()->LookupAsciiSymbol("configurable_");
|
||||
Handle<Object> configurable(
|
||||
v8::internal::GetProperty(desc, configurable_name));
|
||||
ASSERT(!isolate->has_pending_exception());
|
||||
ASSERT(configurable->IsTrue() || configurable->IsFalse());
|
||||
if (configurable->IsFalse()) {
|
||||
Handle<String> trap =
|
||||
isolate->factory()->LookupAsciiSymbol("getPropertyDescriptor");
|
||||
Handle<Object> args[] = { handler, trap, name };
|
||||
Handle<Object> error = isolate->factory()->NewTypeError(
|
||||
"proxy_prop_not_configurable", HandleVector(args, ARRAY_SIZE(args)));
|
||||
return isolate->Throw(*error);
|
||||
}
|
||||
ASSERT(configurable->IsTrue());
|
||||
|
||||
// Check for DataDescriptor.
|
||||
Handle<String> hasWritable_name =
|
||||
isolate->factory()->LookupAsciiSymbol("hasWritable_");
|
||||
Handle<Object> hasWritable(v8::internal::GetProperty(desc, hasWritable_name));
|
||||
ASSERT(!isolate->has_pending_exception());
|
||||
ASSERT(hasWritable->IsTrue() || hasWritable->IsFalse());
|
||||
if (hasWritable->IsTrue()) {
|
||||
Handle<String> writable_name =
|
||||
isolate->factory()->LookupAsciiSymbol("writable_");
|
||||
Handle<Object> writable(v8::internal::GetProperty(desc, writable_name));
|
||||
ASSERT(!isolate->has_pending_exception());
|
||||
ASSERT(writable->IsTrue() || writable->IsFalse());
|
||||
*done = writable->IsFalse();
|
||||
if (!*done) return GetHeap()->the_hole_value();
|
||||
if (strict_mode == kNonStrictMode) return *value;
|
||||
Handle<Object> args[] = { name, receiver };
|
||||
Handle<Object> error = isolate->factory()->NewTypeError(
|
||||
"strict_read_only_property", HandleVector(args, ARRAY_SIZE(args)));
|
||||
return isolate->Throw(*error);
|
||||
}
|
||||
|
||||
// We have an AccessorDescriptor.
|
||||
Handle<String> set_name = isolate->factory()->LookupAsciiSymbol("set_");
|
||||
Handle<Object> setter(v8::internal::GetProperty(desc, set_name));
|
||||
ASSERT(!isolate->has_pending_exception());
|
||||
if (!setter->IsUndefined()) {
|
||||
// TODO(rossberg): nicer would be to cast to some JSCallable here...
|
||||
return receiver->SetPropertyWithDefinedSetter(
|
||||
JSReceiver::cast(*setter), *value);
|
||||
}
|
||||
|
||||
if (strict_mode == kNonStrictMode) return *value;
|
||||
Handle<Object> args2[] = { name, proxy };
|
||||
Handle<Object> error = isolate->factory()->NewTypeError(
|
||||
"no_setter_in_callback", HandleVector(args2, ARRAY_SIZE(args2)));
|
||||
return isolate->Throw(*error);
|
||||
}
|
||||
|
||||
|
||||
@ -2933,19 +2940,12 @@ MaybeObject* JSObject::SetPropertyForResult(LookupResult* result,
|
||||
}
|
||||
|
||||
if (!result->IsProperty() && !IsJSContextExtensionObject()) {
|
||||
bool found = false;
|
||||
MaybeObject* result_object;
|
||||
result_object = SetPropertyWithCallbackSetterInPrototypes(name,
|
||||
value,
|
||||
attributes,
|
||||
&found,
|
||||
strict_mode);
|
||||
if (found) return result_object;
|
||||
bool done = false;
|
||||
MaybeObject* result_object =
|
||||
SetPropertyViaPrototypes(name, value, attributes, strict_mode, &done);
|
||||
if (done) return result_object;
|
||||
}
|
||||
|
||||
// At this point, no GC should have happened, as this would invalidate
|
||||
// 'result', which we cannot handlify!
|
||||
|
||||
if (!result->IsFound()) {
|
||||
// Neither properties nor transitions found.
|
||||
return AddProperty(name, value, attributes, strict_mode);
|
||||
|
@ -1837,7 +1837,6 @@ class JSObject: public JSReceiver {
|
||||
void LocalLookupRealNamedProperty(String* name, LookupResult* result);
|
||||
void LookupRealNamedProperty(String* name, LookupResult* result);
|
||||
void LookupRealNamedPropertyInPrototypes(String* name, LookupResult* result);
|
||||
void LookupCallbackSetterInPrototypes(String* name, LookupResult* result);
|
||||
MUST_USE_RESULT MaybeObject* SetElementWithCallbackSetterInPrototypes(
|
||||
uint32_t index, Object* value, bool* found, StrictModeFlag strict_mode);
|
||||
void LookupCallback(String* name, LookupResult* result);
|
||||
@ -2122,17 +2121,16 @@ class JSObject: public JSReceiver {
|
||||
bool check_prototype,
|
||||
SetPropertyMode set_mode);
|
||||
|
||||
// Searches the prototype chain for a callback setter and sets the property
|
||||
// with the setter if it finds one. The '*found' flag indicates whether
|
||||
// a setter was found or not.
|
||||
// This function can cause GC and can return a failure result with
|
||||
// '*found==true'.
|
||||
MUST_USE_RESULT MaybeObject* SetPropertyWithCallbackSetterInPrototypes(
|
||||
// Searches the prototype chain for property 'name'. If it is found and
|
||||
// has a setter, invoke it and set '*done' to true. If it is found and is
|
||||
// read-only, reject and set '*done' to true. Otherwise, set '*done' to
|
||||
// false. Can cause GC and can return a failure result with '*done==true'.
|
||||
MUST_USE_RESULT MaybeObject* SetPropertyViaPrototypes(
|
||||
String* name,
|
||||
Object* value,
|
||||
PropertyAttributes attributes,
|
||||
bool* found,
|
||||
StrictModeFlag strict_mode);
|
||||
StrictModeFlag strict_mode,
|
||||
bool* done);
|
||||
|
||||
MUST_USE_RESULT MaybeObject* DeletePropertyPostInterceptor(String* name,
|
||||
DeleteMode mode);
|
||||
@ -7733,15 +7731,17 @@ class JSProxy: public JSReceiver {
|
||||
Object* value,
|
||||
StrictModeFlag strict_mode);
|
||||
|
||||
// If the handler defines an accessor property, invoke its setter
|
||||
// (or throw if only a getter exists) and set *found to true. Otherwise false.
|
||||
MUST_USE_RESULT MaybeObject* SetPropertyWithHandlerIfDefiningSetter(
|
||||
// If the handler defines an accessor property with a setter, invoke it.
|
||||
// If it defines an accessor property without a setter, or a data property
|
||||
// that is read-only, throw. In all these cases set '*done' to true,
|
||||
// otherwise set it to false.
|
||||
MUST_USE_RESULT MaybeObject* SetPropertyViaPrototypesWithHandler(
|
||||
JSReceiver* receiver,
|
||||
String* name,
|
||||
Object* value,
|
||||
PropertyAttributes attributes,
|
||||
StrictModeFlag strict_mode,
|
||||
bool* found);
|
||||
bool* done);
|
||||
|
||||
MUST_USE_RESULT MaybeObject* DeletePropertyWithHandler(
|
||||
String* name,
|
||||
|
@ -460,14 +460,16 @@ class StubCompiler BASE_EMBEDDED {
|
||||
Register scratch2,
|
||||
Label* miss_label);
|
||||
|
||||
static void GenerateStoreField(MacroAssembler* masm,
|
||||
Handle<JSObject> object,
|
||||
int index,
|
||||
Handle<Map> transition,
|
||||
Register receiver_reg,
|
||||
Register name_reg,
|
||||
Register scratch,
|
||||
Label* miss_label);
|
||||
void GenerateStoreField(MacroAssembler* masm,
|
||||
Handle<JSObject> object,
|
||||
int index,
|
||||
Handle<Map> transition,
|
||||
Handle<String> name,
|
||||
Register receiver_reg,
|
||||
Register name_reg,
|
||||
Register scratch1,
|
||||
Register scratch2,
|
||||
Label* miss_label);
|
||||
|
||||
static void GenerateLoadMiss(MacroAssembler* masm,
|
||||
Code::Kind kind);
|
||||
@ -511,6 +513,7 @@ class StubCompiler BASE_EMBEDDED {
|
||||
int save_at_depth,
|
||||
Label* miss);
|
||||
|
||||
|
||||
protected:
|
||||
Handle<Code> GetCodeWithFlags(Code::Flags flags, const char* name);
|
||||
Handle<Code> GetCodeWithFlags(Code::Flags flags, Handle<String> name);
|
||||
|
@ -731,10 +731,22 @@ void StubCompiler::GenerateStoreField(MacroAssembler* masm,
|
||||
Handle<JSObject> object,
|
||||
int index,
|
||||
Handle<Map> transition,
|
||||
Handle<String> name,
|
||||
Register receiver_reg,
|
||||
Register name_reg,
|
||||
Register scratch,
|
||||
Register scratch1,
|
||||
Register scratch2,
|
||||
Label* miss_label) {
|
||||
LookupResult lookup(masm->isolate());
|
||||
object->Lookup(*name, &lookup);
|
||||
if (lookup.IsFound() && (lookup.IsReadOnly() || !lookup.IsCacheable())) {
|
||||
// In sloppy mode, we could just return the value and be done. However, we
|
||||
// might be in strict mode, where we have to throw. Since we cannot tell,
|
||||
// go into slow case unconditionally.
|
||||
__ jmp(miss_label);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that the map of the object hasn't changed.
|
||||
CompareMapMode mode = transition.is_null() ? ALLOW_ELEMENT_TRANSITION_MAPS
|
||||
: REQUIRE_EXACT_MAP;
|
||||
@ -743,7 +755,32 @@ void StubCompiler::GenerateStoreField(MacroAssembler* masm,
|
||||
|
||||
// Perform global security token check if needed.
|
||||
if (object->IsJSGlobalProxy()) {
|
||||
__ CheckAccessGlobalProxy(receiver_reg, scratch, miss_label);
|
||||
__ CheckAccessGlobalProxy(receiver_reg, scratch1, miss_label);
|
||||
}
|
||||
|
||||
// Check that we are allowed to write this.
|
||||
if (!transition.is_null() && object->GetPrototype()->IsJSObject()) {
|
||||
JSObject* holder;
|
||||
if (lookup.IsFound()) {
|
||||
holder = lookup.holder();
|
||||
} else {
|
||||
// Find the top object.
|
||||
holder = *object;
|
||||
do {
|
||||
holder = JSObject::cast(holder->GetPrototype());
|
||||
} while (holder->GetPrototype()->IsJSObject());
|
||||
}
|
||||
// We need an extra register, push
|
||||
__ push(name_reg);
|
||||
Label miss_pop, done_check;
|
||||
CheckPrototypes(object, receiver_reg, Handle<JSObject>(holder), name_reg,
|
||||
scratch1, scratch2, name, &miss_pop);
|
||||
__ jmp(&done_check);
|
||||
__ bind(&miss_pop);
|
||||
__ pop(name_reg);
|
||||
__ jmp(miss_label);
|
||||
__ bind(&done_check);
|
||||
__ pop(name_reg);
|
||||
}
|
||||
|
||||
// Stub never generated for non-global objects that require access
|
||||
@ -754,11 +791,11 @@ void StubCompiler::GenerateStoreField(MacroAssembler* masm,
|
||||
if (!transition.is_null() && (object->map()->unused_property_fields() == 0)) {
|
||||
// The properties must be extended before we can store the value.
|
||||
// We jump to a runtime call that extends the properties array.
|
||||
__ pop(scratch); // Return address.
|
||||
__ pop(scratch1); // Return address.
|
||||
__ push(receiver_reg);
|
||||
__ Push(transition);
|
||||
__ push(rax);
|
||||
__ push(scratch);
|
||||
__ push(scratch1);
|
||||
__ TailCallExternalReference(
|
||||
ExternalReference(IC_Utility(IC::kSharedStoreIC_ExtendStorage),
|
||||
masm->isolate()),
|
||||
@ -797,19 +834,19 @@ void StubCompiler::GenerateStoreField(MacroAssembler* masm,
|
||||
// Pass the value being stored in the now unused name_reg.
|
||||
__ movq(name_reg, rax);
|
||||
__ RecordWriteField(
|
||||
receiver_reg, offset, name_reg, scratch, kDontSaveFPRegs);
|
||||
receiver_reg, offset, name_reg, scratch1, kDontSaveFPRegs);
|
||||
} else {
|
||||
// Write to the properties array.
|
||||
int offset = index * kPointerSize + FixedArray::kHeaderSize;
|
||||
// Get the properties array (optimistically).
|
||||
__ movq(scratch, FieldOperand(receiver_reg, JSObject::kPropertiesOffset));
|
||||
__ movq(FieldOperand(scratch, offset), rax);
|
||||
__ movq(scratch1, FieldOperand(receiver_reg, JSObject::kPropertiesOffset));
|
||||
__ movq(FieldOperand(scratch1, offset), rax);
|
||||
|
||||
// Update the write barrier for the array address.
|
||||
// Pass the value being stored in the now unused name_reg.
|
||||
__ movq(name_reg, rax);
|
||||
__ RecordWriteField(
|
||||
scratch, offset, name_reg, receiver_reg, kDontSaveFPRegs);
|
||||
scratch1, offset, name_reg, receiver_reg, kDontSaveFPRegs);
|
||||
}
|
||||
|
||||
// Return the value (register rax).
|
||||
@ -2321,7 +2358,13 @@ Handle<Code> StoreStubCompiler::CompileStoreField(Handle<JSObject> object,
|
||||
Label miss;
|
||||
|
||||
// Generate store field code. Preserves receiver and name on jump to miss.
|
||||
GenerateStoreField(masm(), object, index, transition, rdx, rcx, rbx, &miss);
|
||||
GenerateStoreField(masm(),
|
||||
object,
|
||||
index,
|
||||
transition,
|
||||
name,
|
||||
rdx, rcx, rbx, rdi,
|
||||
&miss);
|
||||
|
||||
// Handle store cache miss.
|
||||
__ bind(&miss);
|
||||
@ -2494,7 +2537,13 @@ Handle<Code> KeyedStoreStubCompiler::CompileStoreField(Handle<JSObject> object,
|
||||
__ j(not_equal, &miss);
|
||||
|
||||
// Generate store field code. Preserves receiver and name on jump to miss.
|
||||
GenerateStoreField(masm(), object, index, transition, rdx, rcx, rbx, &miss);
|
||||
GenerateStoreField(masm(),
|
||||
object,
|
||||
index,
|
||||
transition,
|
||||
name,
|
||||
rdx, rcx, rbx, rdi,
|
||||
&miss);
|
||||
|
||||
// Handle store cache miss.
|
||||
__ bind(&miss);
|
||||
|
@ -7662,7 +7662,7 @@ THREADED_TEST(ShadowObject) {
|
||||
value = Script::Compile(v8_str("f()"))->Run();
|
||||
CHECK_EQ(42, value->Int32Value());
|
||||
|
||||
Script::Compile(v8_str("y = 42"))->Run();
|
||||
Script::Compile(v8_str("y = 43"))->Run();
|
||||
CHECK_EQ(1, shadow_y_setter_call_count);
|
||||
value = Script::Compile(v8_str("y"))->Run();
|
||||
CHECK_EQ(1, shadow_y_getter_call_count);
|
||||
@ -10306,11 +10306,11 @@ THREADED_TEST(Overriding) {
|
||||
value = v8_compile("o.g")->Run();
|
||||
CHECK_EQ(42, value->Int32Value());
|
||||
|
||||
// Check 'h' can be shadowed.
|
||||
// Check that 'h' cannot be shadowed.
|
||||
value = v8_compile("o.h = 3; o.h")->Run();
|
||||
CHECK_EQ(3, value->Int32Value());
|
||||
CHECK_EQ(1, value->Int32Value());
|
||||
|
||||
// Check 'i' is cannot be shadowed or changed.
|
||||
// Check that 'i' cannot be shadowed or changed.
|
||||
value = v8_compile("o.i = 3; o.i")->Run();
|
||||
CHECK_EQ(42, value->Int32Value());
|
||||
}
|
||||
@ -12148,7 +12148,7 @@ TEST(RegExpStringModification) {
|
||||
}
|
||||
|
||||
|
||||
// Test that we can set a property on the global object even if there
|
||||
// Test that we cannot set a property on the global object if there
|
||||
// is a read-only property in the prototype chain.
|
||||
TEST(ReadOnlyPropertyInGlobalProto) {
|
||||
v8::HandleScope scope;
|
||||
@ -12162,12 +12162,13 @@ TEST(ReadOnlyPropertyInGlobalProto) {
|
||||
// Check without 'eval' or 'with'.
|
||||
v8::Handle<v8::Value> res =
|
||||
CompileRun("function f() { x = 42; return x; }; f()");
|
||||
CHECK_EQ(v8::Integer::New(0), res);
|
||||
// Check with 'eval'.
|
||||
res = CompileRun("function f() { eval('1'); y = 42; return y; }; f()");
|
||||
CHECK_EQ(v8::Integer::New(42), res);
|
||||
res = CompileRun("function f() { eval('1'); y = 43; return y; }; f()");
|
||||
CHECK_EQ(v8::Integer::New(0), res);
|
||||
// Check with 'with'.
|
||||
res = CompileRun("function f() { with (this) { y = 42 }; return y; }; f()");
|
||||
CHECK_EQ(v8::Integer::New(42), res);
|
||||
res = CompileRun("function f() { with (this) { y = 44 }; return y; }; f()");
|
||||
CHECK_EQ(v8::Integer::New(0), res);
|
||||
}
|
||||
|
||||
static int force_set_set_count = 0;
|
||||
|
@ -576,12 +576,12 @@ var rec
|
||||
var key
|
||||
var val
|
||||
|
||||
function TestSetForDerived(handler) {
|
||||
TestWithProxies(TestSetForDerived2, handler)
|
||||
function TestSetForDerived(trap) {
|
||||
TestWithProxies(TestSetForDerived2, trap)
|
||||
}
|
||||
|
||||
function TestSetForDerived2(create, handler) {
|
||||
var p = create(handler)
|
||||
function TestSetForDerived2(create, trap) {
|
||||
var p = create({getPropertyDescriptor: trap, getOwnPropertyDescriptor: trap})
|
||||
var o = Object.create(p, {x: {value: 88, writable: true},
|
||||
'1': {value: 89, writable: true}})
|
||||
|
||||
@ -608,8 +608,13 @@ function TestSetForDerived2(create, handler) {
|
||||
|
||||
assertEquals(45, o.p_nonwritable = 45)
|
||||
assertEquals("p_nonwritable", key)
|
||||
assertEquals(45, o.p_nonwritable)
|
||||
assertFalse(Object.prototype.hasOwnProperty.call(o, "p_nonwritable"))
|
||||
|
||||
assertThrows(function(){ "use strict"; o.p_nonwritable = 45 }, TypeError)
|
||||
assertEquals("p_nonwritable", key)
|
||||
assertFalse(Object.prototype.hasOwnProperty.call(o, "p_nonwritable"))
|
||||
|
||||
val = ""
|
||||
assertEquals(46, o.p_setter = 46)
|
||||
assertEquals("p_setter", key)
|
||||
assertSame(o, rec)
|
||||
@ -626,24 +631,29 @@ function TestSetForDerived2(create, handler) {
|
||||
assertThrows(function(){ "use strict"; o.p_nosetter = 50 }, TypeError)
|
||||
assertEquals("p_nosetter", key)
|
||||
assertEquals("", val) // not written at all
|
||||
assertFalse(Object.prototype.hasOwnProperty.call(o, "p_nosetter"));
|
||||
|
||||
assertThrows(function(){ o.p_nonconf = 53 }, TypeError)
|
||||
assertEquals("p_nonconf", key)
|
||||
assertFalse(Object.prototype.hasOwnProperty.call(o, "p_nonconf"));
|
||||
|
||||
assertThrows(function(){ o.p_throw = 51 }, "myexn")
|
||||
assertEquals("p_throw", key)
|
||||
assertFalse(Object.prototype.hasOwnProperty.call(o, "p_throw"));
|
||||
|
||||
assertThrows(function(){ o.p_setterthrow = 52 }, "myexn")
|
||||
assertEquals("p_setterthrow", key)
|
||||
assertFalse(Object.prototype.hasOwnProperty.call(o, "p_setterthrow"));
|
||||
}
|
||||
|
||||
TestSetForDerived({
|
||||
getPropertyDescriptor: function(k) {
|
||||
|
||||
TestSetForDerived(
|
||||
function(k) {
|
||||
key = k;
|
||||
switch (k) {
|
||||
case "p_writable": return {writable: true, configurable: true}
|
||||
case "p_nonwritable": return {writable: false, configurable: true}
|
||||
case "p_setter":return {
|
||||
case "p_setter": return {
|
||||
set: function(x) { rec = this; val = x },
|
||||
configurable: true
|
||||
}
|
||||
@ -651,13 +661,13 @@ TestSetForDerived({
|
||||
get: function() { return 1 },
|
||||
configurable: true
|
||||
}
|
||||
case "p_nonconf":return {}
|
||||
case "p_nonconf": return {}
|
||||
case "p_throw": throw "myexn"
|
||||
case "p_setterthrow": return {set: function(x) { throw "myexn" }}
|
||||
default: return undefined
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
// Evil proxy-induced side-effects shouldn't crash.
|
||||
|
@ -38,19 +38,19 @@ F.prototype = Number;
|
||||
var original_number_max = Number.MAX_VALUE;
|
||||
|
||||
// Assignment to a property which does not exist on the object itself,
|
||||
// but is read-only in a prototype takes effect.
|
||||
// but is read-only in a prototype does not take effect.
|
||||
var f = new F();
|
||||
assertEquals(original_number_max, f.MAX_VALUE);
|
||||
f.MAX_VALUE = 42;
|
||||
assertEquals(42, f.MAX_VALUE);
|
||||
assertEquals(original_number_max, f.MAX_VALUE);
|
||||
|
||||
// Assignment to a property which does not exist on the object itself,
|
||||
// but is read-only in a prototype takes effect.
|
||||
// but is read-only in a prototype does not take effect.
|
||||
f = new F();
|
||||
with (f) {
|
||||
MAX_VALUE = 42;
|
||||
}
|
||||
assertEquals(42, f.MAX_VALUE);
|
||||
assertEquals(original_number_max, f.MAX_VALUE);
|
||||
|
||||
// Assignment to read-only property on the object itself is ignored.
|
||||
Number.MAX_VALUE = 42;
|
||||
|
228
test/mjsunit/readonly.js
Normal file
228
test/mjsunit/readonly.js
Normal file
@ -0,0 +1,228 @@
|
||||
// Copyright 2011 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: --allow-natives-syntax --harmony-proxies
|
||||
|
||||
// Different ways to create an object.
|
||||
|
||||
function CreateFromLiteral() {
|
||||
return {};
|
||||
}
|
||||
|
||||
function CreateFromObject() {
|
||||
return new Object;
|
||||
}
|
||||
|
||||
function CreateDefault() {
|
||||
return Object.create(Object.prototype);
|
||||
}
|
||||
|
||||
function CreateFromConstructor(proto) {
|
||||
function C() {}
|
||||
(new C).b = 9; // Make sure that we can have an in-object property.
|
||||
C.prototype = proto;
|
||||
return function() { return new C; }
|
||||
}
|
||||
|
||||
function CreateFromApi(proto) {
|
||||
return function() { return Object.create(proto); }
|
||||
}
|
||||
|
||||
function CreateWithProperty(proto) {
|
||||
function C() { this.a = -100; }
|
||||
C.prototype = proto;
|
||||
return function() { return new C; }
|
||||
}
|
||||
|
||||
var bases = [CreateFromLiteral, CreateFromObject, CreateDefault];
|
||||
var inherits = [CreateFromConstructor, CreateFromApi, CreateWithProperty];
|
||||
var constructs = [CreateFromConstructor, CreateFromApi];
|
||||
|
||||
function TestAllCreates(f) {
|
||||
// The depth of the prototype chain up the.
|
||||
for (var depth = 0; depth < 3; ++depth) {
|
||||
// Introduce readonly-ness this far up the chain.
|
||||
for (var up = 0; up <= depth; ++up) {
|
||||
// Try different construction methods.
|
||||
for (var k = 0; k < constructs.length; ++k) {
|
||||
// Construct a fresh prototype chain from above functions.
|
||||
for (var i = 0; i < bases.length; ++i) {
|
||||
var p = bases[i]();
|
||||
// There may be a preexisting property under the insertion point...
|
||||
for (var j = 0; j < depth - up; ++j) {
|
||||
p = inherits[Math.floor(inherits.length * Math.random())](p)();
|
||||
}
|
||||
// ...but not above it.
|
||||
for (var j = 0; j < up; ++j) {
|
||||
p = constructs[Math.floor(constructs.length * Math.random())](p)();
|
||||
}
|
||||
// Create a fresh constructor.
|
||||
var c = constructs[k](p);
|
||||
f(function() {
|
||||
var o = c();
|
||||
o.up = o;
|
||||
for (var j = 0; j < up; ++j) o.up = Object.getPrototypeOf(o.up);
|
||||
return o;
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Different ways to make a property read-only.
|
||||
|
||||
function ReadonlyByNonwritableDataProperty(o, name) {
|
||||
Object.defineProperty(o, name, {value: -41, writable: false});
|
||||
}
|
||||
|
||||
function ReadonlyByAccessorPropertyWithoutSetter(o, name) {
|
||||
Object.defineProperty(o, name, {get: function() { return -42; }});
|
||||
}
|
||||
|
||||
function ReadonlyByGetter(o, name) {
|
||||
o.__defineGetter__("a", function() { return -43; });
|
||||
}
|
||||
|
||||
function ReadonlyByFreeze(o, name) {
|
||||
o[name] = -44;
|
||||
Object.freeze(o);
|
||||
}
|
||||
|
||||
function ReadonlyByProto(o, name) {
|
||||
var p = Object.create(o.__proto__);
|
||||
Object.defineProperty(p, name, {value: -45, writable: false});
|
||||
o.__proto__ = p;
|
||||
}
|
||||
|
||||
function ReadonlyByProxy(o, name) {
|
||||
var p = Proxy.create({
|
||||
getPropertyDescriptor: function() {
|
||||
return {value: -46, writable: false, configurable: true};
|
||||
}
|
||||
});
|
||||
o.__proto__ = p;
|
||||
}
|
||||
|
||||
var readonlys = [
|
||||
ReadonlyByNonwritableDataProperty, ReadonlyByAccessorPropertyWithoutSetter,
|
||||
ReadonlyByGetter, ReadonlyByFreeze, ReadonlyByProto, ReadonlyByProxy
|
||||
]
|
||||
|
||||
function TestAllReadonlys(f) {
|
||||
// Provide various methods to making a property read-only.
|
||||
for (var i = 0; i < readonlys.length; ++i) {
|
||||
print(" readonly =", i)
|
||||
f(readonlys[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Different use scenarios.
|
||||
|
||||
function Assign(o, x) {
|
||||
o.a = x;
|
||||
}
|
||||
|
||||
function AssignStrict(o, x) {
|
||||
"use strict";
|
||||
o.a = x;
|
||||
}
|
||||
|
||||
function TestAllModes(f) {
|
||||
for (var strict = 0; strict < 2; ++strict) {
|
||||
print(" strict =", strict);
|
||||
f(strict);
|
||||
}
|
||||
}
|
||||
|
||||
function TestAllScenarios(f) {
|
||||
for (var t = 0; t < 100; t = 2*t + 1) {
|
||||
print("t =", t)
|
||||
f(function(strict, create, readonly) {
|
||||
// Make sure that the assignments are monomorphic.
|
||||
%DeoptimizeFunction(Assign);
|
||||
%DeoptimizeFunction(AssignStrict);
|
||||
%ClearFunctionTypeFeedback(Assign);
|
||||
%ClearFunctionTypeFeedback(AssignStrict);
|
||||
for (var i = 0; i < t; ++i) {
|
||||
var o = create();
|
||||
assertFalse("a" in o && !("a" in o.__proto__));
|
||||
if (strict === 0)
|
||||
Assign(o, i);
|
||||
else
|
||||
AssignStrict(o, i);
|
||||
assertEquals(i, o.a);
|
||||
}
|
||||
%OptimizeFunctionOnNextCall(Assign);
|
||||
%OptimizeFunctionOnNextCall(AssignStrict);
|
||||
var o = create();
|
||||
assertFalse("a" in o && !("a" in o.__proto__));
|
||||
readonly(o.up, "a");
|
||||
assertTrue("a" in o);
|
||||
if (strict === 0)
|
||||
Assign(o, t + 1);
|
||||
else
|
||||
assertThrows(function() { AssignStrict(o, t + 1) }, TypeError);
|
||||
assertTrue(o.a < 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Runner.
|
||||
|
||||
TestAllScenarios(function(scenario) {
|
||||
TestAllModes(function(strict) {
|
||||
TestAllReadonlys(function(readonly) {
|
||||
TestAllCreates(function(create) {
|
||||
scenario(strict, create, readonly);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Extra test forcing bailout.
|
||||
|
||||
function Assign2(o, x) { o.a = x }
|
||||
|
||||
(function() {
|
||||
var p = CreateFromConstructor(Object.prototype)();
|
||||
var c = CreateFromConstructor(p);
|
||||
for (var i = 0; i < 3; ++i) {
|
||||
var o = c();
|
||||
Assign2(o, i);
|
||||
assertEquals(i, o.a);
|
||||
}
|
||||
%OptimizeFunctionOnNextCall(Assign2);
|
||||
ReadonlyByNonwritableDataProperty(p, "a");
|
||||
var o = c();
|
||||
Assign2(o, 0);
|
||||
assertTrue(o.a < 0);
|
||||
})();
|
@ -25,7 +25,7 @@
|
||||
// (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: --allow-natives-syntax
|
||||
// Flags: --allow-natives-syntax --es52_globals
|
||||
|
||||
// Make sure that we can introduce global variables (using
|
||||
// both var and const) that shadow even READ_ONLY variables
|
||||
@ -74,5 +74,3 @@ assertEquals(5678, z);
|
||||
assertEquals(1234, w);
|
||||
eval("with({}) { const w = 5678; }");
|
||||
assertEquals(5678, w);
|
||||
|
||||
|
||||
|
@ -40,7 +40,7 @@ var object = {__proto__:{}};
|
||||
%SetProperty(object, "foo", func1, DONT_ENUM | DONT_DELETE);
|
||||
%SetProperty(object, "bar", func1, DONT_ENUM | READ_ONLY);
|
||||
%SetProperty(object, "baz", func1, DONT_DELETE | READ_ONLY);
|
||||
%SetProperty(object.__proto__, "bif", func1, DONT_ENUM | DONT_DELETE | READ_ONLY);
|
||||
%SetProperty(object.__proto__, "bif", func1, DONT_ENUM | DONT_DELETE);
|
||||
object.bif = func2;
|
||||
|
||||
function enumerable(obj) {
|
||||
|
@ -36,8 +36,8 @@ function f() {
|
||||
with (o) {
|
||||
length = 23;
|
||||
length = 24;
|
||||
assertEquals(24, length);
|
||||
assertEquals(2, length);
|
||||
}
|
||||
assertEquals(2, o.length);
|
||||
}
|
||||
f();
|
||||
|
||||
|
@ -39,17 +39,6 @@ S15.12.2_A1: FAIL
|
||||
# V8 Bug: http://code.google.com/p/v8/issues/detail?id=691
|
||||
11.2.3-3_3: FAIL
|
||||
|
||||
# Prototypal inheritance of properties does not maintain accessibility.
|
||||
# The [[CanPut]] operation should traverse the prototype chain to
|
||||
# determine whether given property is writable or not.
|
||||
# V8 Bug: http://code.google.com/p/v8/issues/detail?id=1475
|
||||
8.14.4-8-b_1: FAIL
|
||||
8.14.4-8-b_2: FAIL
|
||||
15.2.3.6-4-405: FAIL
|
||||
15.2.3.6-4-410: FAIL
|
||||
15.2.3.6-4-415: FAIL
|
||||
15.2.3.6-4-420: FAIL
|
||||
|
||||
##################### DELIBERATE INCOMPATIBILITIES #####################
|
||||
|
||||
# We deliberately treat arguments to parseInt() with a leading zero as
|
||||
|
Loading…
Reference in New Issue
Block a user