Преглед изворни кода

jsrt: JsObject Delete/Get/Has/OwnP../Set Property

AcmeAIR LTO gain (3%)

New JSRT property interface that API user can use without going through
convert-to-property-id process.

See # 3790 for details.

Also added a TODO note;
```
TODO: Can we make PropertyString && LiteralStringWithPropertyStringPtr
share the string buffer?
```

Once this PR is merged, there will be an additional PR on
node-chakracore end to benefit this new interface.
Oguz Bastemur пре 8 година
родитељ
комит
fc6240eff9

+ 51 - 18
lib/Common/Codex/Utf8Helper.h

@@ -86,30 +86,28 @@ namespace utf8
         return WideStringToNarrow(Allocator::allocate, sourceString, sourceCount, destStringPtr, destCount, allocateCount);
     }
 
-    ///
-    /// Use the codex library to encode a UTF8 string to UTF16.
-    /// The caller is responsible for freeing the memory, which is allocated
-    /// using Allocator.
-    /// The returned string is null terminated.
-    ///
-    template <typename AllocatorFunction>
-    HRESULT NarrowStringToWide(_In_ AllocatorFunction allocator,_In_ LPCSTR sourceString, size_t sourceCount, _Out_ LPWSTR* destStringPtr, _Out_ size_t* destCount, size_t* allocateCount = nullptr)
+    inline HRESULT NarrowStringToWideNoAlloc(_In_ LPCSTR sourceString, size_t sourceCount,
+        __out_ecount(destBufferCount) LPWSTR destString, size_t destBufferCount, _Out_ size_t* destCount)
     {
-        size_t cbSourceString = sourceCount;
         size_t sourceStart = 0;
-        size_t cbDestString = (sourceCount + 1) * sizeof(WCHAR);
-        if (cbDestString < sourceCount) // overflow ?
+        size_t cbSourceString = sourceCount;
+
+        if (sourceCount >= MAXUINT32)
         {
+            destString[0] = WCHAR(0);
             return E_OUTOFMEMORY;
         }
 
-        WCHAR* destString = (WCHAR*)allocator(cbDestString);
         if (destString == nullptr)
         {
-            return E_OUTOFMEMORY;
+            return E_INVALIDARG;
         }
 
-        if (allocateCount != nullptr) *allocateCount = cbDestString;
+        if (sourceCount >= destBufferCount)
+        {
+            destString[0] = WCHAR(0);
+            return E_INVALIDARG;
+        }
 
         for (; sourceStart < sourceCount; sourceStart++)
         {
@@ -127,7 +125,6 @@ namespace utf8
         {
             *destCount = sourceCount;
             destString[sourceCount] = WCHAR(0);
-            *destStringPtr = destString;
         }
         else
         {
@@ -136,20 +133,56 @@ namespace utf8
 
             charcount_t cchDestString = utf8::ByteIndexIntoCharacterIndex(remSourceString, cbSourceString - sourceStart);
             cchDestString += (charcount_t)sourceStart;
-            Assert (cchDestString <= sourceCount);
+            if (cchDestString > sourceCount)
+            {
+                return E_OUTOFMEMORY;
+            }
 
             // Some node tests depend on the utf8 decoder not swallowing invalid unicode characters
             // instead of replacing them with the "replacement" chracter. Pass a flag to our
             // decoder to require such behavior
             utf8::DecodeUnitsIntoAndNullTerminateNoAdvance(remDestString, remSourceString, (LPCUTF8) sourceString + cbSourceString, DecodeOptions::doAllowInvalidWCHARs);
-            Assert(destString[cchDestString] == 0);
+
             static_assert(sizeof(utf8char_t) == sizeof(char), "Needs to be valid for cast");
-            *destStringPtr = destString;
             *destCount = cchDestString;
         }
+
+        Assert(destString[*destCount] == 0);
+
         return S_OK;
     }
 
+    ///
+    /// Use the codex library to encode a UTF8 string to UTF16.
+    /// The caller is responsible for freeing the memory, which is allocated
+    /// using Allocator.
+    /// The returned string is null terminated.
+    ///
+    template <typename AllocatorFunction>
+    HRESULT NarrowStringToWide(_In_ AllocatorFunction allocator,_In_ LPCSTR sourceString,
+        size_t sourceCount, _Out_ LPWSTR* destStringPtr, _Out_ size_t* destCount, size_t* allocateCount = nullptr)
+    {
+        size_t cbDestString = (sourceCount + 1) * sizeof(WCHAR);
+        if (cbDestString < sourceCount) // overflow ?
+        {
+            return E_OUTOFMEMORY;
+        }
+
+        WCHAR* destString = (WCHAR*)allocator(cbDestString);
+        if (destString == nullptr)
+        {
+            return E_OUTOFMEMORY;
+        }
+
+        if (allocateCount != nullptr)
+        {
+            *allocateCount = cbDestString;
+        }
+
+        *destStringPtr = destString;
+        return NarrowStringToWideNoAlloc(sourceString, sourceCount, destString, sourceCount + 1, destCount);
+    }
+
     template <class Allocator>
     HRESULT NarrowStringToWide(_In_ LPCSTR sourceString, size_t sourceCount, _Out_ LPWSTR* destStringPtr, _Out_ size_t* destCount, size_t* allocateCount = nullptr)
     {

+ 139 - 8
lib/Jsrt/ChakraCore.h

@@ -764,10 +764,10 @@ CHAKRA_API
 ///     The code <c>JsNoError</c> if the operation succeeded, a failure code otherwise.
 /// </returns>
 CHAKRA_API
-JsLessThan(
-    _In_ JsValueRef object1,
-    _In_ JsValueRef object2,
-    _Out_ bool *result);
+    JsLessThan(
+        _In_ JsValueRef object1,
+        _In_ JsValueRef object2,
+        _Out_ bool *result);
 
 /// <summary>
 ///     Determine if one JavaScript value is less than or equal to another JavaScript value.
@@ -787,10 +787,141 @@ JsLessThan(
 ///     The code <c>JsNoError</c> if the operation succeeded, a failure code otherwise.
 /// </returns>
 CHAKRA_API
-JsLessThanOrEqual(
-    _In_ JsValueRef object1,
-    _In_ JsValueRef object2,
-    _Out_ bool *result);
+    JsLessThanOrEqual(
+        _In_ JsValueRef object1,
+        _In_ JsValueRef object2,
+        _Out_ bool *result);
 
+/// <summary>
+///     Gets an object's property.
+/// </summary>
+/// <remarks>
+///     Requires an active script context.
+/// </remarks>
+/// <param name="object">The object that contains the property.</param>
+/// <param name="key">The key (JavascriptString) to the property.</param>
+/// <param name="value">The value of the property.</param>
+/// <returns>
+///     The code <c>JsNoError</c> if the operation succeeded, a failure code otherwise.
+/// </returns>
+CHAKRA_API
+    JsObjectGetProperty(
+        _In_ JsValueRef object,
+        _In_ JsValueRef key,
+        _Out_ JsValueRef *value);
+
+/// <summary>
+///     Puts an object's property.
+/// </summary>
+/// <remarks>
+///     Requires an active script context.
+/// </remarks>
+/// <param name="object">The object that contains the property.</param>
+/// <param name="key">The key (JavascriptString) to the property.</param>
+/// <param name="value">The new value of the property.</param>
+/// <param name="useStrictRules">The property set should follow strict mode rules.</param>
+/// <returns>
+///     The code <c>JsNoError</c> if the operation succeeded, a failure code otherwise.
+/// </returns>
+CHAKRA_API
+    JsObjectSetProperty(
+        _In_ JsValueRef object,
+        _In_ JsValueRef key,
+        _In_ JsValueRef value,
+        _In_ bool useStrictRules);
+
+/// <summary>
+///     Determines whether an object has a property.
+/// </summary>
+/// <remarks>
+///     Requires an active script context.
+/// </remarks>
+/// <param name="object">The object that may contain the property.</param>
+/// <param name="key">The key (JavascriptString) to the property.</param>
+/// <param name="hasProperty">Whether the object (or a prototype) has the property.</param>
+/// <returns>
+///     The code <c>JsNoError</c> if the operation succeeded, a failure code otherwise.
+/// </returns>
+CHAKRA_API
+    JsObjectHasProperty(
+        _In_ JsValueRef object,
+        _In_ JsValueRef key,
+        _Out_ bool *hasProperty);
+
+/// <summary>
+///     Defines a new object's own property from a property descriptor.
+/// </summary>
+/// <remarks>
+///     Requires an active script context.
+/// </remarks>
+/// <param name="object">The object that has the property.</param>
+/// <param name="key">The key (JavascriptString) to the property.</param>
+/// <param name="propertyDescriptor">The property descriptor.</param>
+/// <param name="result">Whether the property was defined.</param>
+/// <returns>
+///     The code <c>JsNoError</c> if the operation succeeded, a failure code otherwise.
+/// </returns>
+CHAKRA_API
+    JsObjectDefineProperty(
+        _In_ JsValueRef object,
+        _In_ JsValueRef key,
+        _In_ JsValueRef propertyDescriptor,
+        _Out_ bool *result);
+
+/// <summary>
+///     Deletes an object's property.
+/// </summary>
+/// <remarks>
+///     Requires an active script context.
+/// </remarks>
+/// <param name="object">The object that contains the property.</param>
+/// <param name="key">The key (JavascriptString) to the property.</param>
+/// <param name="useStrictRules">The property set should follow strict mode rules.</param>
+/// <param name="result">Whether the property was deleted.</param>
+/// <returns>
+///     The code <c>JsNoError</c> if the operation succeeded, a failure code otherwise.
+/// </returns>
+CHAKRA_API
+    JsObjectDeleteProperty(
+        _In_ JsValueRef object,
+        _In_ JsValueRef key,
+        _In_ bool useStrictRules,
+        _Out_ JsValueRef *result);
+
+/// <summary>
+///     Gets a property descriptor for an object's own property.
+/// </summary>
+/// <remarks>
+///     Requires an active script context.
+/// </remarks>
+/// <param name="object">The object that has the property.</param>
+/// <param name="key">The key (JavascriptString) to the property.</param>
+/// <param name="propertyDescriptor">The property descriptor.</param>
+/// <returns>
+///     The code <c>JsNoError</c> if the operation succeeded, a failure code otherwise.
+/// </returns>
+CHAKRA_API
+    JsObjectGetOwnPropertyDescriptor(
+        _In_ JsValueRef object,
+        _In_ JsValueRef key,
+        _Out_ JsValueRef *propertyDescriptor);
+
+/// <summary>
+///     Determines whether an object has a non-inherited property.
+/// </summary>
+/// <remarks>
+///     Requires an active script context.
+/// </remarks>
+/// <param name="object">The object that may contain the property.</param>
+/// <param name="key">The key (JavascriptString) to the property.</param>
+/// <param name="hasOwnProperty">Whether the object has the non-inherited property.</param>
+/// <returns>
+///     The code <c>JsNoError</c> if the operation succeeded, a failure code otherwise.
+/// </returns>
+CHAKRA_API
+    JsObjectHasOwnProperty(
+        _In_ JsValueRef object,
+        _In_ JsValueRef key,
+        _Out_ bool *hasOwnProperty);
 #endif // _CHAKRACOREBUILD
 #endif // _CHAKRACORE_H_

+ 410 - 89
lib/Jsrt/Jsrt.cpp

@@ -1383,102 +1383,252 @@ CHAKRA_API JsPreventExtension(_In_ JsValueRef object)
     });
 }
 
-CHAKRA_API JsGetProperty(_In_ JsValueRef object, _In_ JsPropertyIdRef propertyId, _Out_ JsValueRef *value)
+static JsErrorCode InternalGetPropertyRecord(Js::ScriptContext * scriptContext,
+    Js::RecyclableObject * key, _Out_ const Js::PropertyRecord ** propertyRecord)
 {
-    return ContextAPIWrapper<JSRT_MAYBE_TRUE>([&] (Js::ScriptContext *scriptContext, TTDRecorder& _actionEntryPopper) -> JsErrorCode {
-        PERFORM_JSRT_TTD_RECORD_ACTION(scriptContext, RecordJsRTGetProperty, (Js::PropertyRecord *)propertyId, object);
+    Assert(propertyRecord != nullptr);
+    *propertyRecord = nullptr;
+
+    if (key->GetTypeId() != Js::TypeIds_String)
+    {
+        return JsErrorInvalidArgument;
+    }
+
+    scriptContext->GetOrAddPropertyRecord(Js::JavascriptString::FromVar(key),
+        (Js::PropertyRecord const **)propertyRecord);
+    return JsNoError;
+}
+
+CHAKRA_API JsHasOwnPropertyCommon(Js::ScriptContext * scriptContext, _In_ JsValueRef object,
+    _In_ const Js::PropertyRecord * propertyRecord, _Out_ bool *hasOwnProperty,
+    TTDRecorder& _actionEntryPopper)
+{
+    PERFORM_JSRT_TTD_RECORD_ACTION(scriptContext, RecordJsRTHasOwnProperty, propertyRecord, object);
+
+    *hasOwnProperty = Js::JavascriptOperators::OP_HasOwnProperty(object,
+        propertyRecord->GetPropertyId(), scriptContext) != 0;
+
+    return JsNoError;
+}
+
+CHAKRA_API JsHasOwnProperty(_In_ JsValueRef object, _In_ JsPropertyIdRef propertyId,
+    _Out_ bool *hasOwnProperty)
+{
+    return ContextAPIWrapper<true>([&] (Js::ScriptContext *scriptContext,
+        TTDRecorder& _actionEntryPopper) -> JsErrorCode {
 
         VALIDATE_INCOMING_OBJECT(object, scriptContext);
         VALIDATE_INCOMING_PROPERTYID(propertyId);
-        PARAM_NOT_NULL(value);
-        *value = nullptr;
+        PARAM_NOT_NULL(hasOwnProperty);
+        *hasOwnProperty = false;
 
-        *value = Js::JavascriptOperators::OP_GetProperty((Js::Var)object, ((Js::PropertyRecord *)propertyId)->GetPropertyId(), scriptContext);
-        Assert(*value == nullptr || !Js::CrossSite::NeedMarshalVar(*value, scriptContext));
+        return JsHasOwnPropertyCommon(scriptContext, object,
+            (const Js::PropertyRecord *)propertyId, hasOwnProperty, _actionEntryPopper);
+    });
+}
 
-        PERFORM_JSRT_TTD_RECORD_ACTION_RESULT(scriptContext, value);
+#ifdef _CHAKRACOREBUILD
+CHAKRA_API JsObjectHasOwnProperty(_In_ JsValueRef object, _In_ JsValueRef propertyId, _Out_ bool *hasOwnProperty)
+{
+    return ContextAPIWrapper<true>([&] (Js::ScriptContext *scriptContext,
+        TTDRecorder& _actionEntryPopper) -> JsErrorCode {
 
-        return JsNoError;
+        VALIDATE_INCOMING_OBJECT(object, scriptContext);
+        VALIDATE_INCOMING_RECYCLABLE(propertyId, scriptContext);
+        PARAM_NOT_NULL(hasOwnProperty);
+        *hasOwnProperty = false;
+
+        const Js::PropertyRecord *propertyRecord = nullptr;
+        JsErrorCode errorValue = InternalGetPropertyRecord(scriptContext,
+            Js::RecyclableObject::FromVar(propertyId), &propertyRecord);
+
+        if (errorValue != JsNoError)
+        {
+            return errorValue;
+        }
+
+        return JsHasOwnPropertyCommon(scriptContext, object, propertyRecord, hasOwnProperty, _actionEntryPopper);
     });
 }
+#endif
 
-CHAKRA_API JsGetOwnPropertyDescriptor(_In_ JsValueRef object, _In_ JsPropertyIdRef propertyId, _Out_ JsValueRef *propertyDescriptor)
+static JsErrorCode JsGetPropertyCommon(Js::ScriptContext * scriptContext,
+    _In_ Js::RecyclableObject * object,
+    _In_ const Js::PropertyRecord * propertyRecord, _Out_ JsValueRef *value,
+    TTDRecorder& _actionEntryPopper)
 {
-    return ContextAPIWrapper<JSRT_MAYBE_TRUE>([&] (Js::ScriptContext *scriptContext, TTDRecorder& _actionEntryPopper) -> JsErrorCode {
-        PERFORM_JSRT_TTD_RECORD_ACTION(scriptContext, RecordJsRTGetOwnPropertyInfo, (Js::PropertyRecord *)propertyId, object);
+    AssertMsg(scriptContext->GetThreadContext()->IsScriptActive(), "Caller is expected to be under ContextAPIWrapper!");
+    PERFORM_JSRT_TTD_RECORD_ACTION(scriptContext, RecordJsRTGetProperty, propertyRecord, object);
+
+    *value = Js::JavascriptOperators::GetPropertyNoCache(object, propertyRecord->GetPropertyId(), scriptContext);
+    Assert(*value == nullptr || !Js::CrossSite::NeedMarshalVar(*value, scriptContext));
+
+    PERFORM_JSRT_TTD_RECORD_ACTION_RESULT(scriptContext, value);
+
+    return JsNoError;
+}
+
+CHAKRA_API JsGetProperty(_In_ JsValueRef object, _In_ JsPropertyIdRef propertyId, _Out_ JsValueRef *value)
+{
+    return ContextAPIWrapper<JSRT_MAYBE_TRUE>([&] (Js::ScriptContext *scriptContext,
+        TTDRecorder& _actionEntryPopper) -> JsErrorCode {
 
         VALIDATE_INCOMING_OBJECT(object, scriptContext);
         VALIDATE_INCOMING_PROPERTYID(propertyId);
-        PARAM_NOT_NULL(propertyDescriptor);
-        *propertyDescriptor = nullptr;
+        PARAM_NOT_NULL(value);
+        *value = nullptr;
 
-        Js::PropertyDescriptor propertyDescriptorValue;
-        if (Js::JavascriptOperators::GetOwnPropertyDescriptor(Js::RecyclableObject::FromVar(object), ((Js::PropertyRecord *)propertyId)->GetPropertyId(), scriptContext, &propertyDescriptorValue))
-        {
-            *propertyDescriptor = Js::JavascriptOperators::FromPropertyDescriptor(propertyDescriptorValue, scriptContext);
-        }
-        else
+        Js::RecyclableObject * instance = Js::RecyclableObject::FromVar(object);
+        return JsGetPropertyCommon(scriptContext, instance, (const Js::PropertyRecord *)propertyId,
+             value, _actionEntryPopper);
+    });
+}
+
+#ifdef _CHAKRACOREBUILD
+CHAKRA_API JsObjectGetProperty(_In_ JsValueRef object, _In_ JsValueRef propertyId, _Out_ JsValueRef *value)
+{
+    return ContextAPIWrapper<JSRT_MAYBE_TRUE>([&] (Js::ScriptContext *scriptContext,
+        TTDRecorder& _actionEntryPopper) -> JsErrorCode {
+
+        VALIDATE_INCOMING_OBJECT(object, scriptContext);
+        VALIDATE_INCOMING_RECYCLABLE(propertyId, scriptContext);
+        PARAM_NOT_NULL(value);
+        *value = nullptr;
+
+        const Js::PropertyRecord *propertyRecord = nullptr;
+        JsErrorCode errorValue = InternalGetPropertyRecord(scriptContext,
+            Js::RecyclableObject::FromVar(propertyId), &propertyRecord);
+
+        if (errorValue != JsNoError)
         {
-            *propertyDescriptor = scriptContext->GetLibrary()->GetUndefined();
+            return errorValue;
         }
-        Assert(*propertyDescriptor == nullptr || !Js::CrossSite::NeedMarshalVar(*propertyDescriptor, scriptContext));
 
-        PERFORM_JSRT_TTD_RECORD_ACTION_RESULT(scriptContext, propertyDescriptor);
+        Assert(propertyRecord != nullptr);
 
-        return JsNoError;
+        Js::RecyclableObject * instance = Js::RecyclableObject::FromVar(object);
+        return JsGetPropertyCommon(scriptContext, instance, propertyRecord, value, _actionEntryPopper);
     });
 }
+#endif
 
-CHAKRA_API JsGetOwnPropertyNames(_In_ JsValueRef object, _Out_ JsValueRef *propertyNames)
+static JsErrorCode JsGetOwnPropertyDescriptorCommon(Js::ScriptContext * scriptContext,
+    _In_ JsValueRef object, _In_ const Js::PropertyRecord * propertyRecord, _Out_ JsValueRef *propertyDescriptor,
+    TTDRecorder& _actionEntryPopper)
 {
-    return ContextAPIWrapper<JSRT_MAYBE_TRUE>([&] (Js::ScriptContext *scriptContext, TTDRecorder& _actionEntryPopper) -> JsErrorCode {
-        PERFORM_JSRT_TTD_RECORD_ACTION(scriptContext, RecordJsRTGetOwnPropertyNamesInfo, object);
+    AssertMsg(scriptContext->GetThreadContext()->IsScriptActive(), "Caller is expected to be under ContextAPIWrapper!");
+    PERFORM_JSRT_TTD_RECORD_ACTION(scriptContext, RecordJsRTGetOwnPropertyInfo, propertyRecord, object);
 
-        VALIDATE_INCOMING_OBJECT(object, scriptContext);
-        PARAM_NOT_NULL(propertyNames);
-        *propertyNames = nullptr;
+    Js::PropertyDescriptor propertyDescriptorValue;
+    if (Js::JavascriptOperators::GetOwnPropertyDescriptor(Js::RecyclableObject::FromVar(object),
+        propertyRecord->GetPropertyId(), scriptContext, &propertyDescriptorValue))
+    {
+        *propertyDescriptor = Js::JavascriptOperators::FromPropertyDescriptor(propertyDescriptorValue, scriptContext);
+    }
+    else
+    {
+        *propertyDescriptor = scriptContext->GetLibrary()->GetUndefined();
+    }
+    Assert(*propertyDescriptor == nullptr || !Js::CrossSite::NeedMarshalVar(*propertyDescriptor, scriptContext));
 
-        *propertyNames = Js::JavascriptOperators::GetOwnPropertyNames(object, scriptContext);
-        Assert(*propertyNames == nullptr || !Js::CrossSite::NeedMarshalVar(*propertyNames, scriptContext));
+    PERFORM_JSRT_TTD_RECORD_ACTION_RESULT(scriptContext, propertyDescriptor);
 
-        PERFORM_JSRT_TTD_RECORD_ACTION_RESULT(scriptContext, propertyNames);
+    return JsNoError;
+}
 
-        return JsNoError;
+CHAKRA_API JsGetOwnPropertyDescriptor(_In_ JsValueRef object, _In_ JsPropertyIdRef propertyId, _Out_ JsValueRef *propertyDescriptor)
+{
+    return ContextAPIWrapper<JSRT_MAYBE_TRUE>([&] (Js::ScriptContext *scriptContext, TTDRecorder& _actionEntryPopper) -> JsErrorCode {
+        VALIDATE_INCOMING_OBJECT(object, scriptContext);
+        VALIDATE_INCOMING_PROPERTYID(propertyId);
+        PARAM_NOT_NULL(propertyDescriptor);
+        *propertyDescriptor = nullptr;
+
+        return JsGetOwnPropertyDescriptorCommon(scriptContext, object, (const Js::PropertyRecord *)propertyId,
+            propertyDescriptor, _actionEntryPopper);
     });
 }
 
-CHAKRA_API JsGetOwnPropertySymbols(_In_ JsValueRef object, _Out_ JsValueRef *propertySymbols)
+#ifdef _CHAKRACOREBUILD
+CHAKRA_API JsObjectGetOwnPropertyDescriptor(_In_ JsValueRef object, _In_ JsValueRef propertyId, _Out_ JsValueRef *propertyDescriptor)
 {
-    return ContextAPIWrapper<JSRT_MAYBE_TRUE>([&](Js::ScriptContext *scriptContext, TTDRecorder& _actionEntryPopper) -> JsErrorCode {
-        PERFORM_JSRT_TTD_RECORD_ACTION(scriptContext, RecordJsRTGetOwnPropertySymbolsInfo, object);
+    return ContextAPIWrapper<JSRT_MAYBE_TRUE>([&] (Js::ScriptContext *scriptContext,
+        TTDRecorder& _actionEntryPopper) -> JsErrorCode {
 
         VALIDATE_INCOMING_OBJECT(object, scriptContext);
-        PARAM_NOT_NULL(propertySymbols);
+        VALIDATE_INCOMING_RECYCLABLE(propertyId, scriptContext);
+        PARAM_NOT_NULL(propertyDescriptor);
+        *propertyDescriptor = nullptr;
 
-        *propertySymbols = Js::JavascriptOperators::GetOwnPropertySymbols(object, scriptContext);
-        Assert(*propertySymbols == nullptr || !Js::CrossSite::NeedMarshalVar(*propertySymbols, scriptContext));
+        const Js::PropertyRecord *propertyRecord = nullptr;
+        JsErrorCode errorValue = InternalGetPropertyRecord(scriptContext,
+            Js::RecyclableObject::FromVar(propertyId), &propertyRecord);
 
-        PERFORM_JSRT_TTD_RECORD_ACTION_RESULT(scriptContext, propertySymbols);
+        if (errorValue != JsNoError)
+        {
+            return errorValue;
+        }
 
-        return JsNoError;
+        Assert(propertyRecord != nullptr);
+
+        return JsGetOwnPropertyDescriptorCommon(scriptContext, object, propertyRecord, propertyDescriptor, _actionEntryPopper);
     });
 }
+#endif
+
+static JsErrorCode JsSetPropertyCommon(Js::ScriptContext * scriptContext, _In_ JsValueRef object,
+    _In_ const Js::PropertyRecord * propertyRecord, _In_ JsValueRef value, _In_ bool useStrictRules,
+    TTDRecorder& _actionEntryPopper)
+{
+    AssertMsg(scriptContext->GetThreadContext()->IsScriptActive(), "Caller is expected to be under ContextAPIWrapper!");
+    PERFORM_JSRT_TTD_RECORD_ACTION(scriptContext, RecordJsRTSetProperty, object,
+        propertyRecord, value, useStrictRules);
+
+    Js::JavascriptOperators::OP_SetProperty(object, propertyRecord->GetPropertyId(),
+        value, scriptContext, nullptr, useStrictRules ? Js::PropertyOperation_StrictMode : Js::PropertyOperation_None);
+
+    return JsNoError;
+}
 
 CHAKRA_API JsSetProperty(_In_ JsValueRef object, _In_ JsPropertyIdRef propertyId, _In_ JsValueRef value, _In_ bool useStrictRules)
 {
-    return ContextAPIWrapper<JSRT_MAYBE_TRUE>([&] (Js::ScriptContext *scriptContext, TTDRecorder& _actionEntryPopper) -> JsErrorCode {
-        PERFORM_JSRT_TTD_RECORD_ACTION(scriptContext, RecordJsRTSetProperty, object, (Js::PropertyRecord *)propertyId, value, useStrictRules);
+    return ContextAPIWrapper<JSRT_MAYBE_TRUE>([&] (Js::ScriptContext *scriptContext,
+        TTDRecorder& _actionEntryPopper) -> JsErrorCode {
 
         VALIDATE_INCOMING_OBJECT(object, scriptContext);
         VALIDATE_INCOMING_PROPERTYID(propertyId);
         VALIDATE_INCOMING_REFERENCE(value, scriptContext);
 
-        Js::JavascriptOperators::OP_SetProperty(object, ((Js::PropertyRecord *)propertyId)->GetPropertyId(), value, scriptContext,
-            nullptr, useStrictRules ? Js::PropertyOperation_StrictMode : Js::PropertyOperation_None);
+        return JsSetPropertyCommon(scriptContext, object, (const Js::PropertyRecord *)propertyId,
+            value, useStrictRules, _actionEntryPopper);
+    });
+}
 
-        return JsNoError;
+#ifdef _CHAKRACOREBUILD
+CHAKRA_API JsObjectSetProperty(_In_ JsValueRef object, _In_ JsValueRef propertyId, _In_ JsValueRef value, _In_ bool useStrictRules)
+{
+    return ContextAPIWrapper<JSRT_MAYBE_TRUE>([&] (Js::ScriptContext *scriptContext,
+        TTDRecorder& _actionEntryPopper) -> JsErrorCode {
+
+        VALIDATE_INCOMING_OBJECT(object, scriptContext);
+        VALIDATE_INCOMING_RECYCLABLE(propertyId, scriptContext);
+        VALIDATE_INCOMING_REFERENCE(value, scriptContext);
+
+        const Js::PropertyRecord *propertyRecord = nullptr;
+        JsErrorCode errorValue = InternalGetPropertyRecord(scriptContext,
+            Js::RecyclableObject::FromVar(propertyId), &propertyRecord);
+
+        if (errorValue != JsNoError)
+        {
+            return errorValue;
+        }
+
+        Assert(propertyRecord != nullptr);
+
+        return JsSetPropertyCommon(scriptContext, object, propertyRecord, value, useStrictRules, _actionEntryPopper);
     });
 }
+#endif
 
 CHAKRA_API JsHasProperty(_In_ JsValueRef object, _In_ JsPropertyIdRef propertyId, _Out_ bool *hasProperty)
 {
@@ -1493,7 +1643,8 @@ CHAKRA_API JsHasProperty(_In_ JsValueRef object, _In_ JsPropertyIdRef propertyId
         PARAM_NOT_NULL(hasProperty);
         *hasProperty = false;
 
-        *hasProperty = Js::JavascriptOperators::OP_HasProperty(object, ((Js::PropertyRecord *)propertyId)->GetPropertyId(), scriptContext) != 0;
+        Js::RecyclableObject * instance = Js::RecyclableObject::FromVar(object);
+        *hasProperty = Js::JavascriptOperators::HasProperty(instance, ((Js::PropertyRecord *)propertyId)->GetPropertyId()) != 0;
 
         return JsNoError;
     };
@@ -1516,30 +1667,144 @@ CHAKRA_API JsHasProperty(_In_ JsValueRef object, _In_ JsPropertyIdRef propertyId
     }
 }
 
-CHAKRA_API JsDeleteProperty(_In_ JsValueRef object, _In_ JsPropertyIdRef propertyId, _In_ bool useStrictRules, _Out_ JsValueRef *result)
+#ifdef _CHAKRACOREBUILD
+CHAKRA_API JsObjectHasProperty(_In_ JsValueRef object, _In_ JsValueRef propertyId, _Out_ bool *hasProperty)
 {
-    return ContextAPIWrapper<JSRT_MAYBE_TRUE>([&] (Js::ScriptContext *scriptContext, TTDRecorder& _actionEntryPopper) -> JsErrorCode {
-        PERFORM_JSRT_TTD_RECORD_ACTION(scriptContext, RecordJsRTDeleteProperty, object, (Js::PropertyRecord *)propertyId, useStrictRules);
+    VALIDATE_JSREF(object);
+    if (!Js::JavascriptOperators::IsObject(object)) return JsErrorArgumentNotObject;
+
+    auto internalHasProperty = [&] (Js::ScriptContext *scriptContext, TTDRecorder& _actionEntryPopper) -> JsErrorCode {
+        VALIDATE_INCOMING_OBJECT(object, scriptContext);
+        VALIDATE_INCOMING_RECYCLABLE(propertyId, scriptContext);
+        PARAM_NOT_NULL(hasProperty);
+        *hasProperty = false;
+
+        const Js::PropertyRecord *propertyRecord = nullptr;
+        JsErrorCode errorValue = InternalGetPropertyRecord(scriptContext,
+            Js::RecyclableObject::FromVar(propertyId), &propertyRecord);
+
+        if (errorValue != JsNoError)
+        {
+            return errorValue;
+        }
+
+        PERFORM_JSRT_TTD_RECORD_ACTION(scriptContext, RecordJsRTHasProperty, propertyRecord, object);
+
+        Js::RecyclableObject * instance = Js::RecyclableObject::FromVar(object);
+        *hasProperty = Js::JavascriptOperators::HasProperty(instance, propertyRecord->GetPropertyId()) != 0;
+
+        return JsNoError;
+    };
+
+    Js::RecyclableObject* robject = Js::RecyclableObject::FromVar(object);
+    Js::TypeId typeId = Js::JavascriptOperators::GetTypeId(robject);
+    while (typeId != Js::TypeIds_Null && typeId != Js::TypeIds_Proxy)
+    {
+        robject = robject->GetPrototype();
+        typeId = Js::JavascriptOperators::GetTypeId(robject);
+    }
+
+    if (typeId == Js::TypeIds_Proxy)
+    {
+        return ContextAPIWrapper<JSRT_MAYBE_TRUE>(internalHasProperty);
+    }
+    else
+    {
+        return ContextAPINoScriptWrapper(internalHasProperty);
+    }
+}
+#endif
+
+static JsErrorCode JsDeletePropertyCommon(Js::ScriptContext * scriptContext, _In_ JsValueRef object,
+    _In_ const Js::PropertyRecord * propertyRecord, _In_ bool useStrictRules, _Out_ JsValueRef *result,
+    TTDRecorder& _actionEntryPopper)
+{
+    AssertMsg(scriptContext->GetThreadContext()->IsScriptActive(), "Caller is expected to be under ContextAPIWrapper!");
+    PERFORM_JSRT_TTD_RECORD_ACTION(scriptContext, RecordJsRTDeleteProperty, object,
+        propertyRecord, useStrictRules);
+
+    *result = Js::JavascriptOperators::OP_DeleteProperty((Js::Var)object,
+        propertyRecord->GetPropertyId(),
+        scriptContext, useStrictRules ? Js::PropertyOperation_StrictMode : Js::PropertyOperation_None);
+
+    Assert(*result == nullptr || !Js::CrossSite::NeedMarshalVar(*result, scriptContext));
+
+    PERFORM_JSRT_TTD_RECORD_ACTION_RESULT(scriptContext, result);
+
+    return JsNoError;
+}
+
+CHAKRA_API JsDeleteProperty(_In_ JsValueRef object, _In_ JsPropertyIdRef propertyId,
+    _In_ bool useStrictRules, _Out_ JsValueRef *result)
+{
+    return ContextAPIWrapper<JSRT_MAYBE_TRUE>([&] (Js::ScriptContext *scriptContext,
+        TTDRecorder& _actionEntryPopper) -> JsErrorCode {
 
         VALIDATE_INCOMING_OBJECT(object, scriptContext);
         VALIDATE_INCOMING_PROPERTYID(propertyId);
         PARAM_NOT_NULL(result);
         *result = nullptr;
 
-        *result = Js::JavascriptOperators::OP_DeleteProperty((Js::Var)object, ((Js::PropertyRecord *)propertyId)->GetPropertyId(),
-            scriptContext, useStrictRules ? Js::PropertyOperation_StrictMode : Js::PropertyOperation_None);
-        Assert(*result == nullptr || !Js::CrossSite::NeedMarshalVar(*result, scriptContext));
+        return JsDeletePropertyCommon(scriptContext, object, (const Js::PropertyRecord *)propertyId,
+            useStrictRules, result, _actionEntryPopper);
+    });
+}
 
-        PERFORM_JSRT_TTD_RECORD_ACTION_RESULT(scriptContext, result);
+#ifdef _CHAKRACOREBUILD
+CHAKRA_API JsObjectDeleteProperty(_In_ JsValueRef object, _In_ JsValueRef propertyId,
+    _In_ bool useStrictRules, _Out_ JsValueRef *result)
+{
+    return ContextAPIWrapper<JSRT_MAYBE_TRUE>([&] (Js::ScriptContext *scriptContext,
+        TTDRecorder& _actionEntryPopper) -> JsErrorCode {
 
-        return JsNoError;
+        VALIDATE_INCOMING_OBJECT(object, scriptContext);
+        VALIDATE_INCOMING_RECYCLABLE(propertyId, scriptContext);
+        PARAM_NOT_NULL(result);
+        *result = nullptr;
+
+        const Js::PropertyRecord *propertyRecord = nullptr;
+        JsErrorCode errorValue = InternalGetPropertyRecord(scriptContext,
+            Js::RecyclableObject::FromVar(propertyId), &propertyRecord);
+
+        if (errorValue != JsNoError)
+        {
+            return errorValue;
+        }
+
+        Assert(propertyRecord != nullptr);
+
+        return JsDeletePropertyCommon(scriptContext, object, propertyRecord,
+            useStrictRules, result, _actionEntryPopper);
     });
 }
+#endif
 
-CHAKRA_API JsDefineProperty(_In_ JsValueRef object, _In_ JsPropertyIdRef propertyId, _In_ JsValueRef propertyDescriptor, _Out_ bool *result)
+static JsErrorCode JsDefinePropertyCommon(Js::ScriptContext * scriptContext, _In_ JsValueRef object,
+    _In_ const Js::PropertyRecord *propertyRecord, _In_ JsValueRef propertyDescriptor,
+    _Out_ bool *result, TTDRecorder& _actionEntryPopper)
 {
-    return ContextAPIWrapper<JSRT_MAYBE_TRUE>([&] (Js::ScriptContext *scriptContext, TTDRecorder& _actionEntryPopper) -> JsErrorCode {
-        PERFORM_JSRT_TTD_RECORD_ACTION(scriptContext, RecordJsRTDefineProperty, object, (Js::PropertyRecord *)propertyId, propertyDescriptor);
+    AssertMsg(scriptContext->GetThreadContext()->IsScriptActive(), "Caller is expected to be under ContextAPIWrapper!");
+    PERFORM_JSRT_TTD_RECORD_ACTION(scriptContext, RecordJsRTDefineProperty, object,
+        propertyRecord, propertyDescriptor);
+
+    Js::PropertyDescriptor propertyDescriptorValue;
+    if (!Js::JavascriptOperators::ToPropertyDescriptor(propertyDescriptor, &propertyDescriptorValue, scriptContext))
+    {
+        return JsErrorInvalidArgument;
+    }
+
+    *result = Js::JavascriptOperators::DefineOwnPropertyDescriptor(
+        Js::RecyclableObject::FromVar(object), propertyRecord->GetPropertyId(),
+        propertyDescriptorValue, true, scriptContext) != 0;
+
+    return JsNoError;
+}
+
+CHAKRA_API JsDefineProperty(_In_ JsValueRef object, _In_ JsPropertyIdRef propertyId,
+    _In_ JsValueRef propertyDescriptor, _Out_ bool *result)
+{
+    return ContextAPIWrapper<JSRT_MAYBE_TRUE>([&] (Js::ScriptContext *scriptContext,
+        TTDRecorder& _actionEntryPopper) -> JsErrorCode {
 
         VALIDATE_INCOMING_OBJECT(object, scriptContext);
         VALIDATE_INCOMING_PROPERTYID(propertyId);
@@ -1547,15 +1812,68 @@ CHAKRA_API JsDefineProperty(_In_ JsValueRef object, _In_ JsPropertyIdRef propert
         PARAM_NOT_NULL(result);
         *result = false;
 
-        Js::PropertyDescriptor propertyDescriptorValue;
-        if (!Js::JavascriptOperators::ToPropertyDescriptor(propertyDescriptor, &propertyDescriptorValue, scriptContext))
+        return JsDefinePropertyCommon(scriptContext, object, (const Js::PropertyRecord *)propertyId,
+            propertyDescriptor, result, _actionEntryPopper);
+    });
+}
+
+#ifdef _CHAKRACOREBUILD
+CHAKRA_API JsObjectDefineProperty(_In_ JsValueRef object, _In_ JsValueRef propertyId,
+    _In_ JsValueRef propertyDescriptor, _Out_ bool *result)
+{
+    return ContextAPIWrapper<JSRT_MAYBE_TRUE>([&] (Js::ScriptContext *scriptContext,
+        TTDRecorder& _actionEntryPopper) -> JsErrorCode {
+
+        VALIDATE_INCOMING_OBJECT(object, scriptContext);
+        VALIDATE_INCOMING_RECYCLABLE(propertyId, scriptContext);
+        VALIDATE_INCOMING_OBJECT(propertyDescriptor, scriptContext);
+        PARAM_NOT_NULL(result);
+        *result = false;
+
+        const Js::PropertyRecord *propertyRecord = nullptr;
+        JsErrorCode errorValue = InternalGetPropertyRecord(scriptContext,
+            Js::RecyclableObject::FromVar(propertyId), &propertyRecord);
+
+        if (errorValue != JsNoError)
         {
-            return JsErrorInvalidArgument;
+            return errorValue;
         }
 
-        *result = Js::JavascriptOperators::DefineOwnPropertyDescriptor(
-            Js::RecyclableObject::FromVar(object), ((Js::PropertyRecord *)propertyId)->GetPropertyId(), propertyDescriptorValue,
-            true, scriptContext) != 0;
+        return JsDefinePropertyCommon(scriptContext, object, propertyRecord, propertyDescriptor, result, _actionEntryPopper);
+    });
+}
+#endif
+
+CHAKRA_API JsGetOwnPropertyNames(_In_ JsValueRef object, _Out_ JsValueRef *propertyNames)
+{
+    return ContextAPIWrapper<JSRT_MAYBE_TRUE>([&] (Js::ScriptContext *scriptContext, TTDRecorder& _actionEntryPopper) -> JsErrorCode {
+        PERFORM_JSRT_TTD_RECORD_ACTION(scriptContext, RecordJsRTGetOwnPropertyNamesInfo, object);
+
+        VALIDATE_INCOMING_OBJECT(object, scriptContext);
+        PARAM_NOT_NULL(propertyNames);
+        *propertyNames = nullptr;
+
+        *propertyNames = Js::JavascriptOperators::GetOwnPropertyNames(object, scriptContext);
+        Assert(*propertyNames == nullptr || !Js::CrossSite::NeedMarshalVar(*propertyNames, scriptContext));
+
+        PERFORM_JSRT_TTD_RECORD_ACTION_RESULT(scriptContext, propertyNames);
+
+        return JsNoError;
+    });
+}
+
+CHAKRA_API JsGetOwnPropertySymbols(_In_ JsValueRef object, _Out_ JsValueRef *propertySymbols)
+{
+    return ContextAPIWrapper<JSRT_MAYBE_TRUE>([&](Js::ScriptContext *scriptContext, TTDRecorder& _actionEntryPopper) -> JsErrorCode {
+        PERFORM_JSRT_TTD_RECORD_ACTION(scriptContext, RecordJsRTGetOwnPropertySymbolsInfo, object);
+
+        VALIDATE_INCOMING_OBJECT(object, scriptContext);
+        PARAM_NOT_NULL(propertySymbols);
+
+        *propertySymbols = Js::JavascriptOperators::GetOwnPropertySymbols(object, scriptContext);
+        Assert(*propertySymbols == nullptr || !Js::CrossSite::NeedMarshalVar(*propertySymbols, scriptContext));
+
+        PERFORM_JSRT_TTD_RECORD_ACTION_RESULT(scriptContext, propertySymbols);
 
         return JsNoError;
     });
@@ -4225,14 +4543,21 @@ CHAKRA_API JsCreateString(
     _Out_ JsValueRef *value)
 {
     PARAM_NOT_NULL(content);
+    PARAM_NOT_NULL(value);
 
-    utf8::NarrowToWide wstr(content, length);
-    if (!wstr)
-    {
-        return JsErrorOutOfMemory;
-    }
+    return ContextAPINoScriptWrapper([&](Js::ScriptContext *scriptContext, TTDRecorder& _actionEntryPopper) -> JsErrorCode {
+
+        Js::JavascriptString *stringValue = Js::LiteralStringWithPropertyStringPtr::
+            NewFromCString(content, (CharCount)length, scriptContext->GetLibrary());
+
+        PERFORM_JSRT_TTD_RECORD_ACTION(scriptContext, RecordJsRTCreateString, stringValue->GetSz(), stringValue->GetLength());
+
+        *value = stringValue;
 
-    return JsPointerToString(wstr, wstr.Length(), value);
+        PERFORM_JSRT_TTD_RECORD_ACTION_RESULT(scriptContext, value);
+
+        return JsNoError;
+    });
 }
 
 CHAKRA_API JsCreateStringUtf16(
@@ -4241,9 +4566,21 @@ CHAKRA_API JsCreateStringUtf16(
     _Out_ JsValueRef *value)
 {
     PARAM_NOT_NULL(content);
+    PARAM_NOT_NULL(value);
+
+    return ContextAPINoScriptWrapper([&](Js::ScriptContext *scriptContext, TTDRecorder& _actionEntryPopper) -> JsErrorCode {
+
+        Js::JavascriptString *stringValue = Js::LiteralStringWithPropertyStringPtr::
+            NewFromWideString(content, (CharCount)length, scriptContext->GetLibrary());
+
+        PERFORM_JSRT_TTD_RECORD_ACTION(scriptContext, RecordJsRTCreateString, stringValue->GetSz(), stringValue->GetLength());
 
-    return JsPointerToString(
-        reinterpret_cast<const char16*>(content), length, value);
+        *value = stringValue;
+
+        PERFORM_JSRT_TTD_RECORD_ACTION_RESULT(scriptContext, value);
+
+        return JsNoError;
+    });
 }
 
 
@@ -4798,22 +5135,6 @@ CHAKRA_API JsGetAndClearExceptionWithMetadata(_Out_ JsValueRef *metadata)
     });
 }
 
-CHAKRA_API JsHasOwnProperty(_In_ JsValueRef object, _In_ JsPropertyIdRef propertyId, _Out_ bool *hasOwnProperty)
-{
-    return ContextAPIWrapper<true>([&] (Js::ScriptContext *scriptContext, TTDRecorder& _actionEntryPopper) -> JsErrorCode {
-        PERFORM_JSRT_TTD_RECORD_ACTION(scriptContext, RecordJsRTHasOwnProperty, (Js::PropertyRecord *)propertyId, object);
-
-        VALIDATE_INCOMING_OBJECT(object, scriptContext);
-        VALIDATE_INCOMING_PROPERTYID(propertyId);
-        PARAM_NOT_NULL(hasOwnProperty);
-        *hasOwnProperty = false;
-
-        *hasOwnProperty = Js::JavascriptOperators::OP_HasOwnProperty(object, ((Js::PropertyRecord *)propertyId)->GetPropertyId(), scriptContext) != 0;
-
-        return JsNoError;
-    });
-}
-
 CHAKRA_API JsCopyStringOneByte(
     _In_ JsValueRef value,
     _In_ int start,

+ 10 - 0
lib/Jsrt/JsrtInternal.h

@@ -84,6 +84,16 @@ typedef struct {} TTDRecorder;
             MARSHAL_OBJECT(p, scriptContext)   \
         }
 
+#define VALIDATE_INCOMING_RECYCLABLE(p, scriptContext) \
+{ \
+    VALIDATE_JSREF(p); \
+    if (!Js::RecyclableObject::Is(p)) \
+    { \
+        return JsErrorInvalidArgument; \
+    } \
+    MARSHAL_OBJECT(p, scriptContext)   \
+}
+
 #define VALIDATE_INCOMING_OBJECT_OR_NULL(p, scriptContext) \
         { \
             VALIDATE_JSREF(p); \

+ 3 - 10
lib/Runtime/Base/ScriptContext.cpp

@@ -859,14 +859,7 @@ namespace Js
 
     void ScriptContext::GetOrAddPropertyRecord(_In_ Js::JavascriptString * propertyString, _Out_ PropertyRecord const** propertyRecord)
     {
-        if (VirtualTableInfo<Js::PropertyString>::HasVirtualTable(propertyString) && propertyString->GetScriptContext() == this)
-        {
-            *propertyRecord = ((Js::PropertyString*)propertyString)->GetPropertyRecord();
-        }
-        else
-        {
-            GetOrAddPropertyRecord(propertyString->GetString(), propertyString->GetLength(), propertyRecord);
-        }
+        *propertyRecord = propertyString->GetPropertyRecord();
     }
 
     void ScriptContext::GetOrAddPropertyRecord(JsUtil::CharacterBuffer<WCHAR> const& propertyName, PropertyRecord const ** propertyRecord)
@@ -2115,9 +2108,9 @@ namespace Js
 
     void ScriptContext::OnScriptStart(bool isRoot, bool isScript)
     {
-        const bool isForcedEnter = 
+        const bool isForcedEnter =
 #ifdef ENABLE_SCRIPT_DEBUGGING
-            this->GetDebugContext() != nullptr ? this->GetDebugContext()->GetProbeContainer()->isForcedToEnterScriptStart : 
+            this->GetDebugContext() != nullptr ? this->GetDebugContext()->GetProbeContainer()->isForcedToEnterScriptStart :
 #endif
             false;
         if (this->scriptStartEventHandler != nullptr && ((isRoot && threadContext->GetCallRootLevel() == 1) || isForcedEnter))

+ 8 - 1
lib/Runtime/Base/ThreadContext.cpp

@@ -883,6 +883,13 @@ ThreadContext::GetPropertyNameImpl(Js::PropertyId propertyId)
 void
 ThreadContext::FindPropertyRecord(Js::JavascriptString *pstName, Js::PropertyRecord const ** propertyRecord)
 {
+    const Js::PropertyRecord * propRecord = pstName->GetPropertyRecord(true);
+    if (propRecord != nullptr)
+    {
+        *propertyRecord = propRecord;
+        return;
+    }
+
     LPCWSTR psz = pstName->GetSz();
     FindPropertyRecord(psz, pstName->GetLength(), propertyRecord);
 }
@@ -2963,7 +2970,7 @@ ThreadContext::InExpirableCollectMode()
     // and when debugger is attaching, it might have set the function to deferredParse.
     return (expirableObjectList != nullptr &&
             numExpirableObjects > 0 &&
-            expirableCollectModeGcCount >= 0 
+            expirableCollectModeGcCount >= 0
 #ifdef ENABLE_SCRIPT_DEBUGGING
         &&
             (this->GetDebugManager() != nullptr &&

+ 3 - 13
lib/Runtime/Debug/DiagObjectModel.cpp

@@ -2471,19 +2471,9 @@ namespace Js
                                 {
                                     if (propertyId == Constants::NoProperty)
                                     {
-                                        PropertyString * propertyString = PropertyString::TryFromVar(obj);
-                                        if (propertyString != nullptr)
-                                        {
-                                            // If we have a property string, it is assumed that the propertyId is being
-                                            // kept alive with the object
-                                            propertyId = propertyString->GetPropertyRecord()->GetPropertyId();
-                                        }
-                                        else
-                                        {
-                                            const PropertyRecord* propertyRecord;
-                                            objectContext->GetOrAddPropertyRecord(obj, &propertyRecord);
-                                            propertyId = propertyRecord->GetPropertyId();
-                                        }
+                                        const PropertyRecord* propertyRecord;
+                                        objectContext->GetOrAddPropertyRecord(obj, &propertyRecord);
+                                        propertyId = propertyRecord->GetPropertyId();
                                     }
                                     // MoveAndGetNext shouldn't return an internal property id
                                     Assert(!Js::IsInternalPropertyId(propertyId));

+ 1 - 33
lib/Runtime/Language/JavascriptConversion.cpp

@@ -293,39 +293,7 @@ CommonNumber:
         {
             // For all other types, convert the key into a string and use that as the property name
             JavascriptString * propName = JavascriptConversion::ToString(key, scriptContext);
-
-            // Check if we have one of the JavascriptString types which allow us to directly read the PropertyRecord
-            propertyString = PropertyString::TryFromVar(propName);
-            if (propertyString != nullptr)
-            {
-                // If we have a PropertyString, we can simply read the PropertyRecord off of it
-                *propertyRecord = propertyString->GetPropertyRecord();
-            }
-            else
-            {
-                LiteralStringWithPropertyStringPtr * strWithPtr = LiteralStringWithPropertyStringPtr::TryFromVar(propName);
-                if (strWithPtr != nullptr)
-                {
-                    propertyString = strWithPtr->GetPropertyString();
-                    if (propertyString != nullptr)
-                    {
-                        // If the PropertyString field is set, we can again simply read the propertyRecord
-                        *propertyRecord = propertyString->GetPropertyRecord();
-                    }
-                    else
-                    {
-                        // Otherwise, we need to do a lookup for the PropertyRecord
-                        scriptContext->GetOrAddPropertyRecord(propName, propertyRecord);
-                        // While we have the PropertyRecord available, let's find/create a PropertyString so future usage can be optimized
-                        strWithPtr->SetPropertyString(scriptContext->GetPropertyString((*propertyRecord)->GetPropertyId()));
-                    }
-                }
-                else
-                {
-                    // If we don't have any special JavascriptString, we need to do a lookup for the PropertyRecord
-                    scriptContext->GetOrAddPropertyRecord(propName->GetString(), propName->GetLength(), propertyRecord);
-                }
-            }
+            *propertyRecord = propName->GetPropertyRecord();
         }
 
         if (propString)

+ 27 - 42
lib/Runtime/Language/JavascriptOperators.cpp

@@ -91,19 +91,20 @@ namespace Js
             }
             else
             {
-                char16 buffer[20];
-                ::_itow_s(indexInt, buffer, sizeof(buffer) / sizeof(char16), 10);
-                charcount_t length = JavascriptString::GetBufferLength(buffer);
+                char16 stringBuffer[22];
+
+                int pos = TaggedInt::ToBuffer(indexInt, stringBuffer, _countof(stringBuffer));
+                charcount_t length = (_countof(stringBuffer) - 1) - pos;
                 if (createIfNotFound || preferJavascriptStringOverPropertyRecord)
                 {
                     // When preferring JavascriptString objects, just return a PropertyRecord instead
                     // of creating temporary JavascriptString objects for every negative integer that
                     // comes through here.
-                    scriptContext->GetOrAddPropertyRecord(buffer, length, propertyRecord);
+                    scriptContext->GetOrAddPropertyRecord(stringBuffer + pos, length, propertyRecord);
                 }
                 else
                 {
-                    scriptContext->FindPropertyRecord(buffer, length, propertyRecord);
+                    scriptContext->FindPropertyRecord(stringBuffer + pos, length, propertyRecord);
                 }
                 return IndexType_PropertyId;
             }
@@ -117,27 +118,24 @@ namespace Js
                 // We already know what the PropertyRecord is since it is stored in the JavascriptSymbol itself so just return it.
 
                 *propertyRecord = symbol->GetValue();
-
                 return IndexType_PropertyId;
             }
             else
             {
                 JavascriptString* indexStr = JavascriptConversion::ToString(indexVar, scriptContext);
-                char16 const * propertyName = indexStr->GetString();
-                charcount_t const propertyLength = indexStr->GetLength();
+                *propertyRecord = indexStr->GetPropertyRecord();
 
-                if (!createIfNotFound && preferJavascriptStringOverPropertyRecord)
+                if ((*propertyRecord)->IsNumeric())
                 {
-                    if (JavascriptOperators::TryConvertToUInt32(propertyName, propertyLength, index) &&
-                        (*index != JavascriptArray::InvalidIndex))
-                    {
-                        return IndexType_Number;
-                    }
+                    *index = (*propertyRecord)->GetNumericValue();
+                    return IndexType_Number;
+                }
 
+                if (preferJavascriptStringOverPropertyRecord)
+                {
                     *propertyNameString = indexStr;
-                    return IndexType_JavascriptString;
                 }
-                return GetIndexTypeFromString(propertyName, propertyLength, scriptContext, index, propertyRecord, createIfNotFound);
+                return IndexType_PropertyId;
             }
         }
     }
@@ -3726,17 +3724,17 @@ CommonNumber:
             temp = JavascriptOperators::TryFromVar<JavascriptString>(index);
             if (temp && RecyclableObject::Is(instance)) // fastpath for PropertyStrings
             {
-                Assert(temp->GetScriptContext() == scriptContext);
-
-                PropertyString * propertyString = PropertyString::TryFromVar(temp);
-                if (propertyString == nullptr)
+                PropertyString * propertyString = nullptr;
+                if (VirtualTableInfo<Js::PropertyString>::HasVirtualTable(temp))
                 {
-                    LiteralStringWithPropertyStringPtr * strWithPtr = LiteralStringWithPropertyStringPtr::TryFromVar(temp);
-                    if (strWithPtr)
-                    {
-                        propertyString = strWithPtr->GetPropertyString();
-                    }
+                    propertyString = (PropertyString*)temp;
                 }
+                else if (VirtualTableInfo<Js::LiteralStringWithPropertyStringPtr>::HasVirtualTable(temp))
+                {
+                    LiteralStringWithPropertyStringPtr * propStr = (LiteralStringWithPropertyStringPtr *)temp;
+                    propertyString = propStr->GetOrAddPropertyString();
+                }
+
                 if (propertyString != nullptr)
                 {
                     RecyclableObject* object = nullptr;
@@ -4378,13 +4376,8 @@ CommonNumber:
             LiteralStringWithPropertyStringPtr * strWithPtr = LiteralStringWithPropertyStringPtr::TryFromVar(index);
             if (strWithPtr != nullptr)
             {
-                propertyString = strWithPtr->GetPropertyString();
-                if (propertyString == nullptr)
-                {
-                    scriptContext->GetOrAddPropertyRecord(strWithPtr, &propertyRecord);
-                    propertyString = scriptContext->GetPropertyString(propertyRecord->GetPropertyId());
-                    strWithPtr->SetPropertyString(propertyString);
-                }
+                propertyString = strWithPtr->GetOrAddPropertyString();
+                propertyRecord = propertyString->GetPropertyRecord();
             }
         }
 
@@ -10478,15 +10471,8 @@ CommonNumber:
 
     BOOL JavascriptOperators::CheckPrototypesForAccessorOrNonWritableProperty(RecyclableObject* instance, JavascriptString* propertyNameString, Var* setterValue, DescriptorFlags* flags, PropertyValueInfo* info, ScriptContext* scriptContext)
     {
-        JsUtil::CharacterBuffer<WCHAR> propertyName(propertyNameString->GetString(), propertyNameString->GetLength());
-        if (Js::BuiltInPropertyRecords::__proto__.Equals(propertyName))
-        {
-            return CheckPrototypesForAccessorOrNonWritablePropertyCore<JavascriptString*, false, false>(instance, propertyNameString, setterValue, flags, info, scriptContext);
-        }
-        else
-        {
-            return CheckPrototypesForAccessorOrNonWritablePropertyCore<JavascriptString*, true, false>(instance, propertyNameString, setterValue, flags, info, scriptContext);
-        }
+        PropertyId propertyId = propertyNameString->GetPropertyRecord()->GetPropertyId();
+        return CheckPrototypesForAccessorOrNonWritableProperty(instance, propertyId, setterValue, flags, info, scriptContext);
     }
 
     BOOL JavascriptOperators::SetProperty(Var instance, RecyclableObject* object, PropertyId propertyId, Var newValue, ScriptContext* requestContext, PropertyOperationFlags propertyOperationFlags)
@@ -10538,4 +10524,3 @@ CommonNumber:
         return constructor;
     }
 } // namespace Js
- 

+ 121 - 1
lib/Runtime/Library/ConcatString.cpp

@@ -3,7 +3,7 @@
 // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
 //-------------------------------------------------------------------------------------------------------
 #include "RuntimeLibraryPch.h"
-
+#include "Codex/Utf8Helper.h"
 
 
 namespace Js
@@ -17,11 +17,125 @@ namespace Js
     {
     }
 
+    LiteralStringWithPropertyStringPtr::LiteralStringWithPropertyStringPtr(const char16 * wString,
+      const CharCount stringLength, JavascriptLibrary *const library) :
+        LiteralString(library->GetStringTypeStatic(), wString, stringLength),
+        propertyString(nullptr)
+    {
+    }
+
+    JavascriptString * LiteralStringWithPropertyStringPtr::
+    NewFromWideString(const uint16_t * wideString, const CharCount charCount, JavascriptLibrary *const library)
+    {
+        Assert(library != nullptr && wideString != nullptr);
+
+        switch (charCount)
+        {
+            case 0:
+            {
+                JavascriptString * emptyString = library->GetEmptyString();
+                AssertMsg(VirtualTableInfo<Js::LiteralStringWithPropertyStringPtr>::HasVirtualTable(emptyString),
+                    "Library::GetEmptyString is no longer LiteralStringWithPropertyStringPtr ?");
+                return emptyString;
+            }
+            case 1:
+            {
+                return library->GetCharStringCache().GetStringForChar((char16(*wideString)));
+            }
+            default:
+                break;
+        }
+
+        Recycler * recycler = library->GetRecycler();
+        ScriptContext * scriptContext = library->GetScriptContext();
+        char16* destString = RecyclerNewArrayLeaf(recycler, WCHAR, charCount + 1);
+
+        if (destString == nullptr)
+        {
+            Js::JavascriptError::ThrowOutOfMemoryError(scriptContext);
+        }
+
+        js_wmemcpy_s(destString, charCount, (LPWSTR)wideString, charCount);
+        destString[charCount] = char16(0);
+
+        return (JavascriptString*) RecyclerNew(library->GetRecycler(), LiteralStringWithPropertyStringPtr, destString, charCount, library);
+    }
+
+    JavascriptString * LiteralStringWithPropertyStringPtr::CreateEmptyString(JavascriptLibrary *const library)
+    {
+        return (JavascriptString*) RecyclerNew(library->GetRecycler(), LiteralStringWithPropertyStringPtr, _u(""), 0, library);
+    }
+
+    JavascriptString * LiteralStringWithPropertyStringPtr::
+      NewFromCString(const char * cString, const CharCount charCount, JavascriptLibrary *const library)
+    {
+        Assert(library != nullptr && cString != nullptr);
+
+        switch (charCount)
+        {
+            case 0:
+            {
+                JavascriptString * emptyString = library->GetEmptyString();
+                AssertMsg(VirtualTableInfo<Js::LiteralStringWithPropertyStringPtr>::HasVirtualTable(emptyString),
+                    "Library::GetEmptyString is no longer LiteralStringWithPropertyStringPtr ?");
+                return (LiteralStringWithPropertyStringPtr*) emptyString;
+            }
+            case 1:
+            {
+                return library->GetCharStringCache().GetStringForChar((char16(*cString)));
+            }
+            default:
+                break;
+        }
+
+        ScriptContext * scriptContext = library->GetScriptContext();
+        size_t cbDestString = (charCount + 1) * sizeof(WCHAR);
+        if ((CharCount)cbDestString < charCount) // overflow
+        {
+            Js::JavascriptError::ThrowOutOfMemoryError(scriptContext);
+        }
+
+        Recycler * recycler = library->GetRecycler();
+        char16* destString = RecyclerNewArrayLeaf(recycler, WCHAR, cbDestString);
+        if (destString == nullptr)
+        {
+            Js::JavascriptError::ThrowOutOfMemoryError(scriptContext);
+        }
+
+        HRESULT result = utf8::NarrowStringToWideNoAlloc(cString, charCount, destString, charCount + 1, &cbDestString);
+
+        if (result == S_OK)
+        {
+            return (JavascriptString*) RecyclerNew(library->GetRecycler(), LiteralStringWithPropertyStringPtr, destString, (CharCount)cbDestString, library);
+        }
+
+        Js::JavascriptError::ThrowOutOfMemoryError(scriptContext);
+    }
+
     PropertyString * LiteralStringWithPropertyStringPtr::GetPropertyString() const
     {
         return this->propertyString;
     }
 
+    PropertyString * LiteralStringWithPropertyStringPtr::GetOrAddPropertyString()
+    {
+        if (this->propertyString != nullptr)
+        {
+            return this->propertyString;
+        }
+
+        ScriptContext * scriptContext = this->GetScriptContext();
+
+        Js::PropertyRecord *propertyRecord = nullptr;
+        scriptContext->GetOrAddPropertyRecord(this->GetSz(), static_cast<int>(this->GetLength()),
+            (Js::PropertyRecord const **)&propertyRecord);
+
+        this->propertyString = scriptContext->GetPropertyString(propertyRecord->GetPropertyId());
+        this->SetBuffer(this->propertyString->GetString()); // use the same buffer
+
+        return this->propertyString;
+    }
+
     void LiteralStringWithPropertyStringPtr::SetPropertyString(PropertyString * propStr)
     {
         this->propertyString = propStr;
@@ -39,6 +153,12 @@ namespace Js
         return RecyclableObject::Is(var) && LiteralStringWithPropertyStringPtr::Is(RecyclableObject::UnsafeFromVar(var));
     }
 
+    Js::PropertyRecord const * LiteralStringWithPropertyStringPtr::GetPropertyRecord(bool dontLookupFromDictionary)
+    {
+        // ignores dontLookupFromDictionary
+        return GetOrAddPropertyString()->GetPropertyRecord();
+    }
+
     /////////////////////// ConcatStringBase //////////////////////////
 
     ConcatStringBase::ConcatStringBase(StaticType* stringType) : LiteralString(stringType)

+ 15 - 1
lib/Runtime/Library/ConcatString.h

@@ -9,10 +9,13 @@ namespace Js
     class LiteralStringWithPropertyStringPtr : public LiteralString
     {
     private:
-        PropertyString * propertyString;
+        Field(PropertyString*) propertyString;
 
     public:
+        virtual Js::PropertyRecord const * GetPropertyRecord(bool dontLookupFromDictionary = false) override;
+
         PropertyString * GetPropertyString() const;
+        PropertyString * GetOrAddPropertyString(); // Get if it's there, otherwise bring it in.
         void SetPropertyString(PropertyString * propStr);
 
         template <typename StringType> static LiteralStringWithPropertyStringPtr * ConvertString(StringType * originalString);
@@ -21,8 +24,19 @@ namespace Js
         static bool Is(Var var);
         static bool Is(RecyclableObject* var);
         template <typename T> static LiteralStringWithPropertyStringPtr* TryFromVar(T var);
+
+        static JavascriptString *
+        NewFromCString(const char * cString, const CharCount charCount, JavascriptLibrary *const library);
+
+        static JavascriptString *
+        NewFromWideString(const uint16_t * wString, const CharCount charCount, JavascriptLibrary *const library);
+
+        static JavascriptString * CreateEmptyString(JavascriptLibrary *const library);
+
     protected:
         LiteralStringWithPropertyStringPtr(StaticType* stringTypeStatic);
+        LiteralStringWithPropertyStringPtr(const char16 * wString, const CharCount stringLength, JavascriptLibrary *const library);
+
         DEFINE_VTABLE_CTOR(LiteralStringWithPropertyStringPtr, LiteralString);
 
     public:

+ 6 - 14
lib/Runtime/Library/ForInObjectEnumerator.cpp

@@ -152,7 +152,7 @@ namespace Js
     }
 
     JavascriptString * ForInObjectEnumerator::MoveAndGetNext(PropertyId& propertyId)
-    {        
+    {
         PropertyRecord const * propRecord;
         PropertyAttributes attributes = PropertyNone;
 
@@ -184,24 +184,16 @@ namespace Js
                 // Property Id does not exist.
                 if (propertyId == Constants::NoProperty)
                 {
-                    PropertyString * propertyString = PropertyString::TryFromVar(currentIndex);
-                    if(propertyString != nullptr)
-                    {
-                        // If we have a property string, it is assumed that the propertyId is being
-                        // kept alive with the object
-                        propertyId = propertyString->GetPropertyRecord()->GetPropertyId();
-                    }
-                    else
+                    propRecord = currentIndex->GetPropertyRecord(true);
+                    if (propRecord == nullptr)
                     {
-                        ScriptContext* scriptContext = currentIndex->GetScriptContext();
-                        scriptContext->GetOrAddPropertyRecord(currentIndex, &propRecord);
-                        propertyId = propRecord->GetPropertyId();
-
+                        propRecord = currentIndex->GetPropertyRecord(false); // will create
                         // We keep the track of what is enumerated using a bit vector of propertyID.
                         // so the propertyId can't be collected until the end of the for in enumerator
                         // Keep a list of the property string.
                         this->shadowData->newPropertyStrings.Prepend(GetScriptContext()->GetRecycler(), propRecord);
                     }
+                    propertyId = propRecord->GetPropertyId();
                 }
 
                 if (TestAndSetEnumerated(propertyId) //checks if the property is already enumerated or not
@@ -220,7 +212,7 @@ namespace Js
 
                 RecyclableObject * object;
                 if (!this->enumeratingPrototype)
-                {  
+                {
                     this->enumeratingPrototype = true;
                     object = this->shadowData->firstPrototype;
                     this->shadowData->currentObject = object;

+ 1 - 24
lib/Runtime/Library/JSONStringifier.cpp

@@ -221,30 +221,7 @@ JSONStringifier::Stringify(_In_ ScriptContext* scriptContext, _In_ Var value, _I
 _Ret_notnull_ const PropertyRecord*
 JSONStringifier::GetPropertyRecord(_In_ JavascriptString* key)
 {
-    PropertyString* propertyString = PropertyString::TryFromVar(key);
-    if (propertyString != nullptr)
-    {
-        return propertyString->GetPropertyRecord();
-    }
-
-    LiteralStringWithPropertyStringPtr* strWithPtr = LiteralStringWithPropertyStringPtr::TryFromVar(key);
-    if (strWithPtr != nullptr)
-    {
-        propertyString = strWithPtr->GetPropertyString();
-        if (propertyString != nullptr)
-        {
-            return propertyString->GetPropertyRecord();
-        }
-        const PropertyRecord* propertyRecord;
-        this->scriptContext->GetOrAddPropertyRecord(key, &propertyRecord);
-        propertyString = this->scriptContext->GetPropertyString(propertyRecord->GetPropertyId());
-        strWithPtr->SetPropertyString(propertyString);
-        return propertyRecord;
-    }
-
-    const PropertyRecord* propertyRecord;
-    this->scriptContext->GetOrAddPropertyRecord(key, &propertyRecord);
-    return propertyRecord;
+    return key->GetPropertyRecord();
 }
 
 

+ 1 - 1
lib/Runtime/Library/JavascriptLibrary.cpp

@@ -5548,7 +5548,7 @@ namespace Js
     }
     JavascriptString* JavascriptLibrary::CreateEmptyString()
     {
-        return LiteralString::CreateEmptyString(GetStringTypeStatic());
+        return LiteralStringWithPropertyStringPtr::CreateEmptyString(this);
     }
 
     JavascriptRegExp* JavascriptLibrary::CreateEmptyRegExp()

+ 13 - 0
lib/Runtime/Library/JavascriptString.cpp

@@ -226,6 +226,19 @@ namespace Js
         return JavascriptOperators::GetTypeId(aValue) == TypeIds_String;
     }
 
+    Js::PropertyRecord const * JavascriptString::GetPropertyRecord(bool dontLookupFromDictionary)
+    {
+        if (dontLookupFromDictionary)
+        {
+            return nullptr;
+        }
+
+        Js::PropertyRecord const * propertyRecord;
+        GetScriptContext()->GetOrAddPropertyRecord(GetString(), GetLength(), &propertyRecord);
+
+        return propertyRecord;
+    }
+
     JavascriptString* JavascriptString::FromVar(Var aValue)
     {
         AssertOrFailFastMsg(Is(aValue), "Ensure var is actually a 'JavascriptString'");

+ 2 - 0
lib/Runtime/Library/JavascriptString.h

@@ -50,6 +50,8 @@ namespace Js
         BOOL GetItemAt(charcount_t idxChar, Var* value);
         char16 GetItem(charcount_t index);
 
+        virtual Js::PropertyRecord const * GetPropertyRecord(bool dontLookupFromDictionary = false);
+
         _Ret_range_(m_charLength, m_charLength) charcount_t GetLength() const;
         virtual size_t GetAllocatedByteCount() const;
         virtual bool IsSubstring() const;

+ 1 - 9
lib/Runtime/Library/LazyJSONString.cpp

@@ -57,15 +57,7 @@ LazyJSONString::ReconstructObject(_In_ JSONObject* valueList) const
         PropertyValueInfo info;
         if (!propertyString || !propertyString->TrySetPropertyFromCache(obj, propertyValue, this->GetScriptContext(), PropertyOperation_None, &info))
         {
-            const PropertyRecord* propertyRecord = nullptr;
-            if (propertyString)
-            {
-                propertyRecord = propertyString->GetPropertyRecord();
-            }
-            else
-            {
-                this->GetScriptContext()->GetOrAddPropertyRecord(propertyName, &propertyRecord);
-            }
+            const PropertyRecord* propertyRecord = propertyName->GetPropertyRecord();
             JavascriptOperators::SetProperty(obj, obj, propertyRecord->GetPropertyId(), propertyValue, &info, this->GetScriptContext());
         }
     }

+ 5 - 1
lib/Runtime/Library/PropertyString.h

@@ -18,9 +18,13 @@ protected:
 
     PropertyString(StaticType* type, const Js::PropertyRecord* propertyRecord);
 public:
+    virtual Js::PropertyRecord const * GetPropertyRecord(bool dontLookupFromDictionary = false) override
+    {
+        return this->propertyRecord;
+    }
+
     PolymorphicInlineCache * GetLdElemInlineCache() const;
     PolymorphicInlineCache * GetStElemInlineCache() const;
-    Js::PropertyRecord const * GetPropertyRecord() const { return this->propertyRecord; }
     PolymorphicInlineCache * CreateBiggerPolymorphicInlineCache(bool isLdElem);
     void RegisterCacheMiss();
     int GetHitRate() const { return this->hitRate; };

+ 2 - 4
lib/Runtime/Types/PathTypeHandler.cpp

@@ -448,16 +448,14 @@ namespace Js
         // Consider: Implement actual string hash lookup
         Assert(requestContext);
         PropertyRecord const* propertyRecord;
-        char16 const * propertyName = propertyNameString->GetString();
-        charcount_t const propertyNameLength = propertyNameString->GetLength();
 
         if (instance->HasObjectArray())
         {
-            requestContext->GetOrAddPropertyRecord(propertyName, propertyNameLength, &propertyRecord);
+            requestContext->GetOrAddPropertyRecord(propertyNameString, &propertyRecord);
         }
         else
         {
-            requestContext->FindPropertyRecord(propertyName, propertyNameLength, &propertyRecord);
+            requestContext->FindPropertyRecord(propertyNameString, &propertyRecord);
             if (propertyRecord == nullptr)
             {
                 *value = requestContext->GetMissingPropertyResult();

+ 1 - 9
lib/Runtime/Types/SimpleDictionaryTypeHandler.cpp

@@ -57,15 +57,7 @@ namespace Js
     const PropertyRecord* TMapKey_ConvertKey(ScriptContext* scriptContext, JavascriptString* key)
     {
         PropertyRecord const * propertyRecord;
-        PropertyString * propertyString = PropertyString::TryFromVar(key);
-        if (propertyString != nullptr)
-        {
-            propertyRecord = propertyString->GetPropertyRecord();
-        }
-        else
-        {
-            scriptContext->GetOrAddPropertyRecord(key, &propertyRecord);
-        }
+        scriptContext->GetOrAddPropertyRecord(key, &propertyRecord);
         return propertyRecord;
     }
 

+ 52 - 0
test/native-tests/test-property/Platform.js

@@ -0,0 +1,52 @@
+//-------------------------------------------------------------------------------------------------------
+// Copyright (C) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
+//-------------------------------------------------------------------------------------------------------
+
+var isWindows = !WScript.Platform || WScript.Platform.OS == 'win32';
+var path_sep = isWindows ? '\\' : '/';
+var isStaticBuild = WScript.Platform && WScript.Platform.LINK_TYPE == 'static';
+
+if (!isStaticBuild) {
+    // test will be ignored
+    print("# IGNORE_THIS_TEST");
+} else {
+    var platform = WScript.Platform.OS;
+    var binaryPath = WScript.Platform.BINARY_PATH;
+    // discard `ch` from path
+    binaryPath = binaryPath.substr(0, binaryPath.lastIndexOf(path_sep));
+    var makefile =
+"IDIR=" + binaryPath + "/../../lib/Jsrt \n\
+\n\
+LIBRARY_PATH=" + binaryPath + "/lib\n\
+PLATFORM=" + platform + "\n\
+LDIR=$(LIBRARY_PATH)/libChakraCoreStatic.a \n\
+\n\
+ifeq (darwin, ${PLATFORM})\n\
+\tICU4C_LIBRARY_PATH ?= /usr/local/opt/icu4c\n\
+\tCFLAGS=-lstdc++ -std=c++11 -I$(IDIR)\n\
+\tFORCE_STARTS=-Wl,-force_load,\n\
+\tFORCE_ENDS=\n\
+\tLIBS=-framework CoreFoundation -framework Security -lm -ldl -Wno-c++11-compat-deprecated-writable-strings \
+    -Wno-deprecated-declarations -Wno-unknown-warning-option -o sample.o\n\
+\tLDIR+=$(ICU4C_LIBRARY_PATH)/lib/libicudata.a \
+    $(ICU4C_LIBRARY_PATH)/lib/libicuuc.a \
+    $(ICU4C_LIBRARY_PATH)/lib/libicui18n.a\n\
+else\n\
+\tCFLAGS=-lstdc++ -std=c++0x -I$(IDIR)\n\
+\tFORCE_STARTS=-Wl,--whole-archive\n\
+\tFORCE_ENDS=-Wl,--no-whole-archive\n\
+\tLIBS=-pthread -lm -ldl -licuuc -Wno-c++11-compat-deprecated-writable-strings \
+    -Wno-deprecated-declarations -Wno-unknown-warning-option -o sample.o\n\
+endif\n\
+\n\
+testmake:\n\
+\t$(CC) sample.cpp $(CFLAGS) $(FORCE_STARTS) $(LDIR) $(FORCE_ENDS) $(LIBS)\n\
+\n\
+.PHONY: clean\n\
+\n\
+clean:\n\
+\trm sample.o\n";
+
+    print(makefile)
+}

+ 106 - 0
test/native-tests/test-property/sample.cpp

@@ -0,0 +1,106 @@
+//-------------------------------------------------------------------------------------------------------
+// Copyright (C) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
+//-------------------------------------------------------------------------------------------------------
+
+#include "ChakraCore.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <string>
+#include <cstring>
+
+#define FAIL_CHECK(cmd)                     \
+    do                                      \
+    {                                       \
+        JsErrorCode errCode = cmd;          \
+        if (errCode != JsNoError)           \
+        {                                   \
+            printf("Error %d at '%s'\n",    \
+                errCode, #cmd);             \
+            return 1;                       \
+        }                                   \
+    } while(0)
+
+using namespace std;
+
+int main()
+{
+    JsRuntimeHandle runtime;
+    JsContextRef context;
+    JsValueRef result;
+    unsigned currentSourceContext = 0;
+
+    const char* script = "(()=>{return \'SUCCESS\';})()";
+
+    // Create a runtime.
+    JsCreateRuntime(JsRuntimeAttributeNone, nullptr, &runtime);
+
+    // Create an execution context.
+    JsCreateContext(runtime, &context);
+
+    // Now set the current execution context.
+    JsSetCurrentContext(context);
+
+    JsValueRef fname;
+    FAIL_CHECK(JsCreateString("sample", sizeof("sample") - 1, &fname));
+
+    JsValueRef object;
+    FAIL_CHECK(JsCreateObject(&object));
+
+    JsValueRef scriptSource;
+    FAIL_CHECK(JsCreateExternalArrayBuffer((void*)script, (unsigned int)strlen(script),
+                                           nullptr, nullptr, &scriptSource));
+
+    FAIL_CHECK(JsObjectSetProperty(object, fname, fname, false));
+
+    JsValueRef fname_copy;
+    FAIL_CHECK(JsObjectGetProperty(object, fname, &fname_copy));
+
+    bool hasObject = false;
+    FAIL_CHECK(JsObjectHasProperty(object, fname, &hasObject));
+
+    bool hasOwn = false;
+    FAIL_CHECK(JsObjectHasOwnProperty(object, fname, &hasOwn));
+
+    JsValueRef pdesc;
+    FAIL_CHECK(JsObjectGetOwnPropertyDescriptor(object, fname, &pdesc));
+
+    if (!hasOwn || !hasObject || fname_copy != fname || pdesc == nullptr)
+    {
+        fprintf(stderr, "JsObject Has/Get/Set etc. Property... failed\n");
+    }
+
+    JsValueRef fname_second_copy;
+    FAIL_CHECK(JsObjectDeleteProperty(object, fname, false, &fname_second_copy));
+
+    FAIL_CHECK(JsObjectHasProperty(object, fname, &hasObject));
+
+    if (hasObject || fname_second_copy != fname)
+    {
+        fprintf(stderr, "JsObjectDeleteProperty failed\n");
+    }
+
+    // Run the script.
+    FAIL_CHECK(JsRun(scriptSource, currentSourceContext++, fname, JsParseScriptAttributeNone, &result));
+
+    // Convert your script result to String in JavaScript; redundant if your script returns a String
+    JsValueRef resultJSString;
+    FAIL_CHECK(JsConvertValueToString(result, &resultJSString));
+
+    // Project script result back to C++.
+    char *resultSTR = nullptr;
+    size_t stringLength;
+    FAIL_CHECK(JsCopyString(resultJSString, nullptr, 0, &stringLength));
+    resultSTR = (char*) malloc(stringLength + 1);
+    FAIL_CHECK(JsCopyString(resultJSString, resultSTR, stringLength, nullptr));
+    resultSTR[stringLength] = 0;
+
+    printf("Result -> %s \n", resultSTR);
+    free(resultSTR);
+
+    // Dispose runtime
+    JsSetCurrentContext(JS_INVALID_REFERENCE);
+    JsDisposeRuntime(runtime);
+
+    return 0;
+}

+ 3 - 0
test/native-tests/test_native.sh

@@ -94,6 +94,9 @@ RUN "test-pal"
 # test-static-native
 RUN "test-static-native"
 
+# test-property
+RUN "test-property"
+
 # shared lib tests
 LIB_DIR="$(dirname ${CH_DIR})"
 if [[ `uname -a` =~ "Darwin" ]]; then