1/*
2 * Copyright (c) 2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include "ecmascript/property_accessor.h"
17
18#include "ecmascript/js_object-inl.h"
19
20namespace panda::ecmascript {
21PropertyAccessor::PropertyAccessor(JSThread *thread, JSHandle<JSTaggedValue> object)
22    : thread_(thread),
23      receiver_(thread, object.GetTaggedValue()),
24      fastKeysArray_(thread, JSTaggedValue::Undefined()),
25      cachedHclass_(thread, JSTaggedValue::Undefined()),
26      keyLength_(0),
27      shadowKeyLength_(0),
28      onlyHasSimpleProperties_(true),
29      canUseEnumCache_(true),
30      hasSlowProperties_(false),
31      slowKeysArray_(thread, JSTaggedValue::Undefined()),
32      acutalKeyLength_(0)
33{
34    PreLoad();
35}
36
37void PropertyAccessor::PreLoad()
38{
39    if (receiver_->IsSlowKeysObject()) {
40        hasSlowProperties_ = true;
41        return;
42    }
43    JSHandle<JSObject> receiverObj(receiver_);
44    JSHClass *jshclass = receiverObj->GetJSHClass();
45    if (jshclass->IsDictionaryMode()) {
46        onlyHasSimpleProperties_ = false;
47        canUseEnumCache_ = false;
48    }
49    uint32_t numOfElements = receiverObj->GetNumberOfElements();
50    if (numOfElements > 0) {
51        AccumulateKeyLength(numOfElements);
52        onlyHasSimpleProperties_ = false;
53        canUseEnumCache_ = false;
54    }
55    std::pair<uint32_t, uint32_t> numOfKeys = receiverObj->GetNumberOfEnumKeys();
56    uint32_t numOfEnumKeys = numOfKeys.first;
57    if (numOfEnumKeys > 0) {
58        AccumulateKeyLength(numOfEnumKeys);
59    }
60    uint32_t numOfShadowKeys = numOfKeys.second;
61    if (numOfShadowKeys > 0) {
62        AccumulateShadowKeyLength(numOfShadowKeys);
63    }
64
65    CollectPrototypeInfo();
66    if (hasSlowProperties_ || !onlyHasSimpleProperties_) {
67        return;
68    }
69    ASSERT(canUseEnumCache_);
70    // fast path
71    InitSimplePropertiesEnumCache();
72}
73
74void PropertyAccessor::CollectPrototypeInfo()
75{
76    DISALLOW_GARBAGE_COLLECTION;
77    JSTaggedValue current = JSTaggedValue::GetPrototype(thread_, receiver_);
78    RETURN_IF_ABRUPT_COMPLETION(thread_);
79    while (current.IsHeapObject()) {
80        if (current.IsSlowKeysObject()) {
81            hasSlowProperties_ = true;
82            break;
83        }
84        JSObject *currentObj = JSObject::Cast(current.GetTaggedObject());
85        uint32_t numOfCurrentElements = currentObj->GetNumberOfElements();
86        if (numOfCurrentElements > 0) {
87            AccumulateKeyLength(numOfCurrentElements);
88            onlyHasSimpleProperties_ = false;
89            canUseEnumCache_ = false;
90        }
91        std::pair<uint32_t, uint32_t> numOfKeys = currentObj->GetNumberOfEnumKeys();
92        uint32_t numOfEnumKeys = numOfKeys.first;
93        if (numOfEnumKeys > 0) {
94            AccumulateKeyLength(numOfEnumKeys);
95            onlyHasSimpleProperties_ = false;
96        }
97        uint32_t numOfShadowKeys = numOfKeys.second;
98        if (numOfShadowKeys > 0) {
99            AccumulateShadowKeyLength(numOfShadowKeys);
100        }
101        JSHClass *jshclass = currentObj->GetJSHClass();
102        if (jshclass->IsDictionaryMode()) {
103            onlyHasSimpleProperties_ = false;
104            canUseEnumCache_ = false;
105        }
106        if (onlyHasSimpleProperties_) {
107            // a fast path to check simple enum cache
108            jshclass->SetEnumCache(thread_, JSTaggedValue::Undefined());
109        }
110        current = JSObject::GetPrototype(current);
111    }
112}
113
114void PropertyAccessor::InitSimplePropertiesEnumCache()
115{
116    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
117    JSHandle<JSObject> receiverObj(receiver_);
118    ASSERT(receiverObj->GetNumberOfElements() == 0);
119    ASSERT(!receiver_->IsInSharedHeap());
120    JSMutableHandle<TaggedArray> keyArray(thread_, JSTaggedValue::Undefined());
121    if (keyLength_ == 0) {
122        keyArray.Update(factory->EmptyArray());
123        SetActualKeyLength(0);
124    } else {
125        uint32_t arraySize = keyLength_ + EnumCache::ENUM_CACHE_HEADER_SIZE;
126        JSHandle<TaggedArray> newArray = thread_->GetEcmaVM()->GetFactory()->NewTaggedArray(arraySize);
127        uint32_t length = JSObject::GetAllEnumKeys(thread_, receiverObj, EnumCache::ENUM_CACHE_HEADER_SIZE, newArray);
128        SetActualKeyLength(length);
129        JSObject::SetEnumCacheKind(thread_, *newArray, EnumCacheKind::SIMPLE);
130        keyArray.Update(newArray);
131    }
132    JSObject::ClearHasDeleteProperty(receiver_);
133    JSHClass *jsHclass = receiverObj->GetJSHClass();
134    jsHclass->SetEnumCache(thread_, keyArray.GetTaggedValue());
135    fastKeysArray_.Update(keyArray.GetTaggedValue());
136    cachedHclass_.Update(JSTaggedValue(jsHclass));
137}
138
139inline void PropertyAccessor::AccumulateKeyLength(uint32_t length)
140{
141    keyLength_ += length;
142}
143
144inline void PropertyAccessor::AccumulateShadowKeyLength(uint32_t length)
145{
146    shadowKeyLength_ += length;
147}
148
149JSHandle<JSTaggedValue> PropertyAccessor::GetCachedHclass()
150{
151    return cachedHclass_;
152}
153
154uint32_t PropertyAccessor::GetActualKeyLength() const
155{
156    return acutalKeyLength_;
157}
158
159inline void PropertyAccessor::SetActualKeyLength(uint32_t length)
160{
161    acutalKeyLength_ = length;
162}
163
164void PropertyAccessor::AddKeysEndIfNeeded(JSHandle<TaggedArray> keys)
165{
166    // when has duplicated keys
167    if (acutalKeyLength_ < keyLength_) {
168        keys->Set(thread_, acutalKeyLength_ + EnumCache::ENUM_CACHE_HEADER_SIZE, JSTaggedValue::Undefined());
169    }
170}
171
172void PropertyAccessor::TryInitEnumCacheWithProtoChainInfo()
173{
174#if ECMASCRIPT_ENABLE_IC
175    if (!canUseEnumCache_) {
176        JSObject::SetEnumCacheKind(thread_, TaggedArray::Cast(fastKeysArray_->GetTaggedObject()), EnumCacheKind::NONE);
177        return;
178    }
179    ASSERT(!receiver_->IsInSharedHeap());
180    ASSERT(!onlyHasSimpleProperties_);
181    JSHandle<JSObject> receiverObj(receiver_);
182    JSHandle<JSHClass> jsHclass(thread_, receiverObj->GetJSHClass());
183    jsHclass->SetEnumCache(thread_, fastKeysArray_.GetTaggedValue());
184    JSObject::SetEnumCacheKind(
185        thread_, TaggedArray::Cast(fastKeysArray_->GetTaggedObject()), EnumCacheKind::PROTOCHAIN);
186    cachedHclass_.Update(jsHclass);
187    JSHClass::EnableProtoChangeMarker(thread_, jsHclass);
188#endif
189}
190
191JSHandle<JSTaggedValue> PropertyAccessor::GetKeysFast()
192{
193    if (!fastKeysArray_->IsUndefined()) {
194        AddKeysEndIfNeeded(JSHandle<TaggedArray>(thread_, fastKeysArray_.GetTaggedValue()));
195        return fastKeysArray_;
196    }
197    if (hasSlowProperties_) {
198        return JSHandle<JSTaggedValue>(thread_, JSTaggedValue::Undefined());
199    }
200    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
201    uint32_t arraySize = keyLength_ + EnumCache::ENUM_CACHE_HEADER_SIZE;
202    JSHandle<TaggedArray> keyArray = factory->NewTaggedArray(arraySize);
203    JSHandle<TaggedQueue> shadowQueue = factory->NewTaggedQueue(shadowKeyLength_ + 1);
204    uint32_t keysNum = EnumCache::ENUM_CACHE_HEADER_SIZE;
205    JSMutableHandle<JSTaggedValue> current(thread_, receiver_);
206    while (current->IsHeapObject()) {
207        JSObject::AppendOwnEnumPropertyKeys(thread_, JSHandle<JSObject>(current), keyArray, &keysNum, shadowQueue);
208        RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
209        JSObject::ClearHasDeleteProperty(current);
210        current.Update(JSObject::GetPrototype(current.GetTaggedValue()));
211    }
212    SetActualKeyLength(keysNum - EnumCache::ENUM_CACHE_HEADER_SIZE);
213    AddKeysEndIfNeeded(keyArray);
214    fastKeysArray_.Update(keyArray.GetTaggedValue());
215    TryInitEnumCacheWithProtoChainInfo();
216    return fastKeysArray_;
217}
218
219JSHandle<JSTaggedValue> PropertyAccessor::GetKeysSlow()
220{
221    std::vector<JSHandle<TaggedArray>> remainings;
222    std::vector<JSHandle<JSTaggedValue>> visited;
223    JSMutableHandle<JSTaggedValue> current(thread_, receiver_);
224    while (current->IsHeapObject()) {
225        PushRemainingKeys(JSHandle<JSObject>(current), remainings);
226        RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
227        JSObject::ClearHasDeleteProperty(current);
228        visited.emplace_back(thread_, current.GetTaggedValue());
229        current.Update(JSTaggedValue::GetPrototype(thread_, current));
230        RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
231    }
232    MergeRemainings(remainings, visited);
233    return JSHandle<JSTaggedValue>(thread_, slowKeysArray_.GetTaggedValue());
234}
235
236void PropertyAccessor::PushRemainingKeys(JSHandle<JSObject> object, std::vector<JSHandle<TaggedArray>> &remainings)
237{
238    JSMutableHandle<JSTaggedValue> value(thread_, JSTaggedValue::Undefined());
239    uint32_t remainingIndex = 0;
240    if (object->IsJSProxy()) {
241        JSHandle<TaggedArray> proxyArr = JSProxy::OwnPropertyKeys(thread_, JSHandle<JSProxy>(object));
242        RETURN_IF_ABRUPT_COMPLETION(thread_);
243        uint32_t length = proxyArr->GetLength();
244        for (uint32_t i = 0; i < length; i++) {
245            value.Update(proxyArr->Get(i));
246            PropertyDescriptor desc(thread_);
247            JSProxy::GetOwnProperty(thread_, JSHandle<JSProxy>(object), value, desc);
248            RETURN_IF_ABRUPT_COMPLETION(thread_);
249            if (!desc.IsEnumerable()) {
250                proxyArr->Set(thread_, i, JSTaggedValue::Hole());
251            } else {
252                remainingIndex++;
253            }
254        }
255        remainings.push_back(proxyArr);
256        AccumulateKeyLength(remainingIndex);
257    } else {
258        JSHandle<TaggedArray> array = JSTaggedValue::GetOwnEnumPropertyKeys(thread_, JSHandle<JSTaggedValue>(object));
259        uint32_t length = array->GetLength();
260        for (uint32_t i = 0; i < length; i++) {
261            value.Update(array->Get(i));
262            if (!value->IsString()) {
263                array->Set(thread_, i, JSTaggedValue::Hole());
264            } else {
265                remainingIndex++;
266            }
267        }
268        remainings.push_back(array);
269        AccumulateKeyLength(remainingIndex);
270    }
271}
272
273void PropertyAccessor::MergeRemainings(const std::vector<JSHandle<TaggedArray>> &remainings,
274                                       const std::vector<JSHandle<JSTaggedValue>> &visited)
275{
276    uint32_t arraySize = keyLength_ + EnumCache::ENUM_CACHE_HEADER_SIZE;
277    JSHandle<TaggedArray> keyArray = thread_->GetEcmaVM()->GetFactory()->NewTaggedArray(arraySize);
278
279    JSMutableHandle<TaggedArray> remaining(thread_, JSTaggedValue::Undefined());
280    JSMutableHandle<JSTaggedValue> keyHandle(thread_, JSTaggedValue::Undefined());
281    JSMutableHandle<JSTaggedValue> objHandle(thread_, JSTaggedValue::Undefined());
282    uint32_t index = EnumCache::ENUM_CACHE_HEADER_SIZE;
283    uint32_t numberOfRemaining = remainings.size();
284    for (uint32_t i = 0; i < numberOfRemaining; i++) {
285        remaining.Update(remainings[i]);
286        uint32_t remainingSize = remaining->GetLength();
287        for (uint32_t j = 0; j < remainingSize; j++) {
288            keyHandle.Update(remaining->Get(thread_, j));
289            if (keyHandle->IsHole()) {
290                continue;
291            }
292            bool has = false;
293            for (uint32_t k = 0; k < i; k++) {
294                objHandle.Update(visited[k]);
295                PropertyDescriptor desc(thread_);
296                has = JSTaggedValue::GetOwnProperty(thread_, objHandle, keyHandle, desc);
297                RETURN_IF_ABRUPT_COMPLETION(thread_);
298                if (has) {
299                    break;
300                }
301            }
302            if (!has) {
303                keyArray->Set(thread_, index, keyHandle);
304                index++;
305            }
306        }
307    }
308    SetActualKeyLength(index - EnumCache::ENUM_CACHE_HEADER_SIZE);
309    AddKeysEndIfNeeded(keyArray);
310    slowKeysArray_.Update(keyArray.GetTaggedValue());
311    JSObject::SetEnumCacheKind(thread_, *keyArray, EnumCacheKind::NONE);
312}
313}  // namespace panda::ecmascript
314