* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ecmascript/property_accessor.h"
#include "ecmascript/ic/proto_change_details.h"
#include "ecmascript/js_handle.h"
#include "ecmascript/js_object-inl.h"
#include "ecmascript/js_tagged_value_wrapper.h"
namespace panda::ecmascript {
PropertyAccessor::PropertyAccessor(JSThread *thread, JSHandle<JSTaggedValue> object)
: thread_(thread),
receiver_(thread, object.GetTaggedValue()),
fastKeysArray_(thread, JSTaggedValue::Undefined()),
cachedHClass_(thread, JSTaggedValue::Undefined()),
enumCache_(thread, JSTaggedValue::Undefined()),
keyLength_(0),
shadowKeyLength_(0),
onlyHasSimpleProperties_(true),
hasSlowProperties_(false),
hasPrototypeChainEnumCache_(false),
slowKeysArray_(thread, JSTaggedValue::Undefined()),
acutalKeyLength_(0)
{
PreLoad();
}
void PropertyAccessor::PreLoad()
{
JSHandle<JSObject> receiverObj(receiver_);
JSHandle<JSHClass> jsHClass(thread_, receiverObj->GetJSHClass());
if (receiver_->IsSlowKeysObject()) {
hasSlowProperties_ = true;
return;
}
enumCache_ = JSHandle<JSTaggedValue>::Cast(
JSObject::GetOrCreateEnumCache(thread_, jsHClass));
cachedHClass_.Update(jsHClass);
if (jsHClass->IsDictionaryMode()) {
onlyHasSimpleProperties_ = false;
}
uint32_t numOfElements = receiverObj->GetNumberOfElements(thread_);
if (numOfElements > 0) {
AccumulateKeyLength(numOfElements);
onlyHasSimpleProperties_ = false;
tryPrototypeChainEnumCache_ = false;
}
std::pair<uint32_t, uint32_t> numOfKeys = receiverObj->GetNumberOfEnumKeys(thread_);
uint32_t numOfEnumKeys = numOfKeys.first;
if (numOfEnumKeys > 0) {
AccumulateKeyLength(numOfEnumKeys);
}
uint32_t numOfShadowKeys = numOfKeys.second;
if (numOfShadowKeys > 0) {
AccumulateShadowKeyLength(numOfShadowKeys);
}
CopyKeyLengthToSelf();
hasPrototypeChainEnumCache_ = HasPrototypeChainEnumCache();
CollectPrototypeInfo();
if (hasSlowProperties_ || !onlyHasSimpleProperties_) {
return;
}
ASSERT(!hasSlowProperties_ && onlyHasSimpleProperties_);
InitSimplePropertiesEnumCache();
}
bool PropertyAccessor::HasPrototypeChainEnumCache()
{
JSTaggedValue current = JSTaggedValue::GetPrototype(thread_, receiver_);
if (!current.IsHeapObject()) {
tryPrototypeChainEnumCache_ = false;
return false;
}
JSTaggedValue enumCache = current.GetTaggedObject()->GetClass()->GetEnumCache(thread_);
return enumCache.IsEnumCacheAllValid(thread_);
}
void PropertyAccessor::CollectPrototypeInfo()
{
DISALLOW_GARBAGE_COLLECTION;
JSTaggedValue current = JSTaggedValue::GetPrototype(thread_, receiver_);
RETURN_IF_ABRUPT_COMPLETION(thread_);
while (current.IsHeapObject()) {
if (current.IsSlowKeysObject()) {
hasSlowProperties_ = true;
break;
}
JSObject *currentObj = JSObject::Cast(current.GetTaggedObject());
uint32_t numOfCurrentElements = currentObj->GetNumberOfElements(thread_);
if (numOfCurrentElements > 0) {
AccumulateKeyLength(numOfCurrentElements);
onlyHasSimpleProperties_ = false;
hasPrototypeChainEnumCache_ = false;
tryPrototypeChainEnumCache_ = false;
}
std::pair<uint32_t, uint32_t> numOfKeys = currentObj->GetNumberOfEnumKeys(thread_);
uint32_t numOfEnumKeys = numOfKeys.first;
if (numOfEnumKeys > 0) {
AccumulateKeyLength(numOfEnumKeys);
onlyHasSimpleProperties_ = false;
}
uint32_t numOfShadowKeys = numOfKeys.second;
if (numOfShadowKeys > 0) {
AccumulateShadowKeyLength(numOfShadowKeys);
}
JSHClass *jshclass = currentObj->GetJSHClass();
if (jshclass->IsDictionaryMode()) {
onlyHasSimpleProperties_ = false;
hasPrototypeChainEnumCache_ = false;
tryPrototypeChainEnumCache_ = false;
}
current = JSObject::GetPrototype(thread_, current);
}
}
void PropertyAccessor::InitSimplePropertiesEnumCache()
{
ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
JSHandle<JSObject> receiverObj(receiver_);
JSHandle<JSHClass> jsHClass(thread_, receiverObj->GetJSHClass());
ASSERT(receiverObj->GetNumberOfElements(thread_) == 0);
ASSERT(!receiver_->IsInSharedHeap());
JSMutableHandle<TaggedArray> keyArray(thread_, JSTaggedValue::Undefined());
if (keyLength_ == 0) {
keyArray.Update(factory->EmptyArray());
SetActualKeyLength(0);
} else {
uint32_t arraySize = keyLength_;
JSHandle<TaggedArray> newArray = thread_->GetEcmaVM()->GetFactory()->NewTaggedArray(arraySize);
uint32_t length = JSObject::GetAllEnumKeys(thread_, receiverObj, 0, newArray);
SetActualKeyLength(length);
keyArray.Update(newArray);
}
JSTaggedValue proto = JSObject::GetPrototype(thread_, receiver_.GetTaggedValue());
if (proto.IsHeapObject()) {
JSHandle<EnumCache> enumCache = JSObject::GetOrCreateEnumCache(thread_,
JSHandle<JSHClass>(thread_, proto.GetTaggedObject()->GetClass()));
enumCache->SetProtoChainInfoEnumCache(thread_, JSTaggedValue::Undefined());
JSHClass::EnableProtoChangeMarker(thread_, jsHClass);
}
JSObject::SetEnumCacheKind(thread_, JSHandle<EnumCache>::Cast(enumCache_), EnumCacheKind::SIMPLE);
JSObject::ClearHasDeleteProperty(receiver_);
fastKeysArray_.Update(keyArray.GetTaggedValue());
}
inline void PropertyAccessor::CopyKeyLengthToSelf()
{
keyLengthSelf_ = keyLength_;
shadowKeyLengthSelf_ = shadowKeyLength_;
}
inline void PropertyAccessor::AccumulateKeyLength(uint32_t length)
{
keyLength_ += length;
}
inline void PropertyAccessor::AccumulateShadowKeyLength(uint32_t length)
{
shadowKeyLength_ += length;
}
JSHandle<JSTaggedValue> PropertyAccessor::GetCachedHClass() const
{
return cachedHClass_;
}
JSHandle<JSTaggedValue> PropertyAccessor::GetEnumCache() const
{
return enumCache_;
}
uint32_t PropertyAccessor::GetActualKeyLength() const
{
return acutalKeyLength_;
}
inline void PropertyAccessor::SetActualKeyLength(uint32_t length)
{
acutalKeyLength_ = length;
}
void PropertyAccessor::AddUndefinedEndIfNeeded(JSHandle<TaggedArray> keys)
{
if (acutalKeyLength_ < keyLength_) {
keys->Set(thread_, acutalKeyLength_, JSTaggedValue::Undefined());
}
}
void PropertyAccessor::AddUndefinedEndIfNeeded(JSHandle<TaggedArray> keys,
const uint32_t keyLength, const uint32_t acutalKeyLength)
{
if (acutalKeyLength < keyLength) {
keys->Set(thread_, acutalKeyLength, JSTaggedValue::Undefined());
}
}
JSHandle<TaggedArray> PropertyAccessor::GetChainKeys(const JSHandle<JSTaggedValue> &receiver,
const uint32_t keyLength,
const uint32_t shadowKeyLength)
{
ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
uint32_t arraySize = keyLength;
JSHandle<TaggedArray> keyArray = factory->NewTaggedArray(arraySize);
JSHandle<TaggedQueue> shadowQueue = factory->NewTaggedQueue(shadowKeyLength + 1);
uint32_t keysNum = 0;
JSMutableHandle<JSTaggedValue> current(thread_, receiver);
while (current->IsHeapObject()) {
JSObject::AppendOwnEnumPropertyKeys(thread_, JSHandle<JSObject>(current), keyArray, &keysNum, shadowQueue);
RETURN_HANDLE_IF_ABRUPT_COMPLETION(TaggedArray, thread_);
JSObject::ClearHasDeleteProperty(current);
current.Update(JSObject::GetPrototype(thread_, current.GetTaggedValue()));
}
AddUndefinedEndIfNeeded(keyArray, keyLength, keysNum);
return keyArray;
}
JSHandle<JSTaggedValue> PropertyAccessor::GetKeysFastWithoutCache()
{
JSHandle<TaggedArray> keyArray = GetChainKeys(receiver_, keyLength_, shadowKeyLength_);
return JSHandle<JSTaggedValue>::Cast(keyArray);
}
std::pair<JSHandle<TaggedArray>, JSHandle<TaggedQueue>> PropertyAccessor::GetOwnKeysWithoutCache()
{
ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
uint32_t arraySize = keyLengthSelf_;
JSHandle<TaggedArray> keyArrayOnReceiver = factory->NewTaggedArray(arraySize);
JSHandle<TaggedQueue> shadowQueueOnReceiver = factory->NewTaggedQueue(shadowKeyLengthSelf_ + 1);
uint32_t keysNum = 0;
JSObject::AppendOwnEnumPropertyKeys(thread_, JSHandle<JSObject>(receiver_),
keyArrayOnReceiver, &keysNum, shadowQueueOnReceiver);
AddUndefinedEndIfNeeded(keyArrayOnReceiver, arraySize, keysNum);
return std::make_pair(keyArrayOnReceiver, shadowQueueOnReceiver);
}
bool PropertyAccessor::IsObjectWithoutKey() const
{
return keyLengthSelf_ == 0 && shadowKeyLengthSelf_ == 0;
}
std::pair<JSHandle<TaggedArray>, JSHandle<TaggedQueue>> PropertyAccessor::GetOwnKeys()
{
if (IsObjectWithoutKey()) {
ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
JSHandle<TaggedArray> keyArrayOnReceiver = factory->EmptyArray();
JSHandle<TaggedQueue> shadowQueueOnReceiver = factory->NewTaggedQueue(0);
return std::make_pair(keyArrayOnReceiver, shadowQueueOnReceiver);
}
return GetOwnKeysWithoutCache();
}
JSHandle<TaggedArray> PropertyAccessor::GetAndSetChainKeys(const JSHandle<JSTaggedValue> &proto,
const uint32_t keyLength,
const uint32_t shadowKeyLength)
{
JSHandle<TaggedArray> keyArrayOnPrototypeChain = GetChainKeys(proto, keyLength, shadowKeyLength);
ASSERT(!receiver_->IsInSharedHeap());
ASSERT(!onlyHasSimpleProperties_);
JSHandle<JSObject> protoObj(proto);
JSHandle<JSHClass> jsHClass(thread_, protoObj->GetJSHClass());
JSHandle<EnumCache> enumCacheProto = JSObject::GetOrCreateEnumCache(thread_, jsHClass);
JSHandle<EnumCache>::Cast(enumCache_)->
SetProtoChainInfoEnumCache(thread_, keyArrayOnPrototypeChain.GetTaggedValue());
enumCacheProto->SetEnumCacheAll(thread_, keyArrayOnPrototypeChain.GetTaggedValue());
JSHClass::EnablePHCProtoChangeMarker(thread_, jsHClass);
return keyArrayOnPrototypeChain;
}
void PropertyAccessor::AddKey(const JSHandle<JSTaggedValue> &value,
JSHandle<TaggedArray>& allKeys, uint32_t& allKeysLength,
const JSHandle<TaggedArray> &keyArrayOnReceiver,
const JSHandle<TaggedQueue> &shadowQueueOnReceiver)
{
uint32_t receiverKeysLength = keyArrayOnReceiver->GetLength();
JSMutableHandle<JSTaggedValue> key(thread_, JSTaggedValue::Undefined());
for (uint32_t i = 0; i < receiverKeysLength; i++) {
key.Update(keyArrayOnReceiver->Get(thread_, i));
if (JSTaggedValue::Equal(thread_, key, value)) {
return;
}
}
uint32_t shadowKeysSize = shadowQueueOnReceiver->Size(thread_);
for (uint32_t i = 0; i < shadowKeysSize; i++) {
key.Update(shadowQueueOnReceiver->Get(thread_, i));
if (JSTaggedValue::Equal(thread_, key, value)) {
return;
}
}
allKeys->Set(thread_, allKeysLength++, value);
}
JSHandle<TaggedArray> PropertyAccessor::CombineKeys(const JSHandle<TaggedArray> &keyArrayOnReceiver,
const JSHandle<TaggedArray> &keyArrayOnPrototypeChain,
const JSHandle<TaggedQueue> &shadowQueueOnReceiver)
{
uint32_t receiverKeysLength = keyArrayOnReceiver->GetLength();
uint32_t PrototypeChainKeysLength = keyArrayOnPrototypeChain->GetLength();
uint32_t allKeysLength = receiverKeysLength;
ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
uint32_t arraySize = receiverKeysLength + PrototypeChainKeysLength;
JSHandle<TaggedArray> allKeys = factory->NewTaggedArray(arraySize);
allKeys->Copy(thread_, 0, 0, keyArrayOnReceiver.GetObject<TaggedArray>(), receiverKeysLength);
JSMutableHandle<JSTaggedValue> value(thread_, JSTaggedValue::Undefined());
for (uint32_t i = 0; i < PrototypeChainKeysLength; i++) {
value.Update(keyArrayOnPrototypeChain->Get(thread_, i));
AddKey(value, allKeys, allKeysLength, keyArrayOnReceiver, shadowQueueOnReceiver);
}
AddUndefinedEndIfNeeded(allKeys, arraySize, allKeysLength);
return allKeys;
}
JSHandle<JSTaggedValue> PropertyAccessor::GetKeysFastWithPrototypeChainEnumCache()
{
auto [keyArrayOnReceiver, shadowQueueOnReceiver] = GetOwnKeys();
JSHandle<TaggedArray> keyArrayOnPrototypeChain;
auto proto = JSTaggedValue::GetPrototype(thread_, receiver_);
JSHandle<JSTaggedValue> protoHandle(thread_, proto);
if (hasPrototypeChainEnumCache_) {
EnumCache* enumCacheProto = EnumCache::Cast(proto.GetTaggedObject()->GetClass()->GetEnumCache(thread_));
JSHandle<JSTaggedValue> keyValueOnPrototypeChain(thread_, enumCacheProto->GetEnumCacheAll(thread_));
keyArrayOnPrototypeChain = JSHandle<TaggedArray>::Cast(keyValueOnPrototypeChain);
JSHandle<EnumCache>::Cast(enumCache_)->
SetProtoChainInfoEnumCache(thread_, keyArrayOnPrototypeChain.GetTaggedValue());
} else {
keyArrayOnPrototypeChain = GetAndSetChainKeys(protoHandle,
keyLength_ - keyLengthSelf_, shadowKeyLength_ - shadowKeyLengthSelf_);
}
JSHandle<TaggedArray> allKeys = CombineKeys(keyArrayOnReceiver, keyArrayOnPrototypeChain, shadowQueueOnReceiver);
return JSHandle<JSTaggedValue>::Cast(allKeys);
}
JSHandle<JSTaggedValue> PropertyAccessor::GetKeysFast()
{
if (!fastKeysArray_->IsUndefined()) {
AddUndefinedEndIfNeeded(JSHandle<TaggedArray>(thread_, fastKeysArray_.GetTaggedValue()));
JSHandle<EnumCache>::Cast(enumCache_)->SetEnumCacheAll(thread_, fastKeysArray_.GetTaggedValue());
return fastKeysArray_;
}
if (hasSlowProperties_) {
return JSHandle<JSTaggedValue>(thread_, JSTaggedValue::Undefined());
}
ASSERT(!receiver_->IsInSharedHeap());
ASSERT(!onlyHasSimpleProperties_);
if (tryPrototypeChainEnumCache_) {
fastKeysArray_.Update(GetKeysFastWithPrototypeChainEnumCache());
JSHandle<EnumCache>::Cast(enumCache_)->SetEnumCacheAll(thread_, fastKeysArray_.GetTaggedValue());
} else {
fastKeysArray_.Update(GetKeysFastWithoutCache());
}
JSObject::SetEnumCacheKind(thread_, JSHandle<EnumCache>::Cast(enumCache_), EnumCacheKind::PROTOCHAIN);
return fastKeysArray_;
}
JSHandle<JSTaggedValue> PropertyAccessor::GetKeysSlow()
{
std::vector<JSHandle<TaggedArray>> remainings;
std::vector<JSHandle<JSTaggedValue>> visited;
JSMutableHandle<JSTaggedValue> current(thread_, receiver_);
while (current->IsHeapObject()) {
PushRemainingKeys(JSHandle<JSObject>(current), remainings);
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
JSObject::ClearHasDeleteProperty(current);
visited.emplace_back(thread_, current.GetTaggedValue());
current.Update(JSTaggedValue::GetPrototype(thread_, current));
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
}
MergeRemainings(remainings, visited);
return JSHandle<JSTaggedValue>(thread_, slowKeysArray_.GetTaggedValue());
}
void PropertyAccessor::PushRemainingKeys(JSHandle<JSObject> object, std::vector<JSHandle<TaggedArray>> &remainings)
{
JSMutableHandle<JSTaggedValue> value(thread_, JSTaggedValue::Undefined());
uint32_t remainingIndex = 0;
if (object->IsJSProxy()) {
JSHandle<TaggedArray> proxyArr = JSProxy::OwnPropertyKeys(thread_, JSHandle<JSProxy>(object));
RETURN_IF_ABRUPT_COMPLETION(thread_);
uint32_t length = proxyArr->GetLength();
for (uint32_t i = 0; i < length; i++) {
value.Update(proxyArr->Get(thread_, i));
PropertyDescriptor desc(thread_);
JSProxy::GetOwnProperty(thread_, JSHandle<JSProxy>(object), value, desc);
RETURN_IF_ABRUPT_COMPLETION(thread_);
if (!desc.IsEnumerable()) {
proxyArr->Set(thread_, i, JSTaggedValue::Hole());
} else {
remainingIndex++;
}
}
remainings.push_back(proxyArr);
AccumulateKeyLength(remainingIndex);
} else {
JSHandle<TaggedArray> array = JSTaggedValue::GetOwnEnumPropertyKeys(thread_, JSHandle<JSTaggedValue>(object));
uint32_t length = array->GetLength();
for (uint32_t i = 0; i < length; i++) {
value.Update(array->Get(thread_, i));
if (!value->IsString()) {
array->Set(thread_, i, JSTaggedValue::Hole());
} else {
remainingIndex++;
}
}
remainings.push_back(array);
AccumulateKeyLength(remainingIndex);
}
}
void PropertyAccessor::MergeRemainings(const std::vector<JSHandle<TaggedArray>> &remainings,
const std::vector<JSHandle<JSTaggedValue>> &visited)
{
uint32_t arraySize = keyLength_;
JSHandle<TaggedArray> keyArray = thread_->GetEcmaVM()->GetFactory()->NewTaggedArray(arraySize);
JSMutableHandle<TaggedArray> remaining(thread_, JSTaggedValue::Undefined());
JSMutableHandle<JSTaggedValue> keyHandle(thread_, JSTaggedValue::Undefined());
JSMutableHandle<JSTaggedValue> objHandle(thread_, JSTaggedValue::Undefined());
uint32_t index = 0;
uint32_t numberOfRemaining = remainings.size();
for (uint32_t i = 0; i < numberOfRemaining; i++) {
remaining.Update(remainings[i]);
uint32_t remainingSize = remaining->GetLength();
for (uint32_t j = 0; j < remainingSize; j++) {
keyHandle.Update(remaining->Get(thread_, j));
if (keyHandle->IsHole()) {
continue;
}
bool has = false;
for (uint32_t k = 0; k < i; k++) {
objHandle.Update(visited[k]);
PropertyDescriptor desc(thread_);
has = JSTaggedValue::GetOwnProperty(thread_, objHandle, keyHandle, desc);
RETURN_IF_ABRUPT_COMPLETION(thread_);
if (has) {
break;
}
}
if (!has) {
keyArray->Set(thread_, index, keyHandle);
index++;
}
}
}
SetActualKeyLength(index);
AddUndefinedEndIfNeeded(keyArray);
slowKeysArray_.Update(keyArray.GetTaggedValue());
}
}