diff --git a/src/api/EscargotPublic.cpp b/src/api/EscargotPublic.cpp index 15d955c95..c4d1fae67 100644 --- a/src/api/EscargotPublic.cpp +++ b/src/api/EscargotPublic.cpp @@ -1265,7 +1265,13 @@ StringRef* Evaluator::EvaluatorResult::resultOrErrorToString(ContextRef* ctx) co if (isSuccessful()) { return result->toStringWithoutException(ctx); } else { - return ((ValueRef*)error.value())->toStringWithoutException(ctx); + // Check if error value is valid before dereferencing + // In some edge cases (e.g., nested eval throw with finally allocation), + // the error value might be invalid or null + if (error.hasValue()) { + return ((ValueRef*)error.value())->toStringWithoutException(ctx); + } + return StringRef::emptyString(); } } diff --git a/src/builtins/BuiltinTypedArray.cpp b/src/builtins/BuiltinTypedArray.cpp index ae6e112e5..f39819f95 100644 --- a/src/builtins/BuiltinTypedArray.cpp +++ b/src/builtins/BuiltinTypedArray.cpp @@ -485,7 +485,9 @@ static Value builtinTypedArrayCopyWithin(ExecutionState& state, Value thisValue, // Set len to TypedArrayLength(taRecord). len = O->arrayLength(); // Set count to min(count, len - startIndex, len - targetIndex). - count = std::min(std::min(count, len - startIndex), len - targetIndex); + // NOTE: After buffer resize during argument coercion, len - startIndex or len - targetIndex can be negative. + // We must clamp count to non-negative to prevent integer underflow when casting to size_t. + count = std::max(0.0, std::min(std::min(count, len - startIndex), len - targetIndex)); // Let typedArrayName be the String value of O.[[TypedArrayName]]. // Let elementSize be the Number value of the Element Size value specified in Table 59 for typedArrayName. size_t elementSize = O->elementSize(); diff --git a/src/interpreter/ByteCode.h b/src/interpreter/ByteCode.h index ced991ccf..1a19a2da3 100644 --- a/src/interpreter/ByteCode.h +++ b/src/interpreter/ByteCode.h @@ -3308,7 +3308,7 @@ class ByteCodeBlock : public gc { context->m_locData->push_back(std::make_pair(start, idx)); } -#ifndef NDEBUG +#if !defined(NDEBUG) && defined(ESCARGOT_DEBUGGER) const auto loc = computeNodeLOC(m_codeBlock->src(), m_codeBlock->functionStart(), idx); ByteCodeLOC* bytecodeLoc = &reinterpret_cast(first)->m_loc; bytecodeLoc->index = loc.index; diff --git a/src/interpreter/ByteCodeInterpreter.cpp b/src/interpreter/ByteCodeInterpreter.cpp index 22e4ad14e..ddc690596 100644 --- a/src/interpreter/ByteCodeInterpreter.cpp +++ b/src/interpreter/ByteCodeInterpreter.cpp @@ -4602,7 +4602,34 @@ NEVER_INLINE void InterpreterSlowPath::arrayDefineOwnPropertyBySpreadElementOper size_t newLength = baseIndex + elementLength; arr->setArrayLength(state, newLength); - ASSERT(arr->isFastModeArray()); + + // Check if the array is still in fast mode after setArrayLength + // setArrayLength can convert the array to non-fast mode when length exceeds thresholds + if (UNLIKELY(!arr->isFastModeArray())) { + // Array was converted to non-fast mode, use slow path + size_t elementIndex = 0; + for (size_t i = 0; i < code->m_count; i++) { + if (LIKELY(code->m_loadRegisterIndexs[i] != REGISTER_LIMIT)) { + Value element = registerFile[code->m_loadRegisterIndexs[i]]; + if (element.isObject() && element.asObject()->isSpreadArray()) { + ArrayObject* spreadArray = element.asObject()->asArrayObject(); + ASSERT(spreadArray->isFastModeArray()); + Value spreadElement; + for (size_t spreadIndex = 0; spreadIndex < spreadArray->arrayLength(state); spreadIndex++) { + spreadElement = spreadArray->m_fastModeData[spreadIndex]; + arr->defineOwnProperty(state, ObjectPropertyName(state, baseIndex + elementIndex), ObjectPropertyDescriptor(spreadElement, ObjectPropertyDescriptor::AllPresent)); + elementIndex++; + } + } else { + arr->defineOwnProperty(state, ObjectPropertyName(state, baseIndex + elementIndex), ObjectPropertyDescriptor(element, ObjectPropertyDescriptor::AllPresent)); + elementIndex++; + } + } else { + elementIndex++; + } + } + return; + } size_t elementIndex = 0; for (size_t i = 0; i < code->m_count; i++) { diff --git a/src/parser/CodeBlock.h b/src/parser/CodeBlock.h index c3d1ace20..1428a6b1f 100644 --- a/src/parser/CodeBlock.h +++ b/src/parser/CodeBlock.h @@ -35,7 +35,7 @@ struct ASTScopeContext; struct ByteCodeGenerateContext; typedef HashMap, std::equal_to, - GCUtil::gc_malloc_allocator>> + GCUtil::gc_malloc_atomic_allocator>> FunctionContextVarMap; // length of argv is same with NativeFunctionInfo.m_argumentCount diff --git a/src/runtime/DataViewObject.h b/src/runtime/DataViewObject.h index 6f53751f6..bffe2a84e 100644 --- a/src/runtime/DataViewObject.h +++ b/src/runtime/DataViewObject.h @@ -90,8 +90,8 @@ class DataViewObject : public ArrayBufferView { ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, state.context()->staticStrings().DataView.string(), false, String::emptyString(), ErrorObject::Messages::GlobalObject_InvalidArrayBufferOffset); } + // Perform coercion first before any buffer state checks auto numericValue = val.toNumeric(state); - UNUSED_VARIABLE(numericValue); bool isLittleEndian = _isLittleEndian.toBoolean(); throwTypeErrorIfDetached(state); @@ -105,7 +105,8 @@ class DataViewObject : public ArrayBufferView { } size_t bufferIndex = numberIndex + viewOffset; - buffer()->setValueInBuffer(state, bufferIndex, type, val, isLittleEndian); + // Pass the already-coerced numeric value to prevent re-coercion in setValueInBuffer + buffer()->setValueInBuffer(state, bufferIndex, type, numericValue.first, isLittleEndian); } }; } // namespace Escargot diff --git a/src/runtime/IteratorObject.cpp b/src/runtime/IteratorObject.cpp index 4562df645..9f04247f2 100644 --- a/src/runtime/IteratorObject.cpp +++ b/src/runtime/IteratorObject.cpp @@ -26,6 +26,7 @@ #include "runtime/AsyncFromSyncIteratorObject.h" #include "runtime/ScriptAsyncFunctionObject.h" #include "runtime/StringObject.h" +#include "runtime/ArrayBuffer.h" namespace Escargot { @@ -227,6 +228,11 @@ ValueVectorWithInlineStorage IteratorObject::iterableToList(ExecutionState& stat if (next.hasValue()) { Value nextValue = IteratorObject::iteratorValue(state, next.value()); values.pushBack(nextValue); + // Check if the size exceeds the maximum allowed size for TypedArray construction + // This prevents memory exhaustion when iterating over very large sparse arrays + if (values.size() >= ArrayBuffer::maxArrayBufferSize) { + ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, state.context()->staticStrings().TypedArray.string(), false, String::emptyString(), ErrorObject::Messages::GlobalObject_InvalidArrayLength); + } } else { break; } diff --git a/src/runtime/JSON.cpp b/src/runtime/JSON.cpp index 8aea19fbf..8792b7c69 100644 --- a/src/runtime/JSON.cpp +++ b/src/runtime/JSON.cpp @@ -83,6 +83,30 @@ struct JSONStringStream { const Ch* tail_; }; +template +class JSONDocument : public rapidjson::GenericDocument { +public: + JSONDocument(ExecutionState& state) + : m_state(state) + { + } + + bool StartObject() + { + CHECK_STACK_OVERFLOW(m_state); + return rapidjson::GenericDocument::StartObject(); + } + + bool StartArray() + { + CHECK_STACK_OVERFLOW(m_state); + return rapidjson::GenericDocument::StartArray(); + } + +private: + ExecutionState& m_state; +}; + template static Value parseJSONWorker(ExecutionState& state, const rapidjson::GenericValue& value) { @@ -156,12 +180,12 @@ static Value parseJSONWorker(ExecutionState& state, const rapidjson::GenericValu } template -static Value parseJSON(ExecutionState& state, const CharType* data, size_t length, rapidjson::GenericDocument& jsonDocument) +static Value parseJSON(ExecutionState& state, const CharType* data, size_t length, JSONDocument& jsonDocument) { auto strings = &state.context()->staticStrings(); JSONStringStream stringStream(data, length); - jsonDocument.ParseStream(stringStream); + jsonDocument.template ParseStream, JSONDocument>(stringStream); if (jsonDocument.HasParseError()) { ErrorObject::throwBuiltinError(state, ErrorCode::SyntaxError, strings->JSON.string(), true, strings->parse.string(), rapidjson::GetParseError_En(jsonDocument.GetParseError())); } @@ -197,7 +221,7 @@ Value JSON::parse(ExecutionState& state, Value text, Value reviver) // 1, 2, 3 String* JText = text.toString(state); - rapidjson::GenericDocument> parseResult; + JSONDocument> parseResult(state); Value unfiltered; if (JText->has8BitContent()) { size_t len = JText->length(); diff --git a/src/runtime/StringObject.cpp b/src/runtime/StringObject.cpp index c5ad03b43..d6af0a76c 100644 --- a/src/runtime/StringObject.cpp +++ b/src/runtime/StringObject.cpp @@ -77,6 +77,18 @@ ObjectGetResult StringObject::getOwnProperty(ExecutionState& state, const Object bool StringObject::defineOwnProperty(ExecutionState& state, const ObjectPropertyName& P, const ObjectPropertyDescriptor& desc) { + // Check if this is an index property within the string length + // String index properties are non-configurable and non-writable per ECMAScript spec + // We must reject any attempt to define a property on these indexed positions + size_t idx = P.tryToUseAsIndexProperty(); + if (idx != Value::InvalidIndexPropertyValue) { + size_t strLen = m_primitiveValue->length(); + if (idx < strLen) { + // Indexed properties within string length are non-configurable + // Per ECMAScript spec, defining a non-configurable property should fail + return false; + } + } auto r = getOwnProperty(state, P); if (r.hasValue() && !r.isConfigurable()) return false; diff --git a/test/vendortest b/test/vendortest index 0db8e32d9..71d8a3453 160000 --- a/test/vendortest +++ b/test/vendortest @@ -1 +1 @@ -Subproject commit 0db8e32d96a15fcd392021c29fe9103cd52b86a6 +Subproject commit 71d8a3453148662bcbde7cd8180aaea7bf29ae32 diff --git a/third_party/rapidjson/include/rapidjson/document.h b/third_party/rapidjson/include/rapidjson/document.h index 3e23551f8..f06a2eb55 100644 --- a/third_party/rapidjson/include/rapidjson/document.h +++ b/third_party/rapidjson/include/rapidjson/document.h @@ -2063,6 +2063,26 @@ class GenericDocument : public GenericValue { //!@name Parse from stream //!@{ + //! Parse JSON text from an input stream (with Encoding conversion) + /*! \tparam parseFlags Combination of \ref ParseFlag. + \tparam SourceEncoding Encoding of input stream + \tparam InputStream Type of input stream, implementing Stream concept + \param is Input stream to be parsed. + \return The document itself for fluent API. + */ + template + GenericDocument& ParseStream(InputStream& is) + { + ValueType::SetNull(); // Remove existing root if exist + GenericReader reader(&stack_.GetAllocator()); + ClearStackOnExit scope(*this); + parseResult_ = reader.template Parse(is, (Handler&)*this); + if (parseResult_) { + RAPIDJSON_ASSERT(stack_.GetSize() == sizeof(ValueType)); // Got one and only one root object + this->RawAssign(*stack_.template Pop(1)); // Add this-> to prevent issue 13. + } + return *this; + } //! Parse JSON text from an input stream (with Encoding conversion) /*! \tparam parseFlags Combination of \ref ParseFlag. @@ -2189,7 +2209,7 @@ class GenericDocument : public GenericValue { //! Get the capacity of stack in bytes. size_t GetStackCapacity() const { return stack_.GetCapacity(); } -private: +protected: // clear stack on any exit from ParseStream, e.g. due to exception struct ClearStackOnExit { explicit ClearStackOnExit(GenericDocument& d)