/* * 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.h" namespace panda::ecmascript { PropertyAccessor::PropertyAccessor(JSThread *thread, JSHandle 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 receiverObj(receiver_); JSHandle jsHClass(thread_, receiverObj->GetJSHClass()); if (receiver_->IsSlowKeysObject()) { hasSlowProperties_ = true; return; } enumCache_ = JSHandle::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 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_); // Fath path for simple properties. 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 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); } } // For simple enumCache, set the ProtoChainInfoEnumCache of // the receiver's prototype to JSTaggedValue::UNDEFINED. // Additionally, enable the proto change marker to ensure that if the receiver's prototype chain changes, // the ProtoChainInfoEnumCache of the receiver's prototype becomes JSTaggedValue::NULL. void PropertyAccessor::InitSimplePropertiesEnumCache() { ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory(); JSHandle receiverObj(receiver_); JSHClass *jsHClass = receiverObj->GetJSHClass(); ASSERT(receiverObj->GetNumberOfElements(thread_) == 0); ASSERT(!receiver_->IsInSharedHeap()); JSMutableHandle keyArray(thread_, JSTaggedValue::Undefined()); if (keyLength_ == 0) { keyArray.Update(factory->EmptyArray()); SetActualKeyLength(0); } else { uint32_t arraySize = keyLength_; JSHandle newArray = thread_->GetEcmaVM()->GetFactory()->NewTaggedArray(arraySize); uint32_t length = JSObject::GetAllEnumKeys(thread_, receiverObj, 0, newArray); SetActualKeyLength(length); keyArray.Update(newArray); } // Set proto's ProtoChainInfoEnumCache to JSTaggedValue::NULL and enable proto's ChangeMarker. JSTaggedValue proto = JSObject::GetPrototype(thread_, receiver_.GetTaggedValue()); if (proto.IsHeapObject()) { JSHandle enumCache = JSObject::GetOrCreateEnumCache(thread_, JSHandle(thread_, proto.GetTaggedObject()->GetClass())); enumCache->SetProtoChainInfoEnumCache(thread_, JSTaggedValue::Undefined()); JSHClass::EnableProtoChangeMarker(thread_, JSHandle(thread_, jsHClass)); } JSObject::SetEnumCacheKind(thread_, JSHandle::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 PropertyAccessor::GetCachedHClass() const { return cachedHClass_; } JSHandle 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 keys) { // when has duplicated keys if (acutalKeyLength_ < keyLength_) { keys->Set(thread_, acutalKeyLength_, JSTaggedValue::Undefined()); } } void PropertyAccessor::AddUndefinedEndIfNeeded(JSHandle keys, const uint32_t keyLength, const uint32_t acutalKeyLength) { if (acutalKeyLength < keyLength) { keys->Set(thread_, acutalKeyLength, JSTaggedValue::Undefined()); } } JSHandle PropertyAccessor::GetChainKeys(const JSHandle &receiver, const uint32_t keyLength, const uint32_t shadowKeyLength) { ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory(); uint32_t arraySize = keyLength; JSHandle keyArray = factory->NewTaggedArray(arraySize); JSHandle shadowQueue = factory->NewTaggedQueue(shadowKeyLength + 1); uint32_t keysNum = 0; JSMutableHandle current(thread_, receiver); while (current->IsHeapObject()) { JSObject::AppendOwnEnumPropertyKeys(thread_, JSHandle(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 PropertyAccessor::GetKeysFastWithoutCache() { JSHandle keyArray = GetChainKeys(receiver_, keyLength_, shadowKeyLength_); return JSHandle::Cast(keyArray); } std::pair, JSHandle> PropertyAccessor::GetOwnKeysWithoutCache() { ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory(); uint32_t arraySize = keyLengthSelf_; JSHandle keyArrayOnReceiver = factory->NewTaggedArray(arraySize); JSHandle shadowQueueOnReceiver = factory->NewTaggedQueue(shadowKeyLengthSelf_ + 1); uint32_t keysNum = 0; JSObject::AppendOwnEnumPropertyKeys(thread_, JSHandle(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> PropertyAccessor::GetOwnKeys() { if (IsObjectWithoutKey()) { ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory(); JSHandle keyArrayOnReceiver = factory->NewTaggedArray(0); JSHandle shadowQueueOnReceiver = factory->NewTaggedQueue(0); return std::make_pair(keyArrayOnReceiver, shadowQueueOnReceiver); } return GetOwnKeysWithoutCache(); } JSHandle PropertyAccessor::GetAndSetChainKeys(const JSHandle &proto, const uint32_t keyLength, const uint32_t shadowKeyLength) { JSHandle keyArrayOnPrototypeChain = GetChainKeys(proto, keyLength, shadowKeyLength); ASSERT(!receiver_->IsInSharedHeap()); ASSERT(!onlyHasSimpleProperties_); JSHandle protoObj(proto); JSHandle jsHClass(thread_, protoObj->GetJSHClass()); JSHandle enumCacheProto = JSObject::GetOrCreateEnumCache(thread_, jsHClass); // receiver's ProtoChainInfoEnumCache == proto's EnumCacheAll. JSHandle::Cast(enumCache_)-> SetProtoChainInfoEnumCache(thread_, keyArrayOnPrototypeChain.GetTaggedValue()); enumCacheProto->SetEnumCacheAll(thread_, keyArrayOnPrototypeChain.GetTaggedValue()); JSHClass::EnablePHCProtoChangeMarker(thread_, jsHClass); return keyArrayOnPrototypeChain; } void PropertyAccessor::AddKey(const JSHandle &value, JSHandle& allKeys, uint32_t& allKeysLength, const JSHandle &keyArrayOnReceiver, const JSHandle &shadowQueueOnReceiver) { uint32_t receiverKeysLength = keyArrayOnReceiver->GetLength(); JSMutableHandle 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 PropertyAccessor::CombineKeys(const JSHandle &keyArrayOnReceiver, const JSHandle &keyArrayOnPrototypeChain, const JSHandle &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 allKeys = factory->NewTaggedArray(arraySize); allKeys->Copy(thread_, 0, 0, keyArrayOnReceiver.GetObject(), receiverKeysLength); JSMutableHandle 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 PropertyAccessor::GetKeysFastWithPrototypeChainEnumCache() { auto [keyArrayOnReceiver, shadowQueueOnReceiver] = GetOwnKeys(); JSHandle keyArrayOnPrototypeChain; auto proto = JSTaggedValue::GetPrototype(thread_, receiver_); JSHandle protoHandle(thread_, proto); if (hasPrototypeChainEnumCache_) { EnumCache* enumCacheProto = EnumCache::Cast(proto.GetTaggedObject()->GetClass()->GetEnumCache(thread_)); JSHandle keyValueOnPrototypeChain(thread_, enumCacheProto->GetEnumCacheAll(thread_)); keyArrayOnPrototypeChain = JSHandle::Cast(keyValueOnPrototypeChain); JSHandle::Cast(enumCache_)-> SetProtoChainInfoEnumCache(thread_, keyArrayOnPrototypeChain.GetTaggedValue()); } else { keyArrayOnPrototypeChain = GetAndSetChainKeys(protoHandle, keyLength_ - keyLengthSelf_, shadowKeyLength_ - shadowKeyLengthSelf_); } JSHandle allKeys = CombineKeys(keyArrayOnReceiver, keyArrayOnPrototypeChain, shadowQueueOnReceiver); return JSHandle::Cast(allKeys); } JSHandle PropertyAccessor::GetKeysFast() { // Fath path for simple properties. if (!fastKeysArray_->IsUndefined()) { AddUndefinedEndIfNeeded(JSHandle(thread_, fastKeysArray_.GetTaggedValue())); JSHandle::Cast(enumCache_)->SetEnumCacheAll(thread_, fastKeysArray_.GetTaggedValue()); return fastKeysArray_; } if (hasSlowProperties_) { return JSHandle(thread_, JSTaggedValue::Undefined()); } ASSERT(!receiver_->IsInSharedHeap()); ASSERT(!onlyHasSimpleProperties_); if (tryPrototypeChainEnumCache_) { // This handles the case where prototype chain and self have no elements or slow keys. fastKeysArray_.Update(GetKeysFastWithPrototypeChainEnumCache()); JSHandle::Cast(enumCache_)->SetEnumCacheAll(thread_, fastKeysArray_.GetTaggedValue()); } else { fastKeysArray_.Update(GetKeysFastWithoutCache()); } JSObject::SetEnumCacheKind(thread_, JSHandle::Cast(enumCache_), EnumCacheKind::PROTOCHAIN); return fastKeysArray_; } JSHandle PropertyAccessor::GetKeysSlow() { std::vector> remainings; std::vector> visited; JSMutableHandle current(thread_, receiver_); while (current->IsHeapObject()) { PushRemainingKeys(JSHandle(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(thread_, slowKeysArray_.GetTaggedValue()); } void PropertyAccessor::PushRemainingKeys(JSHandle object, std::vector> &remainings) { JSMutableHandle value(thread_, JSTaggedValue::Undefined()); uint32_t remainingIndex = 0; if (object->IsJSProxy()) { JSHandle proxyArr = JSProxy::OwnPropertyKeys(thread_, JSHandle(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(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 array = JSTaggedValue::GetOwnEnumPropertyKeys(thread_, JSHandle(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> &remainings, const std::vector> &visited) { uint32_t arraySize = keyLength_; JSHandle keyArray = thread_->GetEcmaVM()->GetFactory()->NewTaggedArray(arraySize); JSMutableHandle remaining(thread_, JSTaggedValue::Undefined()); JSMutableHandle keyHandle(thread_, JSTaggedValue::Undefined()); JSMutableHandle 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()); } } // namespace panda::ecmascript