* Copyright (c) 2021 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/js_for_in_iterator.h"
#include "ecmascript/base/builtins_base.h"
#include "ecmascript/enum_cache.h"
#include "ecmascript/ic/proto_change_details.h"
#include "ecmascript/js_object-inl.h"
#include "ecmascript/js_tagged_value_wrapper.h"
namespace panda::ecmascript {
using BuiltinsBase = base::BuiltinsBase;
bool JSForInIterator::IsEnumCacheValid(const JSThread *thread, JSTaggedValue receiver, JSTaggedValue cachedHClass,
EnumCacheKind kind)
{
DISALLOW_GARBAGE_COLLECTION;
JSHClass *hclass = receiver.GetTaggedObject()->GetClass();
if (JSTaggedValue(hclass) != cachedHClass) {
return false;
}
if (kind == EnumCacheKind::SIMPLE) {
return true;
}
if (kind == EnumCacheKind::NONE) {
return false;
}
ASSERT(kind == EnumCacheKind::PROTOCHAIN);
JSTaggedValue proto = hclass->GetPrototype(thread);
if (!proto.IsHeapObject()) {
return true;
}
JSTaggedValue enumCache = proto.GetTaggedObject()->GetClass()->GetEnumCache(thread);
if (enumCache.IsEnumCache()) {
JSTaggedValue enumCacheAll = EnumCache::Cast(enumCache.GetTaggedObject())->GetEnumCacheAll(thread);
if (enumCacheAll != JSTaggedValue::Null()) {
return true;
}
}
return false;
}
bool JSForInIterator::NeedCheckProperty(const JSThread *thread, JSTaggedValue receiver)
{
DISALLOW_GARBAGE_COLLECTION;
JSTaggedValue current = receiver;
while (current.IsHeapObject()) {
if (!current.IsJSObject() || current.GetTaggedObject()->GetClass()->HasDeleteProperty()) {
return true;
}
current = JSObject::GetPrototype(thread, current);
}
return false;
}
bool JSForInIterator::HasProperty(JSThread *thread, JSHandle<JSTaggedValue> receiver, JSHandle<JSTaggedValue> key)
{
JSMutableHandle<JSTaggedValue> current(thread, receiver.GetTaggedValue());
while (current->IsHeapObject()) {
PropertyDescriptor desc(thread);
bool has = JSTaggedValue::GetOwnProperty(thread, current, key, desc);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
if (has && desc.IsEnumerable()) {
return true;
}
current.Update(JSTaggedValue::GetPrototype(thread, current));
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
}
return false;
}
JSTaggedValue JSForInIterator::NextInternal(JSThread *thread, const JSHandle<JSForInIterator> &it)
{
JSTaggedValue receiver = it->GetObject(thread);
if (receiver.IsJSArray()) {
return NextInternalSlowpath(thread, it);
}
uint32_t length = it->GetLength();
uint32_t index = it->GetIndex();
if (index >= length) {
return JSTaggedValue::Undefined();
}
JSTaggedValue taggedKeys = it->GetKeys(thread);
EnumCacheKind kind = static_cast<EnumCacheKind>(it->GetCacheKind());
TaggedArray *keys = TaggedArray::Cast(taggedKeys.GetTaggedObject());
if (IsEnumCacheValid(thread, receiver, it->GetCachedHClass(thread), kind)) {
JSTaggedValue key = keys->Get(thread, index);
index++;
it->SetIndex(index);
return key;
}
if (!NeedCheckProperty(thread, receiver)) {
JSTaggedValue key = keys->Get(thread, index);
index++;
it->SetIndex(index);
return key;
}
return NextInternalSlowpath(thread, it);
}
JSTaggedValue JSForInIterator::NextInternalSlowpath(JSThread *thread, const JSHandle<JSForInIterator> &it)
{
uint32_t length = it->GetLength();
uint32_t index = it->GetIndex();
if (index >= length) {
return JSTaggedValue::Undefined();
}
JSHandle<TaggedArray> keysHandle(thread, it->GetKeys(thread));
JSHandle<JSTaggedValue> receiverHandle(thread, it->GetObject(thread));
JSMutableHandle<JSTaggedValue> keyHandle(thread, JSTaggedValue::Undefined());
bool has = false;
while (index < length) {
keyHandle.Update(keysHandle->Get(thread, index));
if (keyHandle->IsUndefined()) {
has = false;
break;
}
has = HasProperty(thread, receiverHandle, keyHandle);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (has) {
break;
}
index++;
}
if (!has) {
it->SetIndex(index);
return JSTaggedValue::Undefined();
}
JSTaggedValue key = keysHandle->Get(thread, index);
index++;
it->SetIndex(index);
return key;
}
JSTaggedValue JSForInIterator::Next(EcmaRuntimeCallInfo *msg)
{
ASSERT(msg);
JSThread *thread = msg->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSForInIterator> it(BuiltinsBase::GetThis(msg));
ASSERT(JSHandle<JSTaggedValue>(it)->IsForinIterator());
JSTaggedValue res = NextInternal(thread, it);
return res;
}
}