escargot/src/runtime/ArrayObject.cpp
Saebom Kim 6d4ed41488 1. When 'writable' property of array length turned into 'false', convert it into NonFastMode
2. If RegExp's source needs escapeSlash process, it should count the number of leading backSlashs

Signed-off-by: Saebom Kim sae-bom.kim@samsung.com
2017-04-05 19:28:08 +09:00

406 lines
15 KiB
C++

/*
* Copyright (c) 2016-present Samsung Electronics 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 "Escargot.h"
#include "ArrayObject.h"
#include "Context.h"
#include "VMInstance.h"
namespace Escargot {
size_t g_arrayObjectTag;
ArrayObject::ArrayObject(ExecutionState& state)
: Object(state, ESCARGOT_OBJECT_BUILTIN_PROPERTY_NUMBER + 1, true)
{
m_structure = state.context()->defaultStructureForArrayObject();
m_values[ESCARGOT_OBJECT_BUILTIN_PROPERTY_NUMBER] = Value(0);
setPrototype(state, state.context()->globalObject()->arrayPrototype());
if (UNLIKELY(state.context()->vmInstance()->didSomePrototypeObjectDefineIndexedProperty())) {
ensureObjectRareData()->m_isFastModeArrayObject = false;
}
}
ObjectGetResult ArrayObject::getOwnProperty(ExecutionState& state, const ObjectPropertyName& P) ESCARGOT_OBJECT_SUBCLASS_MUST_REDEFINE
{
ObjectGetResult v = getFastModeValue(state, P);
if (LIKELY(v.hasValue())) {
return v;
} else {
return Object::getOwnProperty(state, P);
}
}
bool ArrayObject::defineOwnProperty(ExecutionState& state, const ObjectPropertyName& P, const ObjectPropertyDescriptor& desc) ESCARGOT_OBJECT_SUBCLASS_MUST_REDEFINE
{
if (LIKELY(setFastModeValue(state, P, desc))) {
return true;
}
uint64_t idx;
if (LIKELY(P.isUIntType())) {
idx = P.uintValue();
} else {
idx = P.string(state)->tryToUseAsArrayIndex();
}
auto oldLenDesc = structure()->readProperty(state, (size_t)0);
uint32_t oldLen = getArrayLength(state);
if (idx != Value::InvalidArrayIndexValue) {
if ((idx >= oldLen) && !oldLenDesc.m_descriptor.isWritable())
return false;
bool succeeded = Object::defineOwnProperty(state, P, desc);
if (!succeeded)
return false;
if (idx >= oldLen && ((idx + 1) <= Value::InvalidArrayIndexValue)) {
return setArrayLength(state, idx + 1);
}
return true;
} else if (P.string(state)->equals(state.context()->staticStrings().length.string())) {
// See 3.a ~ 3.n on http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.5.1
if (desc.isValuePresent()) {
ObjectPropertyDescriptor newLenDesc(desc);
uint32_t newLen = desc.value().toLength(state);
bool newWritable = false;
if (newLen != desc.value().toNumber(state)) {
ErrorObject::throwBuiltinError(state, ErrorObject::RangeError, errorMessage_GlobalObject_InvalidArrayLength);
}
newLenDesc.setValue(Value(newLen));
if (newLen >= oldLen) {
return Object::defineOwnProperty(state, P, newLenDesc);
}
if (!(oldLenDesc.m_descriptor.isWritable())) {
return false;
}
if (!(newLenDesc.isWritablePresent()) || newLenDesc.isWritable()) {
newWritable = true;
} else {
// In this case, even if an element of this array cannot be deleted,
// it should not throw any error.
// Object::defineOwnProperty() below can make call to ArrayObject::setArrayLength()
// so, we set this variable to give the information to ArrayObject::setArrayLength()
// not to throw error.
ensureObjectRareData()->m_isInArrayObjectDefineOwnProperty = true;
newWritable = false;
newLenDesc.setWritable(true);
}
if (!(Object::defineOwnProperty(state, P, newLenDesc))) {
if (isInArrayObjectDefineOwnProperty()) {
ASSERT(rareData());
rareData()->m_isInArrayObjectDefineOwnProperty = false;
}
return false;
}
while (newLen < oldLen) {
oldLen--;
bool deleteSucceeded = Object::deleteOwnProperty(state, ObjectPropertyName(state, Value(oldLen).toString(state)));
if (!deleteSucceeded) {
newLenDesc.setValue(Value(oldLen + 1));
if (!newWritable) {
newLenDesc.setWritable(false);
}
Object::defineOwnProperty(state, P, newLenDesc);
if (isInArrayObjectDefineOwnProperty()) {
ASSERT(rareData());
rareData()->m_isInArrayObjectDefineOwnProperty = false;
}
return false;
}
}
if (!newWritable) {
ObjectPropertyDescriptor tmpDesc(ObjectPropertyDescriptor::NonWritablePresent, true);
Object::defineOwnProperty(state, P, tmpDesc);
}
if (isInArrayObjectDefineOwnProperty()) {
ASSERT(rareData());
rareData()->m_isInArrayObjectDefineOwnProperty = false;
}
return true;
} else {
return Object::defineOwnProperty(state, P, desc);
}
}
return Object::defineOwnProperty(state, P, desc);
}
bool ArrayObject::deleteOwnProperty(ExecutionState& state, const ObjectPropertyName& P) ESCARGOT_OBJECT_SUBCLASS_MUST_REDEFINE
{
if (LIKELY(isFastModeArray())) {
uint64_t idx;
if (LIKELY(P.isUIntType())) {
idx = P.uintValue();
} else {
idx = P.string(state)->tryToUseAsArrayIndex();
}
if (LIKELY(idx != Value::InvalidArrayIndexValue)) {
uint64_t len = getArrayLength(state);
if (idx < len) {
m_fastModeData[idx] = Value(Value::EmptyValue);
return true;
}
}
}
return Object::deleteOwnProperty(state, P);
}
void ArrayObject::enumeration(ExecutionState& state, bool (*callback)(ExecutionState& state, Object* self, const ObjectPropertyName&, const ObjectStructurePropertyDescriptor& desc, void* data), void* data) ESCARGOT_OBJECT_SUBCLASS_MUST_REDEFINE
{
if (LIKELY(isFastModeArray())) {
size_t len = getArrayLength(state);
for (size_t i = 0; i < len; i++) {
ASSERT(isFastModeArray());
if (m_fastModeData[i].isEmpty())
continue;
if (!callback(state, this, ObjectPropertyName(state, Value(i)), ObjectStructurePropertyDescriptor::createDataDescriptor(ObjectStructurePropertyDescriptor::AllPresent), data)) {
return;
}
}
}
Object::enumeration(state, callback, data);
}
void ArrayObject::sort(ExecutionState& state, std::function<bool(const Value& a, const Value& b)> comp)
{
if (isFastModeArray()) {
if (getArrayLength(state)) {
std::vector<Value, GCUtil::gc_malloc_ignore_off_page_allocator<Value>> values(&m_fastModeData[0], m_fastModeData.data() + getArrayLength(state));
std::sort(values.begin(), values.end(), comp);
for (size_t i = 0; i < values.size(); i++) {
m_fastModeData[i] = values[i];
}
}
return;
}
Object::sort(state, comp);
}
void* ArrayObject::operator new(size_t size)
{
return CustomAllocator<ArrayObject>().allocate(1);
}
void ArrayObject::iterateArrays(ExecutionState& state, HeapObjectIteratorCallback callback)
{
iterateSpecificKindOfObject(state, HeapObjectKind::ArrayObjectKind, callback);
}
void ArrayObject::convertIntoNonFastMode(ExecutionState& state)
{
if (!isFastModeArray())
return;
if (!structure()->isStructureWithFastAccess()) {
m_structure = structure()->convertToWithFastAccess(state);
}
ensureObjectRareData()->m_isFastModeArrayObject = false;
auto length = getArrayLength(state);
for (size_t i = 0; i < length; i++) {
if (!m_fastModeData[i].isEmpty()) {
defineOwnPropertyThrowsExceptionWhenStrictMode(state, ObjectPropertyName(state, Value(i)), ObjectPropertyDescriptor(m_fastModeData[i], ObjectPropertyDescriptor::AllPresent));
}
}
m_fastModeData.clear();
}
bool ArrayObject::setArrayLength(ExecutionState& state, const uint64_t& newLength)
{
ASSERT(isExtensible() || newLength <= getArrayLength(state));
if (UNLIKELY(isFastModeArray() && (newLength > ESCARGOT_ARRAY_NON_FASTMODE_MIN_SIZE))) {
uint32_t orgLength = getArrayLength(state);
if (newLength > orgLength) {
if ((newLength - orgLength > ESCARGOT_ARRAY_NON_FASTMODE_START_MIN_GAP)) {
convertIntoNonFastMode(state);
}
}
}
if (LIKELY(isFastModeArray())) {
auto oldSize = getArrayLength(state);
auto oldLenDesc = structure()->readProperty(state, (size_t)0);
m_values[ESCARGOT_OBJECT_BUILTIN_PROPERTY_NUMBER] = Value(newLength);
m_fastModeData.resize(oldSize, newLength, Value(Value::EmptyValue));
if (UNLIKELY(!oldLenDesc.m_descriptor.isWritable())) {
convertIntoNonFastMode(state);
}
return true;
} else {
if (!isInArrayObjectDefineOwnProperty()) {
auto oldLenDesc = structure()->readProperty(state, (size_t)0);
int64_t oldLen = length(state);
int64_t newLen = newLength;
while (newLen < oldLen) {
oldLen--;
ObjectPropertyName key(state, Value(oldLen));
if (!getOwnProperty(state, key).hasValue()) {
double result;
Object::nextIndexBackward(state, this, oldLen, -1, false, result);
oldLen = result;
if (oldLen < newLen) {
break;
}
key = ObjectPropertyName(state, Value(oldLen));
}
bool deleteSucceeded = deleteOwnProperty(state, key);
if (!deleteSucceeded) {
m_values[ESCARGOT_OBJECT_BUILTIN_PROPERTY_NUMBER] = Value(oldLen + 1);
return false;
}
}
}
m_values[ESCARGOT_OBJECT_BUILTIN_PROPERTY_NUMBER] = Value(newLength);
return true;
}
}
ObjectGetResult ArrayObject::getFastModeValue(ExecutionState& state, const ObjectPropertyName& P)
{
if (LIKELY(isFastModeArray())) {
uint64_t idx;
if (LIKELY(P.isUIntType())) {
idx = P.uintValue();
} else {
idx = P.string(state)->tryToUseAsArrayIndex();
}
if (LIKELY(idx != Value::InvalidArrayIndexValue)) {
if (LIKELY(idx < getArrayLength(state))) {
Value v = m_fastModeData[idx];
if (LIKELY(!v.isEmpty())) {
return ObjectGetResult(v, true, true, true);
}
return ObjectGetResult();
}
}
}
return ObjectGetResult();
}
bool ArrayObject::setFastModeValue(ExecutionState& state, const ObjectPropertyName& P, const ObjectPropertyDescriptor& desc)
{
if (LIKELY(isFastModeArray())) {
uint64_t idx;
if (LIKELY(P.isUIntType())) {
idx = P.uintValue();
} else {
idx = P.string(state)->tryToUseAsArrayIndex();
}
if (LIKELY(idx != Value::InvalidArrayIndexValue)) {
uint32_t len = getArrayLength(state);
if (len > idx && !m_fastModeData[idx].isEmpty()) {
// Non-empty slot of fast-mode array always has {writable:true, enumerable:true, configurable:true}.
// So, when new desciptor is not present, keep {w:true, e:true, c:true}
if (UNLIKELY(!(desc.isValuePresentAlone() || desc.isDataWritableEnumerableConfigurable()))) {
convertIntoNonFastMode(state);
return false;
}
} else if (UNLIKELY(!desc.isDataWritableEnumerableConfigurable())) {
// In case of empty slot property or over-lengthed property,
// when new desciptor is not present, keep {w:false, e:false, c:false}
convertIntoNonFastMode(state);
return false;
}
if (!desc.isValuePresent()) {
convertIntoNonFastMode(state);
return false;
}
if (UNLIKELY(len <= idx)) {
if (UNLIKELY(!isExtensible())) {
return false;
}
if (UNLIKELY(!setArrayLength(state, idx + 1)) || UNLIKELY(!isFastModeArray())) {
return false;
}
}
m_fastModeData[idx] = desc.value();
return true;
}
}
return false;
}
ObjectGetResult ArrayObject::getIndexedProperty(ExecutionState& state, const Value& property)
{
if (LIKELY(isFastModeArray())) {
uint64_t idx;
if (LIKELY(property.isUInt32())) {
idx = property.asUInt32();
} else {
idx = property.toString(state)->tryToUseAsArrayIndex();
}
if (LIKELY(idx != Value::InvalidArrayIndexValue)) {
if (LIKELY(idx < getArrayLength(state))) {
Value v = m_fastModeData[idx];
if (LIKELY(!v.isEmpty())) {
return ObjectGetResult(v, true, true, true);
}
return get(state, ObjectPropertyName(state, property));
}
}
}
return get(state, ObjectPropertyName(state, property));
}
bool ArrayObject::setIndexedProperty(ExecutionState& state, const Value& property, const Value& value)
{
if (LIKELY(isFastModeArray())) {
uint64_t idx;
if (LIKELY(property.isUInt32())) {
idx = property.asUInt32();
} else {
idx = property.toString(state)->tryToUseAsArrayIndex();
}
if (LIKELY(idx != Value::InvalidArrayIndexValue)) {
uint32_t len = getArrayLength(state);
if (UNLIKELY(len <= idx)) {
if (UNLIKELY(!isExtensible())) {
return false;
}
if (UNLIKELY(!setArrayLength(state, idx + 1)) || UNLIKELY(!isFastModeArray())) {
return set(state, ObjectPropertyName(state, property), value, this);
}
}
m_fastModeData[idx] = value;
return true;
}
}
return set(state, ObjectPropertyName(state, property), value, this);
}
}