Add missing negative dictionary lookup to NonexistentHandlerFrontend

BUG=v8:2980
R=verwaest@chromium.org

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

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@17459 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent 4fbc96ff
...@@ -121,18 +121,14 @@ static void ProbeTable(Isolate* isolate, ...@@ -121,18 +121,14 @@ static void ProbeTable(Isolate* isolate,
} }
// Helper function used to check that the dictionary doesn't contain void StubCompiler::GenerateDictionaryNegativeLookup(MacroAssembler* masm,
// the property. This function may return false negatives, so miss_label
// must always call a backup property check that is complete.
// This function is safe to call if the receiver has fast properties.
// Name must be unique and receiver must be a heap object.
static void GenerateDictionaryNegativeLookup(MacroAssembler* masm,
Label* miss_label, Label* miss_label,
Register receiver, Register receiver,
Handle<Name> name, Handle<Name> name,
Register scratch0, Register scratch0,
Register scratch1) { Register scratch1) {
ASSERT(name->IsUniqueName()); ASSERT(name->IsUniqueName());
ASSERT(!receiver.is(scratch0));
Counters* counters = masm->isolate()->counters(); Counters* counters = masm->isolate()->counters();
__ IncrementCounter(counters->negative_lookups(), 1, scratch0, scratch1); __ IncrementCounter(counters->negative_lookups(), 1, scratch0, scratch1);
__ IncrementCounter(counters->negative_lookups_miss(), 1, scratch0, scratch1); __ IncrementCounter(counters->negative_lookups_miss(), 1, scratch0, scratch1);
...@@ -418,7 +414,7 @@ void StubCompiler::GenerateLoadFunctionPrototype(MacroAssembler* masm, ...@@ -418,7 +414,7 @@ void StubCompiler::GenerateLoadFunctionPrototype(MacroAssembler* masm,
// Generate code to check that a global property cell is empty. Create // Generate code to check that a global property cell is empty. Create
// the property cell at compilation time if no cell exists for the // the property cell at compilation time if no cell exists for the
// property. // property.
static void GenerateCheckPropertyCell(MacroAssembler* masm, void StubCompiler::GenerateCheckPropertyCell(MacroAssembler* masm,
Handle<GlobalObject> global, Handle<GlobalObject> global,
Handle<Name> name, Handle<Name> name,
Register scratch, Register scratch,
...@@ -1156,9 +1152,7 @@ class CallInterceptorCompiler BASE_EMBEDDED { ...@@ -1156,9 +1152,7 @@ class CallInterceptorCompiler BASE_EMBEDDED {
}; };
// Calls GenerateCheckPropertyCell for each global object in the prototype chain void StubCompiler::GenerateCheckPropertyCells(MacroAssembler* masm,
// from object to (but not including) holder.
static void GenerateCheckPropertyCells(MacroAssembler* masm,
Handle<JSObject> object, Handle<JSObject> object,
Handle<JSObject> holder, Handle<JSObject> holder,
Handle<Name> name, Handle<Name> name,
...@@ -1373,26 +1367,6 @@ Register LoadStubCompiler::CallbackHandlerFrontend( ...@@ -1373,26 +1367,6 @@ Register LoadStubCompiler::CallbackHandlerFrontend(
} }
void LoadStubCompiler::NonexistentHandlerFrontend(
Handle<JSObject> object,
Handle<JSObject> last,
Handle<Name> name,
Label* success,
Handle<GlobalObject> global) {
Label miss;
HandlerFrontendHeader(object, receiver(), last, name, &miss);
// If the last object in the prototype chain is a global object,
// check that the global property cell is empty.
if (!global.is_null()) {
GenerateCheckPropertyCell(masm(), global, name, scratch2(), &miss);
}
HandlerFrontendFooter(name, success, &miss);
}
void LoadStubCompiler::GenerateLoadField(Register reg, void LoadStubCompiler::GenerateLoadField(Register reg,
Handle<JSObject> holder, Handle<JSObject> holder,
PropertyIndex field, PropertyIndex field,
......
...@@ -137,38 +137,34 @@ static void ProbeTable(Isolate* isolate, ...@@ -137,38 +137,34 @@ static void ProbeTable(Isolate* isolate,
} }
// Helper function used to check that the dictionary doesn't contain void StubCompiler::GenerateDictionaryNegativeLookup(MacroAssembler* masm,
// the property. This function may return false negatives, so miss_label
// must always call a backup property check that is complete.
// This function is safe to call if the receiver has fast properties.
// Name must be unique and receiver must be a heap object.
static void GenerateDictionaryNegativeLookup(MacroAssembler* masm,
Label* miss_label, Label* miss_label,
Register receiver, Register receiver,
Handle<Name> name, Handle<Name> name,
Register r0, Register scratch0,
Register r1) { Register scratch1) {
ASSERT(name->IsUniqueName()); ASSERT(name->IsUniqueName());
ASSERT(!receiver.is(scratch0));
Counters* counters = masm->isolate()->counters(); Counters* counters = masm->isolate()->counters();
__ IncrementCounter(counters->negative_lookups(), 1); __ IncrementCounter(counters->negative_lookups(), 1);
__ IncrementCounter(counters->negative_lookups_miss(), 1); __ IncrementCounter(counters->negative_lookups_miss(), 1);
__ mov(r0, FieldOperand(receiver, HeapObject::kMapOffset)); __ mov(scratch0, FieldOperand(receiver, HeapObject::kMapOffset));
const int kInterceptorOrAccessCheckNeededMask = const int kInterceptorOrAccessCheckNeededMask =
(1 << Map::kHasNamedInterceptor) | (1 << Map::kIsAccessCheckNeeded); (1 << Map::kHasNamedInterceptor) | (1 << Map::kIsAccessCheckNeeded);
// Bail out if the receiver has a named interceptor or requires access checks. // Bail out if the receiver has a named interceptor or requires access checks.
__ test_b(FieldOperand(r0, Map::kBitFieldOffset), __ test_b(FieldOperand(scratch0, Map::kBitFieldOffset),
kInterceptorOrAccessCheckNeededMask); kInterceptorOrAccessCheckNeededMask);
__ j(not_zero, miss_label); __ j(not_zero, miss_label);
// Check that receiver is a JSObject. // Check that receiver is a JSObject.
__ CmpInstanceType(r0, FIRST_SPEC_OBJECT_TYPE); __ CmpInstanceType(scratch0, FIRST_SPEC_OBJECT_TYPE);
__ j(below, miss_label); __ j(below, miss_label);
// Load properties array. // Load properties array.
Register properties = r0; Register properties = scratch0;
__ mov(properties, FieldOperand(receiver, JSObject::kPropertiesOffset)); __ mov(properties, FieldOperand(receiver, JSObject::kPropertiesOffset));
// Check that the properties array is a dictionary. // Check that the properties array is a dictionary.
...@@ -182,7 +178,7 @@ static void GenerateDictionaryNegativeLookup(MacroAssembler* masm, ...@@ -182,7 +178,7 @@ static void GenerateDictionaryNegativeLookup(MacroAssembler* masm,
&done, &done,
properties, properties,
name, name,
r1); scratch1);
__ bind(&done); __ bind(&done);
__ DecrementCounter(counters->negative_lookups_miss(), 1); __ DecrementCounter(counters->negative_lookups_miss(), 1);
} }
...@@ -792,7 +788,7 @@ void StoreStubCompiler::GenerateRestoreName(MacroAssembler* masm, ...@@ -792,7 +788,7 @@ void StoreStubCompiler::GenerateRestoreName(MacroAssembler* masm,
// Generate code to check that a global property cell is empty. Create // Generate code to check that a global property cell is empty. Create
// the property cell at compilation time if no cell exists for the // the property cell at compilation time if no cell exists for the
// property. // property.
static void GenerateCheckPropertyCell(MacroAssembler* masm, void StubCompiler::GenerateCheckPropertyCell(MacroAssembler* masm,
Handle<GlobalObject> global, Handle<GlobalObject> global,
Handle<Name> name, Handle<Name> name,
Register scratch, Register scratch,
...@@ -1122,9 +1118,7 @@ void StoreStubCompiler::GenerateStoreField(MacroAssembler* masm, ...@@ -1122,9 +1118,7 @@ void StoreStubCompiler::GenerateStoreField(MacroAssembler* masm,
} }
// Calls GenerateCheckPropertyCell for each global object in the prototype chain void StubCompiler::GenerateCheckPropertyCells(MacroAssembler* masm,
// from object to (but not including) holder.
static void GenerateCheckPropertyCells(MacroAssembler* masm,
Handle<JSObject> object, Handle<JSObject> object,
Handle<JSObject> holder, Handle<JSObject> holder,
Handle<Name> name, Handle<Name> name,
...@@ -1355,26 +1349,6 @@ Register LoadStubCompiler::CallbackHandlerFrontend( ...@@ -1355,26 +1349,6 @@ Register LoadStubCompiler::CallbackHandlerFrontend(
} }
void LoadStubCompiler::NonexistentHandlerFrontend(
Handle<JSObject> object,
Handle<JSObject> last,
Handle<Name> name,
Label* success,
Handle<GlobalObject> global) {
Label miss;
HandlerFrontendHeader(object, receiver(), last, name, &miss);
// If the last object in the prototype chain is a global object,
// check that the global property cell is empty.
if (!global.is_null()) {
GenerateCheckPropertyCell(masm(), global, name, scratch2(), &miss);
}
HandlerFrontendFooter(name, success, &miss);
}
void LoadStubCompiler::GenerateLoadField(Register reg, void LoadStubCompiler::GenerateLoadField(Register reg,
Handle<JSObject> holder, Handle<JSObject> holder,
PropertyIndex field, PropertyIndex field,
......
...@@ -117,18 +117,14 @@ static void ProbeTable(Isolate* isolate, ...@@ -117,18 +117,14 @@ static void ProbeTable(Isolate* isolate,
} }
// Helper function used to check that the dictionary doesn't contain void StubCompiler::GenerateDictionaryNegativeLookup(MacroAssembler* masm,
// the property. This function may return false negatives, so miss_label
// must always call a backup property check that is complete.
// This function is safe to call if the receiver has fast properties.
// Name must be unique and receiver must be a heap object.
static void GenerateDictionaryNegativeLookup(MacroAssembler* masm,
Label* miss_label, Label* miss_label,
Register receiver, Register receiver,
Handle<Name> name, Handle<Name> name,
Register scratch0, Register scratch0,
Register scratch1) { Register scratch1) {
ASSERT(name->IsUniqueName()); ASSERT(name->IsUniqueName());
ASSERT(!receiver.is(scratch0));
Counters* counters = masm->isolate()->counters(); Counters* counters = masm->isolate()->counters();
__ IncrementCounter(counters->negative_lookups(), 1, scratch0, scratch1); __ IncrementCounter(counters->negative_lookups(), 1, scratch0, scratch1);
__ IncrementCounter(counters->negative_lookups_miss(), 1, scratch0, scratch1); __ IncrementCounter(counters->negative_lookups_miss(), 1, scratch0, scratch1);
...@@ -408,10 +404,7 @@ void StubCompiler::GenerateLoadFunctionPrototype(MacroAssembler* masm, ...@@ -408,10 +404,7 @@ void StubCompiler::GenerateLoadFunctionPrototype(MacroAssembler* masm,
} }
// Generate code to check that a global property cell is empty. Create void StubCompiler::GenerateCheckPropertyCell(MacroAssembler* masm,
// the property cell at compilation time if no cell exists for the
// property.
static void GenerateCheckPropertyCell(MacroAssembler* masm,
Handle<GlobalObject> global, Handle<GlobalObject> global,
Handle<Name> name, Handle<Name> name,
Register scratch, Register scratch,
...@@ -1149,9 +1142,7 @@ class CallInterceptorCompiler BASE_EMBEDDED { ...@@ -1149,9 +1142,7 @@ class CallInterceptorCompiler BASE_EMBEDDED {
}; };
// Calls GenerateCheckPropertyCell for each global object in the prototype chain void StubCompiler::GenerateCheckPropertyCells(MacroAssembler* masm,
// from object to (but not including) holder.
static void GenerateCheckPropertyCells(MacroAssembler* masm,
Handle<JSObject> object, Handle<JSObject> object,
Handle<JSObject> holder, Handle<JSObject> holder,
Handle<Name> name, Handle<Name> name,
...@@ -1364,26 +1355,6 @@ Register LoadStubCompiler::CallbackHandlerFrontend( ...@@ -1364,26 +1355,6 @@ Register LoadStubCompiler::CallbackHandlerFrontend(
} }
void LoadStubCompiler::NonexistentHandlerFrontend(
Handle<JSObject> object,
Handle<JSObject> last,
Handle<Name> name,
Label* success,
Handle<GlobalObject> global) {
Label miss;
HandlerFrontendHeader(object, receiver(), last, name, &miss);
// If the last object in the prototype chain is a global object,
// check that the global property cell is empty.
if (!global.is_null()) {
GenerateCheckPropertyCell(masm(), global, name, scratch2(), &miss);
}
HandlerFrontendFooter(name, success, &miss);
}
void LoadStubCompiler::GenerateLoadField(Register reg, void LoadStubCompiler::GenerateLoadField(Register reg,
Handle<JSObject> holder, Handle<JSObject> holder,
PropertyIndex field, PropertyIndex field,
......
...@@ -1208,6 +1208,40 @@ Register BaseLoadStoreStubCompiler::HandlerFrontend(Handle<JSObject> object, ...@@ -1208,6 +1208,40 @@ Register BaseLoadStoreStubCompiler::HandlerFrontend(Handle<JSObject> object,
} }
void LoadStubCompiler::NonexistentHandlerFrontend(
Handle<JSObject> object,
Handle<JSObject> last,
Handle<Name> name,
Label* success,
Handle<GlobalObject> global) {
Label miss;
Register holder =
HandlerFrontendHeader(object, receiver(), last, name, &miss);
if (!last->HasFastProperties() &&
!last->IsJSGlobalObject() &&
!last->IsJSGlobalProxy()) {
if (!name->IsUniqueName()) {
ASSERT(name->IsString());
name = factory()->InternalizeString(Handle<String>::cast(name));
}
ASSERT(last->property_dictionary()->FindEntry(*name) ==
NameDictionary::kNotFound);
GenerateDictionaryNegativeLookup(masm(), &miss, holder, name,
scratch2(), scratch3());
}
// If the last object in the prototype chain is a global object,
// check that the global property cell is empty.
if (!global.is_null()) {
GenerateCheckPropertyCell(masm(), global, name, scratch2(), &miss);
}
HandlerFrontendFooter(name, success, &miss);
}
Handle<Code> LoadStubCompiler::CompileLoadField( Handle<Code> LoadStubCompiler::CompileLoadField(
Handle<JSObject> object, Handle<JSObject> object,
Handle<JSObject> holder, Handle<JSObject> holder,
......
...@@ -434,6 +434,18 @@ class StubCompiler BASE_EMBEDDED { ...@@ -434,6 +434,18 @@ class StubCompiler BASE_EMBEDDED {
int index, int index,
Register prototype); Register prototype);
// Helper function used to check that the dictionary doesn't contain
// the property. This function may return false negatives, so miss_label
// must always call a backup property check that is complete.
// This function is safe to call if the receiver has fast properties.
// Name must be unique and receiver must be a heap object.
static void GenerateDictionaryNegativeLookup(MacroAssembler* masm,
Label* miss_label,
Register receiver,
Handle<Name> name,
Register r0,
Register r1);
// Generates prototype loading code that uses the objects from the // Generates prototype loading code that uses the objects from the
// context we were in when this function was called. If the context // context we were in when this function was called. If the context
// has changed, a jump to miss is performed. This ties the generated // has changed, a jump to miss is performed. This ties the generated
...@@ -469,6 +481,24 @@ class StubCompiler BASE_EMBEDDED { ...@@ -469,6 +481,24 @@ class StubCompiler BASE_EMBEDDED {
Register scratch2, Register scratch2,
Label* miss_label); Label* miss_label);
// Generate code to check that a global property cell is empty. Create
// the property cell at compilation time if no cell exists for the
// property.
static void GenerateCheckPropertyCell(MacroAssembler* masm,
Handle<GlobalObject> global,
Handle<Name> name,
Register scratch,
Label* miss);
// Calls GenerateCheckPropertyCell for each global object in the prototype
// chain from object to (but not including) holder.
static void GenerateCheckPropertyCells(MacroAssembler* masm,
Handle<JSObject> object,
Handle<JSObject> holder,
Handle<Name> name,
Register scratch,
Label* miss);
static void TailCallBuiltin(MacroAssembler* masm, Builtins::Name name); static void TailCallBuiltin(MacroAssembler* masm, Builtins::Name name);
// Generates code that verifies that the property holder has not changed // Generates code that verifies that the property holder has not changed
......
...@@ -107,38 +107,34 @@ static void ProbeTable(Isolate* isolate, ...@@ -107,38 +107,34 @@ static void ProbeTable(Isolate* isolate,
} }
// Helper function used to check that the dictionary doesn't contain void StubCompiler::GenerateDictionaryNegativeLookup(MacroAssembler* masm,
// the property. This function may return false negatives, so miss_label
// must always call a backup property check that is complete.
// This function is safe to call if the receiver has fast properties.
// Name must be unique and receiver must be a heap object.
static void GenerateDictionaryNegativeLookup(MacroAssembler* masm,
Label* miss_label, Label* miss_label,
Register receiver, Register receiver,
Handle<Name> name, Handle<Name> name,
Register r0, Register scratch0,
Register r1) { Register scratch1) {
ASSERT(name->IsUniqueName()); ASSERT(name->IsUniqueName());
ASSERT(!receiver.is(scratch0));
Counters* counters = masm->isolate()->counters(); Counters* counters = masm->isolate()->counters();
__ IncrementCounter(counters->negative_lookups(), 1); __ IncrementCounter(counters->negative_lookups(), 1);
__ IncrementCounter(counters->negative_lookups_miss(), 1); __ IncrementCounter(counters->negative_lookups_miss(), 1);
__ movq(r0, FieldOperand(receiver, HeapObject::kMapOffset)); __ movq(scratch0, FieldOperand(receiver, HeapObject::kMapOffset));
const int kInterceptorOrAccessCheckNeededMask = const int kInterceptorOrAccessCheckNeededMask =
(1 << Map::kHasNamedInterceptor) | (1 << Map::kIsAccessCheckNeeded); (1 << Map::kHasNamedInterceptor) | (1 << Map::kIsAccessCheckNeeded);
// Bail out if the receiver has a named interceptor or requires access checks. // Bail out if the receiver has a named interceptor or requires access checks.
__ testb(FieldOperand(r0, Map::kBitFieldOffset), __ testb(FieldOperand(scratch0, Map::kBitFieldOffset),
Immediate(kInterceptorOrAccessCheckNeededMask)); Immediate(kInterceptorOrAccessCheckNeededMask));
__ j(not_zero, miss_label); __ j(not_zero, miss_label);
// Check that receiver is a JSObject. // Check that receiver is a JSObject.
__ CmpInstanceType(r0, FIRST_SPEC_OBJECT_TYPE); __ CmpInstanceType(scratch0, FIRST_SPEC_OBJECT_TYPE);
__ j(below, miss_label); __ j(below, miss_label);
// Load properties array. // Load properties array.
Register properties = r0; Register properties = scratch0;
__ movq(properties, FieldOperand(receiver, JSObject::kPropertiesOffset)); __ movq(properties, FieldOperand(receiver, JSObject::kPropertiesOffset));
// Check that the properties array is a dictionary. // Check that the properties array is a dictionary.
...@@ -152,7 +148,7 @@ static void GenerateDictionaryNegativeLookup(MacroAssembler* masm, ...@@ -152,7 +148,7 @@ static void GenerateDictionaryNegativeLookup(MacroAssembler* masm,
&done, &done,
properties, properties,
name, name,
r1); scratch1);
__ bind(&done); __ bind(&done);
__ DecrementCounter(counters->negative_lookups_miss(), 1); __ DecrementCounter(counters->negative_lookups_miss(), 1);
} }
...@@ -777,10 +773,7 @@ void StoreStubCompiler::GenerateRestoreName(MacroAssembler* masm, ...@@ -777,10 +773,7 @@ void StoreStubCompiler::GenerateRestoreName(MacroAssembler* masm,
} }
// Generate code to check that a global property cell is empty. Create void StubCompiler::GenerateCheckPropertyCell(MacroAssembler* masm,
// the property cell at compilation time if no cell exists for the
// property.
static void GenerateCheckPropertyCell(MacroAssembler* masm,
Handle<GlobalObject> global, Handle<GlobalObject> global,
Handle<Name> name, Handle<Name> name,
Register scratch, Register scratch,
...@@ -1054,9 +1047,7 @@ void StoreStubCompiler::GenerateStoreField(MacroAssembler* masm, ...@@ -1054,9 +1047,7 @@ void StoreStubCompiler::GenerateStoreField(MacroAssembler* masm,
} }
// Calls GenerateCheckPropertyCell for each global object in the prototype chain void StubCompiler::GenerateCheckPropertyCells(MacroAssembler* masm,
// from object to (but not including) holder.
static void GenerateCheckPropertyCells(MacroAssembler* masm,
Handle<JSObject> object, Handle<JSObject> object,
Handle<JSObject> holder, Handle<JSObject> holder,
Handle<Name> name, Handle<Name> name,
...@@ -1282,26 +1273,6 @@ Register LoadStubCompiler::CallbackHandlerFrontend( ...@@ -1282,26 +1273,6 @@ Register LoadStubCompiler::CallbackHandlerFrontend(
} }
void LoadStubCompiler::NonexistentHandlerFrontend(
Handle<JSObject> object,
Handle<JSObject> last,
Handle<Name> name,
Label* success,
Handle<GlobalObject> global) {
Label miss;
HandlerFrontendHeader(object, receiver(), last, name, &miss);
// If the last object in the prototype chain is a global object,
// check that the global property cell is empty.
if (!global.is_null()) {
GenerateCheckPropertyCell(masm(), global, name, scratch2(), &miss);
}
HandlerFrontendFooter(name, success, &miss);
}
void LoadStubCompiler::GenerateLoadField(Register reg, void LoadStubCompiler::GenerateLoadField(Register reg,
Handle<JSObject> holder, Handle<JSObject> holder,
PropertyIndex field, PropertyIndex field,
......
// Copyright 2013 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
function test(expected, holder) {
assertEquals(expected, holder.property);
}
var holder = {}
holder.__proto__ = null;
holder.property = "foo";
delete holder.property;
test(undefined, holder);
test(undefined, holder);
test(undefined, holder);
holder.property = "bar";
test("bar", holder);
test("bar", holder);
// Now the same thing with a nontrivial prototype chain.
function test2(expected, holder) {
assertEquals(expected, holder.prop2);
}
var holder2 = {}
holder2.prop2 = "foo";
holder2.__proto__ = null;
function Receiver() {}
Receiver.prototype = holder2;
var rec2 = new Receiver();
delete holder2.prop2;
test2(undefined, rec2);
test2(undefined, rec2);
test2(undefined, rec2);
holder2.prop2 = "bar";
test2("bar", rec2);
test2("bar", rec2);
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