From 95cb7c5ae3f56aeea556d4256f36e763699be00b Mon Sep 17 00:00:00 2001 From: Michael Holman Date: Tue, 27 Feb 2018 12:38:01 -0800 Subject: [PATCH 1/5] optimize object.assign({}, obj) --- lib/Common/ConfigFlagsList.h | 1 + lib/Runtime/Library/JavascriptObject.cpp | 12 ++- lib/Runtime/Types/DynamicObject.cpp | 95 +++++++++++++++++++++++- lib/Runtime/Types/DynamicObject.h | 5 +- 4 files changed, 107 insertions(+), 6 deletions(-) diff --git a/lib/Common/ConfigFlagsList.h b/lib/Common/ConfigFlagsList.h index 787337dcfb5..f87e9ac9fba 100644 --- a/lib/Common/ConfigFlagsList.h +++ b/lib/Common/ConfigFlagsList.h @@ -358,6 +358,7 @@ PHASE(All) PHASE(Error) PHASE(PropertyRecord) PHASE(TypePathDynamicSize) + PHASE(ObjectCopy) PHASE(ShareTypesWithAttributes) PHASE(ShareAccessorTypes) PHASE(ConditionalCompilation) diff --git a/lib/Runtime/Library/JavascriptObject.cpp b/lib/Runtime/Library/JavascriptObject.cpp index ad40179a9d1..36979110720 100644 --- a/lib/Runtime/Library/JavascriptObject.cpp +++ b/lib/Runtime/Library/JavascriptObject.cpp @@ -1538,7 +1538,17 @@ namespace Js // else use enumerator to extract keys from source else { - AssignForGenericObjects(from, to, scriptContext); + DynamicObject* fromObj = JavascriptOperators::TryFromVar(from); + DynamicObject* toObj = JavascriptOperators::TryFromVar(to); + bool cloned = false; + if (toObj && fromObj && toObj->GetType() == scriptContext->GetLibrary()->GetObjectType()) + { + cloned = toObj->TryCopy(fromObj); + } + if(!cloned) + { + AssignForGenericObjects(from, to, scriptContext); + } } } diff --git a/lib/Runtime/Types/DynamicObject.cpp b/lib/Runtime/Types/DynamicObject.cpp index 6247da24c0e..5d27ca86f6c 100644 --- a/lib/Runtime/Types/DynamicObject.cpp +++ b/lib/Runtime/Types/DynamicObject.cpp @@ -58,8 +58,8 @@ namespace Js int propertyCount = typeHandler->GetPropertyCount(); int inlineSlotCapacity = GetTypeHandler()->GetInlineSlotCapacity(); int inlineSlotCount = min(inlineSlotCapacity, propertyCount); - Var * srcSlots = reinterpret_cast(reinterpret_cast(instance) + typeHandler->GetOffsetOfInlineSlots()); - Field(Var) * dstSlots = reinterpret_cast(reinterpret_cast(this) + typeHandler->GetOffsetOfInlineSlots()); + Field(Var)* srcSlots = instance->GetInlineSlots(); + Field(Var)* dstSlots = this->GetInlineSlots(); #if !FLOATVAR ScriptContext * scriptContext = this->GetScriptContext(); #endif @@ -755,6 +755,94 @@ namespace Js } } + Field(Var)* DynamicObject::GetInlineSlots() const + { + return reinterpret_cast(reinterpret_cast(this) + this->GetOffsetOfInlineSlots()); + } + + bool DynamicObject::TryCopy(DynamicObject* from) + { + // Validate that objects are compatible + if (this->GetTypeHandler()->GetInlineSlotCapacity() != from->GetTypeHandler()->GetInlineSlotCapacity()) + { + if (PHASE_TRACE1(ObjectCopyPhase)) + { + Output::Print(_u("ObjectCopy: Can't copy: inline slot capacity doesn't match, from: %u, to: %u\n"), + from->GetTypeHandler()->GetInlineSlotCapacity(), + this->GetTypeHandler()->GetInlineSlotCapacity()); + } + return false; + } + if (this->GetPrototype() != from->GetPrototype()) + { + if (PHASE_TRACE1(ObjectCopyPhase)) + { + Output::Print(_u("ObjectCopy: Can't copy: Protoytypes don't match\n")); + } + return false; + } + if (!from->GetTypeHandler()->AllPropertiesAreEnumerable()) + { + if (PHASE_TRACE1(ObjectCopyPhase)) + { + Output::Print(_u("ObjectCopy: Can't copy: from obj has non-enumerable properties\n")); + } + return false; + } + if (!from->GetTypeHandler()->IsPathTypeHandler()) + { + if (PHASE_TRACE1(ObjectCopyPhase)) + { + Output::Print(_u("ObjectCopy: Can't copy: Don't have PathTypeHandler\n")); + } + return false; + } + if(PathTypeHandlerBase::FromTypeHandler(from->GetTypeHandler())->HasAccessors()) + { + if (PHASE_TRACE1(ObjectCopyPhase)) + { + Output::Print(_u("ObjectCopy: Can't copy: type handler has accessors\n")); + } + return false; + } + + // Share the type + if (!from->GetDynamicType()->ShareType()) + { + if (PHASE_TRACE1(ObjectCopyPhase)) + { + Output::Print(_u("ObjectCopy: Can't copy: failed to share type\n")); + } + return false; + } + + // Update this object + this->ReplaceType(from->GetDynamicType()); + this->InitSlots(this); + const int slotCapacity = GetTypeHandler()->GetSlotCapacity(); + const uint16 inlineSlotCapacity = GetTypeHandler()->GetInlineSlotCapacity(); + const int auxSlotCapacity = slotCapacity - inlineSlotCapacity; + + if (auxSlotCapacity > 0) + { + CopyArray(this->auxSlots, auxSlotCapacity, from->auxSlots, auxSlotCapacity); + } + if (inlineSlotCapacity != 0) + { + Field(Var)* thisInlineSlots = this->GetInlineSlots(); + Field(Var)* fromInlineSlots = from->GetInlineSlots(); + + CopyArray(thisInlineSlots, inlineSlotCapacity, fromInlineSlots, inlineSlotCapacity); + } + + if (PHASE_TRACE1(ObjectCopyPhase)) + { + Output::Print(_u("ObjectCopy succeeded\n")); + } + + return true; + } + bool DynamicObject::GetHasNoEnumerableProperties() { @@ -894,8 +982,7 @@ namespace Js Js::Var const* DynamicObject::GetInlineSlots_TTD() const { - return reinterpret_cast( - reinterpret_cast(this) + this->GetTypeHandler()->GetOffsetOfInlineSlots()); + return this->GetInlineSlots(); } Js::Var const* DynamicObject::GetAuxSlots_TTD() const diff --git a/lib/Runtime/Types/DynamicObject.h b/lib/Runtime/Types/DynamicObject.h index dd248b69761..0e59ff81ef0 100644 --- a/lib/Runtime/Types/DynamicObject.h +++ b/lib/Runtime/Types/DynamicObject.h @@ -114,6 +114,8 @@ namespace Js void ReplaceType(DynamicType * type); void ReplaceTypeWithPredecessorType(DynamicType * previousType); + Field(Var)* GetInlineSlots() const; + protected: DEFINE_VTABLE_CTOR(DynamicObject, RecyclableObject); DEFINE_MARSHAL_OBJECT_TO_SCRIPT_CONTEXT(DynamicObject); @@ -144,7 +146,6 @@ namespace Js Var GetSlot(int index); Var GetInlineSlot(int index); Var GetAuxSlot(int index); - #if DBG void SetSlot(PropertyId propertyId, bool allowLetConst, int index, Var value); void SetInlineSlot(PropertyId propertyId, bool allowLetConst, int index, Var value); @@ -210,6 +211,8 @@ namespace Js void InvalidateHasOnlyWritableDataPropertiesInPrototypeChainCacheIfPrototype(); void ResetObject(DynamicType* type, BOOL keepProperties); + bool TryCopy(DynamicObject* from); + virtual void SetIsPrototype(); bool HasLockedType() const; From 2d4084b21f1b764e9cb33b74400f15bad1699c94 Mon Sep 17 00:00:00 2001 From: Michael Holman Date: Tue, 13 Mar 2018 18:11:35 -0700 Subject: [PATCH 2/5] fix xplat build; address comments --- lib/Runtime/Debug/TTSnapObjects.cpp | 2 +- lib/Runtime/Types/DynamicObject.cpp | 22 +++++++++++----------- lib/Runtime/Types/DynamicObject.h | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/Runtime/Debug/TTSnapObjects.cpp b/lib/Runtime/Debug/TTSnapObjects.cpp index 37d53578af5..3eaab5a3abb 100644 --- a/lib/Runtime/Debug/TTSnapObjects.cpp +++ b/lib/Runtime/Debug/TTSnapObjects.cpp @@ -82,7 +82,7 @@ namespace TTD TTDVar* cpyBase = snpObject->VarArray; if(sHandler->InlineSlotCapacity != 0) { - Js::Var const* inlineSlots = dynObj->GetInlineSlots_TTD(); + Field(Js::Var) const* inlineSlots = dynObj->GetInlineSlots_TTD(); //copy all the properties (if they all fit into the inline slots) otherwise just copy all the inline slot values uint32 inlineSlotCount = min(sHandler->MaxPropertyIndex, sHandler->InlineSlotCapacity); diff --git a/lib/Runtime/Types/DynamicObject.cpp b/lib/Runtime/Types/DynamicObject.cpp index 5d27ca86f6c..3298bbe81cd 100644 --- a/lib/Runtime/Types/DynamicObject.cpp +++ b/lib/Runtime/Types/DynamicObject.cpp @@ -773,35 +773,35 @@ namespace Js } return false; } - if (this->GetPrototype() != from->GetPrototype()) + if (!from->GetTypeHandler()->IsPathTypeHandler()) { if (PHASE_TRACE1(ObjectCopyPhase)) { - Output::Print(_u("ObjectCopy: Can't copy: Protoytypes don't match\n")); + Output::Print(_u("ObjectCopy: Can't copy: Don't have PathTypeHandler\n")); } return false; } - if (!from->GetTypeHandler()->AllPropertiesAreEnumerable()) + if (PathTypeHandlerBase::FromTypeHandler(from->GetTypeHandler())->HasAccessors()) { if (PHASE_TRACE1(ObjectCopyPhase)) { - Output::Print(_u("ObjectCopy: Can't copy: from obj has non-enumerable properties\n")); + Output::Print(_u("ObjectCopy: Can't copy: type handler has accessors\n")); } return false; } - if (!from->GetTypeHandler()->IsPathTypeHandler()) + if (this->GetPrototype() != from->GetPrototype()) { if (PHASE_TRACE1(ObjectCopyPhase)) { - Output::Print(_u("ObjectCopy: Can't copy: Don't have PathTypeHandler\n")); + Output::Print(_u("ObjectCopy: Can't copy: Protoytypes don't match\n")); } return false; } - if(PathTypeHandlerBase::FromTypeHandler(from->GetTypeHandler())->HasAccessors()) + if (!from->GetTypeHandler()->AllPropertiesAreEnumerable()) { if (PHASE_TRACE1(ObjectCopyPhase)) { - Output::Print(_u("ObjectCopy: Can't copy: type handler has accessors\n")); + Output::Print(_u("ObjectCopy: Can't copy: from obj has non-enumerable properties\n")); } return false; } @@ -819,8 +819,8 @@ namespace Js // Update this object this->ReplaceType(from->GetDynamicType()); this->InitSlots(this); - const int slotCapacity = GetTypeHandler()->GetSlotCapacity(); - const uint16 inlineSlotCapacity = GetTypeHandler()->GetInlineSlotCapacity(); + const int slotCapacity = this->GetTypeHandler()->GetSlotCapacity(); + const uint16 inlineSlotCapacity = this->GetTypeHandler()->GetInlineSlotCapacity(); const int auxSlotCapacity = slotCapacity - inlineSlotCapacity; if (auxSlotCapacity > 0) @@ -980,7 +980,7 @@ namespace Js TTD::NSSnapObjects::StdExtractSetKindSpecificInfo(objData, nullptr); } - Js::Var const* DynamicObject::GetInlineSlots_TTD() const + Field(Js::Var) const* DynamicObject::GetInlineSlots_TTD() const { return this->GetInlineSlots(); } diff --git a/lib/Runtime/Types/DynamicObject.h b/lib/Runtime/Types/DynamicObject.h index 0e59ff81ef0..ece44446486 100644 --- a/lib/Runtime/Types/DynamicObject.h +++ b/lib/Runtime/Types/DynamicObject.h @@ -347,7 +347,7 @@ namespace Js virtual TTD::NSSnapObjects::SnapObjectType GetSnapTag_TTD() const override; virtual void ExtractSnapObjectDataInto(TTD::NSSnapObjects::SnapObject* objData, TTD::SlabAllocator& alloc) override; - Js::Var const* GetInlineSlots_TTD() const; + Field(Js::Var) const* GetInlineSlots_TTD() const; Js::Var const* GetAuxSlots_TTD() const; #if ENABLE_OBJECT_SOURCE_TRACKING From 93212c88e6e90981c2fe28d6a881ca7bc4905733 Mon Sep 17 00:00:00 2001 From: Michael Holman Date: Wed, 14 Mar 2018 16:14:51 -0700 Subject: [PATCH 3/5] add tests --- test/Object/assign.js | 93 +++++++++++++++++++++++++++++++++++++++++++ test/Object/rlexe.xml | 6 +++ 2 files changed, 99 insertions(+) create mode 100644 test/Object/assign.js diff --git a/test/Object/assign.js b/test/Object/assign.js new file mode 100644 index 00000000000..06ee770a0c5 --- /dev/null +++ b/test/Object/assign.js @@ -0,0 +1,93 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js"); + +var tests = [ + { + name: "simple copy", + body: function () + { + let orig = {}; + orig.a = 1; + orig.b = "asdf"; + let newObj = Object.assign({}, orig); + assert.areEqual(newObj.b, orig.b); + assert.areEqual(newObj.a, orig.a); + } + }, + { + name: "non-path type handler", + body: function () + { + let orig = {}; + orig.a = 1; + orig.b = "asdf"; + delete orig.a; + let newObj = Object.assign({}, orig); + assert.areEqual(newObj.b, orig.b); + assert.areEqual(newObj.a, orig.a); + } + }, + { + name: "has getter", + body: function () + { + let orig = {}; + orig.a = 1; + Object.defineProperty(orig, 'b', { + get: function() { return "asdf"; }, enumerable: true + }); + let newObj = Object.assign({}, orig); + assert.areEqual(newObj.b, orig.b); + assert.areEqual(newObj.a, orig.a); + } + }, + { + name: "has setter", + body: function () + { + let orig = {}; + orig.a = 1; + Object.defineProperty(orig, 'b', { + set: function() { }, enumerable: true + }); + let newObj = Object.assign({}, orig); + assert.areEqual(newObj.b, orig.b); + assert.areEqual(newObj.a, orig.a); + } + }, + { + name: "different proto", + body: function () + { + let protoObj = {}; + let orig = Object.create(protoObj); + orig.a = 1; + orig.b = "asdf"; + + let newObj = Object.assign({}, orig); + assert.areEqual(newObj.b, orig.b); + assert.areEqual(newObj.a, orig.a); + } + }, + { + name: "non-enumerable", + body: function () + { + let orig = {}; + orig.a = 1; + Object.defineProperty(orig, 'b', { + value: "asdf", enumerable: false + }); + + let newObj = Object.assign({}, orig); + assert.areEqual(newObj.b, undefined); + assert.areEqual(newObj.a, orig.a); + } + } +]; + +testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" }); diff --git a/test/Object/rlexe.xml b/test/Object/rlexe.xml index 666aa734dd3..28a76de9e75 100644 --- a/test/Object/rlexe.xml +++ b/test/Object/rlexe.xml @@ -436,4 +436,10 @@ + + + assign.js + -args summary -endargs + + From 1bb71ab96058f9ccc9d25f34b141a3789c5ca15f Mon Sep 17 00:00:00 2001 From: Michael Holman Date: Wed, 14 Mar 2018 18:28:17 -0700 Subject: [PATCH 4/5] include trace, add another test case --- test/Object/assign.baseline | 8 ++++++++ test/Object/assign.js | 15 +++++++++++++++ test/Object/rlexe.xml | 3 ++- 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 test/Object/assign.baseline diff --git a/test/Object/assign.baseline b/test/Object/assign.baseline new file mode 100644 index 00000000000..4812f3a6fec --- /dev/null +++ b/test/Object/assign.baseline @@ -0,0 +1,8 @@ +ObjectCopy succeeded +ObjectCopy: Can't copy: Don't have PathTypeHandler +ObjectCopy: Can't copy: type handler has accessors +ObjectCopy: Can't copy: type handler has accessors +ObjectCopy: Can't copy: Protoytypes don't match +ObjectCopy: Can't copy: from obj has non-enumerable properties +ObjectCopy succeeded +pass diff --git a/test/Object/assign.js b/test/Object/assign.js index 06ee770a0c5..afaf3aeba6c 100644 --- a/test/Object/assign.js +++ b/test/Object/assign.js @@ -87,6 +87,21 @@ var tests = [ assert.areEqual(newObj.b, undefined); assert.areEqual(newObj.a, orig.a); } + }, + { + name: "proto accessor", + body: function () + { + Object.defineProperty(Object.prototype, 'b', { + get: function() { return "asdf"; } + }); + let orig = {}; + orig.a = 1; + + let newObj = Object.assign({}, orig); + assert.areEqual(newObj.b, "asdf"); + assert.areEqual(newObj.a, orig.a); + } } ]; diff --git a/test/Object/rlexe.xml b/test/Object/rlexe.xml index 28a76de9e75..cecc3f6e610 100644 --- a/test/Object/rlexe.xml +++ b/test/Object/rlexe.xml @@ -439,7 +439,8 @@ assign.js - -args summary -endargs + -args summary -endargs -trace:ObjectCopy + assign.baseline From 927377139ab5d127de746e81eea9aca8bd87c753 Mon Sep 17 00:00:00 2001 From: Michael Holman Date: Thu, 15 Mar 2018 13:16:25 -0700 Subject: [PATCH 5/5] address review comments --- lib/Runtime/Library/JavascriptObject.cpp | 72 ++++++++++++++---------- lib/Runtime/Library/JavascriptObject.h | 2 + lib/Runtime/Types/DynamicObject.cpp | 30 +++++++++- lib/Runtime/Types/DynamicObject.h | 2 + test/Object/assign.js | 3 + 5 files changed, 76 insertions(+), 33 deletions(-) diff --git a/lib/Runtime/Library/JavascriptObject.cpp b/lib/Runtime/Library/JavascriptObject.cpp index 36979110720..98ad884d313 100644 --- a/lib/Runtime/Library/JavascriptObject.cpp +++ b/lib/Runtime/Library/JavascriptObject.cpp @@ -1507,53 +1507,63 @@ namespace Js // 4. Let sources be the List of argument values starting with the second argument. // 5. For each element nextSource of sources, in ascending index order, - for (unsigned int i = 2; i < args.Info.Count; i++) + AssignHelper(args[2], to, scriptContext); + for (unsigned int i = 3; i < args.Info.Count; i++) { - // a. If nextSource is undefined or null, let keys be an empty List. - // b. Else, - // i.Let from be ToObject(nextSource). - // ii.ReturnIfAbrupt(from). - // iii.Let keys be from.[[OwnPropertyKeys]](). - // iv.ReturnIfAbrupt(keys). + AssignHelper(args[i], to, scriptContext); + } + + // 6. Return to. + return to; + } + + template + void JavascriptObject::AssignHelper(Var fromArg, RecyclableObject* to, ScriptContext* scriptContext) + { + // a. If nextSource is undefined or null, let keys be an empty List. + // b. Else, + // i.Let from be ToObject(nextSource). + // ii.ReturnIfAbrupt(from). + // iii.Let keys be from.[[OwnPropertyKeys]](). + // iv.ReturnIfAbrupt(keys). - RecyclableObject* from = nullptr; - if (!JavascriptConversion::ToObject(args[i], scriptContext, &from)) + RecyclableObject* from = nullptr; + if (!JavascriptConversion::ToObject(fromArg, scriptContext, &from)) + { + if (JavascriptOperators::IsUndefinedOrNull(fromArg)) { - if (JavascriptOperators::IsUndefinedOrNull(args[i])) - { - continue; - } - JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedObject, _u("Object.assign")); + return; } + JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedObject, _u("Object.assign")); + } #if ENABLE_COPYONACCESS_ARRAY - JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray(from); + JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray(from); #endif - // if proxy, take slow path by calling [[OwnPropertyKeys]] on source - if (JavascriptProxy::Is(from)) - { - AssignForProxyObjects(from, to, scriptContext); - } - // else use enumerator to extract keys from source - else + // if proxy, take slow path by calling [[OwnPropertyKeys]] on source + if (JavascriptProxy::Is(from)) + { + AssignForProxyObjects(from, to, scriptContext); + } + // else use enumerator to extract keys from source + else + { + bool copied = false; + if (tryCopy) { DynamicObject* fromObj = JavascriptOperators::TryFromVar(from); DynamicObject* toObj = JavascriptOperators::TryFromVar(to); - bool cloned = false; if (toObj && fromObj && toObj->GetType() == scriptContext->GetLibrary()->GetObjectType()) { - cloned = toObj->TryCopy(fromObj); - } - if(!cloned) - { - AssignForGenericObjects(from, to, scriptContext); + copied = toObj->TryCopy(fromObj); } } + if (!copied) + { + AssignForGenericObjects(from, to, scriptContext); + } } - - // 6. Return to. - return to; } void JavascriptObject::AssignForGenericObjects(RecyclableObject* from, RecyclableObject* to, ScriptContext* scriptContext) diff --git a/lib/Runtime/Library/JavascriptObject.h b/lib/Runtime/Library/JavascriptObject.h index af38ff0b5b7..4503370252e 100644 --- a/lib/Runtime/Library/JavascriptObject.h +++ b/lib/Runtime/Library/JavascriptObject.h @@ -110,6 +110,8 @@ namespace Js static JavascriptString* ToStringTagHelper(Var thisArg, ScriptContext* scriptContext, TypeId type); private: + template + static void AssignHelper(Var fromArg, RecyclableObject* to, ScriptContext* scriptContext); static void AssignForGenericObjects(RecyclableObject* from, RecyclableObject* to, ScriptContext* scriptContext); static void AssignForProxyObjects(RecyclableObject* from, RecyclableObject* to, ScriptContext* scriptContext); static JavascriptArray* CreateKeysHelper(RecyclableObject* object, ScriptContext* scriptContext, BOOL enumNonEnumerable, bool includeSymbolProperties, bool includeStringProperties, bool includeSpecialProperties); diff --git a/lib/Runtime/Types/DynamicObject.cpp b/lib/Runtime/Types/DynamicObject.cpp index 3298bbe81cd..d68f0139daf 100644 --- a/lib/Runtime/Types/DynamicObject.cpp +++ b/lib/Runtime/Types/DynamicObject.cpp @@ -760,9 +760,8 @@ namespace Js return reinterpret_cast(reinterpret_cast(this) + this->GetOffsetOfInlineSlots()); } - bool DynamicObject::TryCopy(DynamicObject* from) + bool DynamicObject::IsCompatibleForCopy(DynamicObject* from) const { - // Validate that objects are compatible if (this->GetTypeHandler()->GetInlineSlotCapacity() != from->GetTypeHandler()->GetInlineSlotCapacity()) { if (PHASE_TRACE1(ObjectCopyPhase)) @@ -805,8 +804,35 @@ namespace Js } return false; } + if (from->IsExternal()) + { + if (PHASE_TRACE1(ObjectCopyPhase)) + { + Output::Print(_u("ObjectCopy: Can't copy: from obj is External\n")); + } + return false; + } + if (this->GetScriptContext() != from->GetScriptContext()) + { + if (PHASE_TRACE1(ObjectCopyPhase)) + { + Output::Print(_u("ObjectCopy: Can't copy: from obj is from different ScriptContext\n")); + } + return false; + } + + return true; + } + bool DynamicObject::TryCopy(DynamicObject* from) + { + // Validate that objects are compatible + if (!this->IsCompatibleForCopy(from)) + { + return false; + } // Share the type + // Note: this will mark type as shared in case of success if (!from->GetDynamicType()->ShareType()) { if (PHASE_TRACE1(ObjectCopyPhase)) diff --git a/lib/Runtime/Types/DynamicObject.h b/lib/Runtime/Types/DynamicObject.h index ece44446486..9b3d80d53ca 100644 --- a/lib/Runtime/Types/DynamicObject.h +++ b/lib/Runtime/Types/DynamicObject.h @@ -157,6 +157,8 @@ namespace Js #endif private: + bool IsCompatibleForCopy(DynamicObject* from) const; + bool IsObjectHeaderInlinedTypeHandlerUnchecked() const; public: bool IsObjectHeaderInlinedTypeHandler() const; diff --git a/test/Object/assign.js b/test/Object/assign.js index afaf3aeba6c..85a5333029a 100644 --- a/test/Object/assign.js +++ b/test/Object/assign.js @@ -11,11 +11,14 @@ var tests = [ body: function () { let orig = {}; + let sym = Symbol("c"); orig.a = 1; orig.b = "asdf"; + orig[sym] = "qwert"; let newObj = Object.assign({}, orig); assert.areEqual(newObj.b, orig.b); assert.areEqual(newObj.a, orig.a); + assert.areEqual(newObj[sym], orig[sym]); } }, {