From 1387d7eedeacea6ae0d12660d4a0431e669b5458 Mon Sep 17 00:00:00 2001 From: huangfeijie Date: Thu, 21 Sep 2023 14:22:30 +0800 Subject: [PATCH 01/20] issue:https://gitee.com/openharmony/commonlibrary_ets_utils/issues/I83868 support TerminateExecution in Taskpool Throw the exception when find the temination flag Signed-off-by: huangfeijie --- BUILD.gn | 4 + ecmascript/base/error_helper.cpp | 3 + ecmascript/base/error_type.h | 1 + ecmascript/base/tests/error_helper_test.cpp | 9 + ecmascript/builtins/builtins.cpp | 16 +- ecmascript/builtins/builtins_errors.cpp | 13 ++ ecmascript/builtins/builtins_errors.h | 7 + ecmascript/compiler/interpreter_stub.cpp | 18 +- ecmascript/dfx/hprof/heap_snapshot.cpp | 2 + ecmascript/dfx/vm_thread_control.cpp | 11 + ecmascript/dfx/vm_thread_control.h | 4 + ecmascript/dump.cpp | 7 + ecmascript/ecma_context.cpp | 4 +- ecmascript/ecma_context.h | 7 +- ecmascript/ecma_macros.h | 4 + ecmascript/global_env_constants.h | 1 + ecmascript/global_env_fields.h | 1 + ecmascript/interpreter/interpreter-inl.h | 3 +- ecmascript/jobs/micro_job_queue.cpp | 5 + ecmascript/js_hclass.h | 5 +- ecmascript/js_serializer.cpp | 7 + ecmascript/js_serializer.h | 1 + ecmascript/js_thread.cpp | 13 ++ ecmascript/js_thread.h | 36 +++ ecmascript/mem/object_xray.h | 1 + ecmascript/napi/dfx_jsnapi.cpp | 6 + ecmascript/napi/include/dfx_jsnapi.h | 1 + ecmascript/napi/include/jsnapi.h | 2 + ecmascript/napi/jsnapi.cpp | 5 + ecmascript/napi/jsnapi_helper.h | 3 +- ecmascript/object_factory.cpp | 7 +- ecmascript/runtime_call_id.h | 2 + ecmascript/tests/dump_test.cpp | 1 + test/executiontest/BUILD.gn | 95 ++++++++ test/executiontest/js/termination_1.js | 27 +++ test/executiontest/js/termination_2.js | 27 +++ test/executiontest/js/termination_3.js | 24 ++ test/executiontest/js/termination_4.js | 37 ++++ test/executiontest/js/termination_5.js | 37 ++++ test/executiontest/js/termination_6.js | 32 +++ .../executiontest/thread_termination_test.cpp | 207 ++++++++++++++++++ test/resource/js_runtime/ohos_test.xml | 11 + 42 files changed, 693 insertions(+), 14 deletions(-) create mode 100644 test/executiontest/BUILD.gn create mode 100644 test/executiontest/js/termination_1.js create mode 100644 test/executiontest/js/termination_2.js create mode 100644 test/executiontest/js/termination_3.js create mode 100644 test/executiontest/js/termination_4.js create mode 100644 test/executiontest/js/termination_5.js create mode 100644 test/executiontest/js/termination_6.js create mode 100644 test/executiontest/thread_termination_test.cpp diff --git a/BUILD.gn b/BUILD.gn index 6c092b6dce..78c18a59d3 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -190,6 +190,9 @@ group("ark_runtime_host_unittest") { # quickfix test deps += [ "$js_root/test/quickfix:ark_quickfix_test" ] + # execution test + deps += [ "$js_root/test/executiontest:ark_execution_test" ] + # ts aot test and asm test if (!run_with_asan) { deps += [ @@ -476,6 +479,7 @@ config("ark_jsruntime_common_config") { config("ecma_test_config") { visibility = [ "./ecmascript/*", + "./test/executiontest/*", "./test/fuzztest/*", ] diff --git a/ecmascript/base/error_helper.cpp b/ecmascript/base/error_helper.cpp index 489997efc0..58197f6ac7 100644 --- a/ecmascript/base/error_helper.cpp +++ b/ecmascript/base/error_helper.cpp @@ -118,6 +118,9 @@ JSHandle ErrorHelper::GetErrorName(JSThread *thread, const JSHand case ErrorType::OOM_ERROR: errorKey = globalConst->GetHandledOOMErrorString(); break; + case ErrorType::TERMINATION_ERROR: + errorKey = globalConst->GetHandledTerminationErrorString(); + break; default: errorKey = globalConst->GetHandledErrorString(); break; diff --git a/ecmascript/base/error_type.h b/ecmascript/base/error_type.h index 7905c0614b..ce5acacf3c 100644 --- a/ecmascript/base/error_type.h +++ b/ecmascript/base/error_type.h @@ -29,6 +29,7 @@ enum class ErrorType : uint8_t { URI_ERROR, AGGREGATE_ERROR, OOM_ERROR, + TERMINATION_ERROR, }; } // namespace panda::ecmascript::base diff --git a/ecmascript/base/tests/error_helper_test.cpp b/ecmascript/base/tests/error_helper_test.cpp index c006c0dbc2..3739202b71 100644 --- a/ecmascript/base/tests/error_helper_test.cpp +++ b/ecmascript/base/tests/error_helper_test.cpp @@ -105,6 +105,7 @@ HWTEST_F_L0(ErrorHelperTest, ErrorCommonToString_002) JSHandle syntaxErrorFunc = env->GetSyntaxErrorFunction(); JSHandle referenceErrorFunc = env->GetReferenceErrorFunction(); JSHandle aggregateErrorFunc = env->GetAggregateErrorFunction(); + JSHandle terminationErrorFunc = env->GetTerminationErrorFunction(); JSHandle uriErrorObj = factory->NewJSObjectByConstructor(JSHandle(uriErrorFunc), uriErrorFunc); JSHandle oomErrorObj = @@ -115,6 +116,8 @@ HWTEST_F_L0(ErrorHelperTest, ErrorCommonToString_002) factory->NewJSObjectByConstructor(JSHandle(referenceErrorFunc), referenceErrorFunc); JSHandle aggregateErrorObj = factory->NewJSObjectByConstructor(JSHandle(aggregateErrorFunc), aggregateErrorFunc); + JSHandle terminationErrorObj = + factory->NewJSObjectByConstructor(JSHandle(terminationErrorFunc), terminationErrorFunc); EcmaRuntimeCallInfo* argv = TestHelper::CreateEcmaRuntimeCallInfo(thread, JSTaggedValue::Undefined(), 4); argv->SetFunction(JSTaggedValue::Undefined()); @@ -128,6 +131,12 @@ HWTEST_F_L0(ErrorHelperTest, ErrorCommonToString_002) JSHandle oomError(thread, ErrorHelper::ErrorCommonToString(argv, ErrorType::OOM_ERROR)); TestHelper::TearDownFrame(thread, prev); + argv->SetThis(JSTaggedValue(*terminationErrorObj)); + prev = TestHelper::SetupFrame(thread, argv); + JSHandle terminationError(thread, + ErrorHelper::ErrorCommonToString(argv, ErrorType::TERMINATION_ERROR)); + TestHelper::TearDownFrame(thread, prev); + argv->SetThis(JSTaggedValue(*syntaxErrorObj)); prev = TestHelper::SetupFrame(thread, argv); JSHandle syntaxError(thread, ErrorHelper::ErrorCommonToString(argv, ErrorType::SYNTAX_ERROR)); diff --git a/ecmascript/builtins/builtins.cpp b/ecmascript/builtins/builtins.cpp index dda0fcc4ad..5b09db9340 100644 --- a/ecmascript/builtins/builtins.cpp +++ b/ecmascript/builtins/builtins.cpp @@ -144,6 +144,7 @@ using URIError = builtins::BuiltinsURIError; using SyntaxError = builtins::BuiltinsSyntaxError; using EvalError = builtins::BuiltinsEvalError; using OOMError = builtins::BuiltinsOOMError; +using TerminationError = builtins::BuiltinsTerminationError; using ErrorType = base::ErrorType; using RandomGenerator = base::RandomGenerator; using Global = builtins::BuiltinsGlobal; @@ -1007,6 +1008,7 @@ void Builtins::InitializeAllTypeError(const JSHandle &env, const JSHa InitializeError(env, errorNativeFuncInstanceHClass, JSType::JS_SYNTAX_ERROR); InitializeError(env, errorNativeFuncInstanceHClass, JSType::JS_EVAL_ERROR); InitializeError(env, errorNativeFuncInstanceHClass, JSType::JS_OOM_ERROR); + InitializeError(env, errorNativeFuncInstanceHClass, JSType::JS_TERMINATION_ERROR); } void Builtins::InitializeAllTypeErrorWithRealm(const JSHandle &realm) const @@ -1024,6 +1026,7 @@ void Builtins::InitializeAllTypeErrorWithRealm(const JSHandle &realm) SetErrorWithRealm(realm, JSType::JS_SYNTAX_ERROR); SetErrorWithRealm(realm, JSType::JS_EVAL_ERROR); SetErrorWithRealm(realm, JSType::JS_OOM_ERROR); + SetErrorWithRealm(realm, JSType::JS_TERMINATION_ERROR); } void Builtins::SetErrorWithRealm(const JSHandle &realm, const JSType &errorTag) const @@ -1074,6 +1077,11 @@ void Builtins::SetErrorWithRealm(const JSHandle &realm, const JSType nameString = JSHandle(thread_->GlobalConstants()->GetHandledOOMErrorString()); realm->SetOOMErrorFunction(thread_, nativeErrorFunction); break; + case JSType::JS_TERMINATION_ERROR: + nativeErrorFunction = env->GetTerminationErrorFunction(); + nameString = JSHandle(thread_->GlobalConstants()->GetHandledTerminationErrorString()); + realm->SetTerminationErrorFunction(thread_, nativeErrorFunction); + break; default: break; } @@ -1132,6 +1140,10 @@ void Builtins::InitializeError(const JSHandle &env, const JSHandle &env, const JSHandleSetSyntaxErrorFunction(thread_, nativeErrorFunction); } else if (errorTag == JSType::JS_EVAL_ERROR) { env->SetEvalErrorFunction(thread_, nativeErrorFunction); - } else { + } else if (errorTag == JSType::JS_OOM_ERROR) { env->SetOOMErrorFunction(thread_, nativeErrorFunction); + } else { + env->SetTerminationErrorFunction(thread_, nativeErrorFunction); } } // namespace panda::ecmascript diff --git a/ecmascript/builtins/builtins_errors.cpp b/ecmascript/builtins/builtins_errors.cpp index 341e3f61ac..351eedf8c3 100644 --- a/ecmascript/builtins/builtins_errors.cpp +++ b/ecmascript/builtins/builtins_errors.cpp @@ -207,4 +207,17 @@ JSTaggedValue BuiltinsOOMError::ToString(EcmaRuntimeCallInfo *argv) BUILTINS_API_TRACE(argv->GetThread(), Error, OOMErrorToString); return ErrorHelper::ErrorCommonToString(argv, ErrorType::OOM_ERROR); } + +// TerminationError +JSTaggedValue BuiltinsTerminationError::TerminationErrorConstructor(EcmaRuntimeCallInfo *argv) +{ + BUILTINS_API_TRACE(argv->GetThread(), Error, TerminationErrorConstructor); + return ErrorHelper::ErrorCommonConstructor(argv, ErrorType::TERMINATION_ERROR); +} + +JSTaggedValue BuiltinsTerminationError::ToString(EcmaRuntimeCallInfo *argv) +{ + BUILTINS_API_TRACE(argv->GetThread(), Error, TerminationErrorToString); + return ErrorHelper::ErrorCommonToString(argv, ErrorType::TERMINATION_ERROR); +} } // namespace panda::ecmascript::builtins diff --git a/ecmascript/builtins/builtins_errors.h b/ecmascript/builtins/builtins_errors.h index 7c75c771c7..c50ada7dc9 100644 --- a/ecmascript/builtins/builtins_errors.h +++ b/ecmascript/builtins/builtins_errors.h @@ -92,5 +92,12 @@ public: static JSTaggedValue ToString(EcmaRuntimeCallInfo *argv); }; + +class BuiltinsTerminationError : public base::BuiltinsBase { +public: + static JSTaggedValue TerminationErrorConstructor(EcmaRuntimeCallInfo *argv); + + static JSTaggedValue ToString(EcmaRuntimeCallInfo *argv); +}; } // namespace panda::ecmascript::builtins #endif // ECMASCRIPT_BUILTINS_BUILTINS_ERRORS_H diff --git a/ecmascript/compiler/interpreter_stub.cpp b/ecmascript/compiler/interpreter_stub.cpp index 34ba2b0b06..b099171c67 100644 --- a/ecmascript/compiler/interpreter_stub.cpp +++ b/ecmascript/compiler/interpreter_stub.cpp @@ -133,12 +133,20 @@ void name##StubBuilder::GenerateCircuitImpl(GateRef glue, GateRef sp, GateRef pc Int8Equal(interruptsFlag, Int8(VmThreadControl::VM_NEED_SUSPENSION))), \ &callRuntime, &initialized); \ Bind(&callRuntime); \ + if (!(callback).IsEmpty()) { \ + varProfileTypeInfo = CallRuntime(glue, RTSTUB_ID(UpdateHotnessCounterWithProf), { func }); \ + } else { \ + varProfileTypeInfo = CallRuntime(glue, RTSTUB_ID(UpdateHotnessCounter), { func }); \ + } \ + Label handleException(env); \ + Label noException(env); \ + Branch(HasPendingException(glue), &handleException, &noException); \ + Bind(&handleException); \ + { \ + DISPATCH_LAST(); \ + } \ + Bind(&noException); \ { \ - if (!(callback).IsEmpty()) { \ - varProfileTypeInfo = CallRuntime(glue, RTSTUB_ID(UpdateHotnessCounterWithProf), { func }); \ - } else { \ - varProfileTypeInfo = CallRuntime(glue, RTSTUB_ID(UpdateHotnessCounter), { func }); \ - } \ Jump(&dispatch); \ } \ Bind(&initialized); \ diff --git a/ecmascript/dfx/hprof/heap_snapshot.cpp b/ecmascript/dfx/hprof/heap_snapshot.cpp index 69d1b7cd49..6c0ae8d3a5 100644 --- a/ecmascript/dfx/hprof/heap_snapshot.cpp +++ b/ecmascript/dfx/hprof/heap_snapshot.cpp @@ -295,6 +295,8 @@ CString *HeapSnapshot::GenerateNodeName(TaggedObject *entry) return GetString("Syntax Error"); case JSType::JS_OOM_ERROR: return GetString("OutOfMemory Error"); + case JSType::JS_TERMINATION_ERROR: + return GetString("Termination Error"); case JSType::JS_REG_EXP: return GetString("Regexp"); case JSType::JS_SET: diff --git a/ecmascript/dfx/vm_thread_control.cpp b/ecmascript/dfx/vm_thread_control.cpp index 869dfbfac6..9c6b7819cf 100644 --- a/ecmascript/dfx/vm_thread_control.cpp +++ b/ecmascript/dfx/vm_thread_control.cpp @@ -36,6 +36,12 @@ bool VmThreadControl::NotifyVMThreadSuspension() // block caller thread return true; } +void VmThreadControl::RequestTerminateExecution() +{ + SetTerminationRequest(true); + thread_->SetCheckSafePointStatus(); +} + void VmThreadControl::SetVMNeedSuspension(bool flag) { thread_->SetVMNeedSuspension(flag); @@ -46,6 +52,11 @@ bool VmThreadControl::VMNeedSuspension() const return thread_->VMNeedSuspension(); } +void VmThreadControl::SetTerminationRequest(bool flag) +{ + thread_->SetTerminationRequest(flag); +} + void VmThreadControl::SetVMSuspended(bool flag) { thread_->SetVMSuspended(flag); diff --git a/ecmascript/dfx/vm_thread_control.h b/ecmascript/dfx/vm_thread_control.h index 19cf253534..27a4d80f91 100644 --- a/ecmascript/dfx/vm_thread_control.h +++ b/ecmascript/dfx/vm_thread_control.h @@ -32,6 +32,8 @@ public: void SetVMNeedSuspension(bool flag); + void SetTerminationRequest(bool flag); + bool VMNeedSuspension() const; void SuspendVM(); @@ -40,6 +42,8 @@ public: bool NotifyVMThreadSuspension(); + void RequestTerminateExecution(); + void SetVMSuspended(bool flag); bool IsSuspended() const; diff --git a/ecmascript/dump.cpp b/ecmascript/dump.cpp index c7edb38d08..9f46ab7121 100644 --- a/ecmascript/dump.cpp +++ b/ecmascript/dump.cpp @@ -183,6 +183,8 @@ CString JSHClass::DumpJSType(JSType type) return "Syntax Error"; case JSType::JS_OOM_ERROR: return "OutOfMemory Error"; + case JSType::JS_TERMINATION_ERROR: + return "Termination Error"; case JSType::JS_REG_EXP: return "Regexp"; case JSType::JS_SET: @@ -685,6 +687,7 @@ static void DumpObject(TaggedObject *obj, std::ostream &os) case JSType::JS_URI_ERROR: case JSType::JS_SYNTAX_ERROR: case JSType::JS_OOM_ERROR: + case JSType::JS_TERMINATION_ERROR: case JSType::JS_ARGUMENTS: needDumpHClass = true; JSObject::Cast(obj)->Dump(os); @@ -2389,6 +2392,8 @@ void GlobalEnv::Dump(std::ostream &os) const GetEvalErrorFunction().GetTaggedValue().Dump(os); os << " - OOMErrorFunction: "; GetOOMErrorFunction().GetTaggedValue().Dump(os); + os << " - TerminationErrorFunction: "; + GetTerminationErrorFunction().GetTaggedValue().Dump(os); os << " - RegExpFunction: "; GetRegExpFunction().GetTaggedValue().Dump(os); os << " - BuiltinsSetFunction: "; @@ -3799,6 +3804,7 @@ static void DumpObject(TaggedObject *obj, std::vector &vec, bool isVm case JSType::JS_URI_ERROR: case JSType::JS_SYNTAX_ERROR: case JSType::JS_OOM_ERROR: + case JSType::JS_TERMINATION_ERROR: case JSType::JS_ARGUMENTS: case JSType::JS_GLOBAL_OBJECT: JSObject::Cast(obj)->DumpForSnapshot(vec); @@ -4839,6 +4845,7 @@ void GlobalEnv::DumpForSnapshot(std::vector &vec) const vec.emplace_back(CString("SyntaxErrorFunction"), GetSyntaxErrorFunction().GetTaggedValue()); vec.emplace_back(CString("EvalErrorFunction"), GetEvalErrorFunction().GetTaggedValue()); vec.emplace_back(CString("OOMErrorFunction"), GetOOMErrorFunction().GetTaggedValue()); + vec.emplace_back(CString("TerminationErrorFunction"), GetTerminationErrorFunction().GetTaggedValue()); vec.emplace_back(CString("RegExpFunction"), GetRegExpFunction().GetTaggedValue()); vec.emplace_back(CString("BuiltinsSetFunction"), GetBuiltinsSetFunction().GetTaggedValue()); vec.emplace_back(CString("BuiltinsMapFunction"), GetBuiltinsMapFunction().GetTaggedValue()); diff --git a/ecmascript/ecma_context.cpp b/ecmascript/ecma_context.cpp index 3f1151ec3f..80b7087871 100644 --- a/ecmascript/ecma_context.cpp +++ b/ecmascript/ecma_context.cpp @@ -612,8 +612,8 @@ void EcmaContext::PrintJSErrorInfo(JSThread *thread, const JSHandleHasTerminated())) { + return false; } return job::MicroJobQueue::HasPendingJob(thread_, GetMicroJobQueue()); } diff --git a/ecmascript/ecma_context.h b/ecmascript/ecma_context.h index 08a1a41148..6a17228ff6 100644 --- a/ecmascript/ecma_context.h +++ b/ecmascript/ecma_context.h @@ -102,6 +102,11 @@ public: bool Initialize(); + bool IsExecutingPendingJob() const + { + return isProcessingPendingJob_.load(); + } + bool HasPendingJob(); bool ExecutePromisePendingJob(); @@ -475,8 +480,8 @@ private: EcmaVM *vm_ {nullptr}; bool isUncaughtExceptionRegistered_ {false}; - bool isProcessingPendingJob_ {false}; bool initialized_ {false}; + std::atomic isProcessingPendingJob_ {false}; ObjectFactory *factory_ {nullptr}; // VM execution states. diff --git a/ecmascript/ecma_macros.h b/ecmascript/ecma_macros.h index d9a256d0ac..82b5aaef37 100644 --- a/ecmascript/ecma_macros.h +++ b/ecmascript/ecma_macros.h @@ -363,6 +363,10 @@ #define THROW_OOM_ERROR(thread, message) \ THROW_ERROR(thread, ErrorType::OOM_ERROR, message) +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define THROW_TERMINATION_ERROR(thread, message) \ + THROW_ERROR(thread, ErrorType::TERMINATION_ERROR, message) + // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define RETURN_STACK_BEFORE_THROW_IF_ASM(thread) \ do { \ diff --git a/ecmascript/global_env_constants.h b/ecmascript/global_env_constants.h index 821b7b41cf..4561a439bb 100644 --- a/ecmascript/global_env_constants.h +++ b/ecmascript/global_env_constants.h @@ -253,6 +253,7 @@ class ObjectFactory; V(SyntaxErrorString, SYNTAX_ERROR_STRING_INDEX, "SyntaxError") \ V(EvalErrorString, EVAL_ERROR_STRING_INDEX, "EvalError") \ V(OOMErrorString, OOM_ERROR_STRING_INDEX, "OutOfMemoryError") \ + V(TerminationErrorString, TERMINATION_ERROR_STRING_INDEX, "TerminationError") \ V(ErrorFuncString, ERROR_FUNC_STRING_INDEX, "errorfunc") \ V(StackString, STACK_STRING_INDEX, "stack") \ V(StackEmptyString, STACK_EMPTY_STRING_INDEX, "stackisempty") \ diff --git a/ecmascript/global_env_fields.h b/ecmascript/global_env_fields.h index d5dbf2a9c3..35e47b40f1 100644 --- a/ecmascript/global_env_fields.h +++ b/ecmascript/global_env_fields.h @@ -63,6 +63,7 @@ V(JSTaggedValue, SyntaxErrorFunction, SYNTAX_ERROR_FUNCTION_INDEX) \ V(JSTaggedValue, EvalErrorFunction, EVAL_ERROR_FUNCTION_INDEX) \ V(JSTaggedValue, OOMErrorFunction, OOM_ERROR_FUNCTION_INDEX) \ + V(JSTaggedValue, TerminationErrorFunction, TERMINATION_ERROR_FUNCTION_INDEX) \ V(JSTaggedValue, IntlFunction, INTL_FUNCTION_INDEX) \ V(JSTaggedValue, LocaleFunction, LOCALE_FUNCTION_INDEX) \ V(JSTaggedValue, DateTimeFormatFunction, DATE_TIME_FORMAT_FUNCTION_INDEX) \ diff --git a/ecmascript/interpreter/interpreter-inl.h b/ecmascript/interpreter/interpreter-inl.h index 1d2c3fb549..661a3b2d30 100644 --- a/ecmascript/interpreter/interpreter-inl.h +++ b/ecmascript/interpreter/interpreter-inl.h @@ -544,6 +544,7 @@ using CommonStubCSigns = kungfu::CommonStubCSigns; #define UPDATE_HOTNESS_COUNTER(offset) \ do { \ if (UpdateHotnessCounter(thread, sp, acc, offset)) { \ + HANDLE_EXCEPTION_IF_ABRUPT_COMPLETION(thread); \ RESTORE_ACC(); \ } \ } while (false) @@ -4843,7 +4844,7 @@ NO_UB_SANITIZE void EcmaInterpreter::RunInternal(JSThread *thread, const uint8_t uint16_t length = READ_INST_8_3(); LOG_INST() << "intrinsics::definefunc length: " << length; auto constpool = GetConstantPool(sp); - + auto module = GetEcmaModule(sp); Method *method = Method::Cast(GET_METHOD_FROM_CACHE(methodId).GetTaggedObject()); ASSERT(method != nullptr); diff --git a/ecmascript/jobs/micro_job_queue.cpp b/ecmascript/jobs/micro_job_queue.cpp index d598dea145..851c6fc7db 100644 --- a/ecmascript/jobs/micro_job_queue.cpp +++ b/ecmascript/jobs/micro_job_queue.cpp @@ -70,6 +70,11 @@ void MicroJobQueue::ExecutePendingJob(JSThread *thread, JSHandle if (thread->HasPendingException()) { return; } + if (thread->HasTerminated()) { + JSHandle emptyQueue(thread->GlobalConstants()->GetHandledEmptyTaggedQueue()); + jobQueue->SetPromiseJobQueue(thread, emptyQueue); + return; + } promiseQueue.Update(jobQueue->GetPromiseJobQueue()); } diff --git a/ecmascript/js_hclass.h b/ecmascript/js_hclass.h index e0396465b0..34b867693a 100644 --- a/ecmascript/js_hclass.h +++ b/ecmascript/js_hclass.h @@ -103,7 +103,8 @@ struct Reference; JS_AGGREGATE_ERROR, /* ////////////////////////////////////////////////////////////////////////////-PADDING */ \ JS_URI_ERROR, /* ////////////////////////////////////////////////////////////////////////////-PADDING */ \ JS_SYNTAX_ERROR, /* ////////////////////////////////////////////////////////////////////////////-PADDING */ \ - JS_OOM_ERROR, /* JS_ERROR_LAST /////////////////////////////////////////////////////////////////////// */\ + JS_OOM_ERROR, /* ////////////////////////////////////////////////////////////////////////////-PADDING */ \ + JS_TERMINATION_ERROR, /* JS_ERROR_LAST ///////////////////////////////////////////////////////////////////// */\ \ JS_REG_EXP, /* ///////////////////////////////////////////////////////////////////////////////////-PADDING */ \ JS_SET, /* ///////////////////////////////////////////////////////////////////////////////////-PADDING */ \ @@ -274,7 +275,7 @@ struct Reference; ECMA_OBJECT_LAST = JS_PROXY, /* /////////////////////////////////////////////////////////////////-PADDING */\ \ JS_ERROR_FIRST = JS_ERROR, /* ////////////////////////////////////////////////////////////////-PADDING */ \ - JS_ERROR_LAST = JS_OOM_ERROR, /* ////////////////////////////////////////////////////////////////-PADDING */\ + JS_ERROR_LAST = JS_TERMINATION_ERROR, /* ////////////////////////////////////////////////////////-PADDING */\ \ JS_ITERATOR_FIRST = JS_ITERATOR, /* //////////////////////////////////////////////////////////-PADDING */ \ JS_ITERATOR_LAST = JS_STRING_ITERATOR, /* //////////////////////////////////////////////////////////-PADDING */\ diff --git a/ecmascript/js_serializer.cpp b/ecmascript/js_serializer.cpp index 20205a420b..509f3e7573 100644 --- a/ecmascript/js_serializer.cpp +++ b/ecmascript/js_serializer.cpp @@ -292,6 +292,7 @@ bool JSSerializer::WriteTaggedObject(const JSHandle &value) case JSType::JS_URI_ERROR: case JSType::JS_SYNTAX_ERROR: case JSType::JS_OOM_ERROR: + case JSType::JS_TERMINATION_ERROR: return WriteJSError(value); case JSType::JS_DATE: return WriteJSDate(value); @@ -554,6 +555,8 @@ bool JSSerializer::WriteJSErrorHeader(JSType type) return WriteType(SerializationUID::SYNTAX_ERROR); case JSType::JS_OOM_ERROR: return WriteType(SerializationUID::OOM_ERROR); + case JSType::JS_TERMINATION_ERROR: + return WriteType(SerializationUID::TERMINATION_ERROR); default: LOG_ECMA(FATAL) << "this branch is unreachable"; UNREACHABLE(); @@ -1153,6 +1156,7 @@ JSHandle JSDeserializer::DeserializeJSTaggedValue() case SerializationUID::URI_ERROR: case SerializationUID::SYNTAX_ERROR: case SerializationUID::OOM_ERROR: + case SerializationUID::TERMINATION_ERROR: return ReadJSError(uid); case SerializationUID::JS_DATE: return ReadJSDate(); @@ -1419,6 +1423,9 @@ JSHandle JSDeserializer::ReadJSError(SerializationUID uid) case SerializationUID::OOM_ERROR: errorType = base::ErrorType::OOM_ERROR; break; + case SerializationUID::TERMINATION_ERROR: + errorType = base::ErrorType::TERMINATION_ERROR; + break; default: LOG_ECMA(FATAL) << "this branch is unreachable"; UNREACHABLE(); diff --git a/ecmascript/js_serializer.h b/ecmascript/js_serializer.h index e153768e74..6843afc4c5 100644 --- a/ecmascript/js_serializer.h +++ b/ecmascript/js_serializer.h @@ -91,6 +91,7 @@ enum class SerializationUID : uint8_t { URI_ERROR, SYNTAX_ERROR, OOM_ERROR, + TERMINATION_ERROR, ERROR_MESSAGE_BEGIN, ERROR_MESSAGE_END, // Function begin diff --git a/ecmascript/js_thread.cpp b/ecmascript/js_thread.cpp index 325e5a3261..db7c0ba2a3 100644 --- a/ecmascript/js_thread.cpp +++ b/ecmascript/js_thread.cpp @@ -469,9 +469,22 @@ void JSThread::CheckOrSwitchPGOStubs() } } +void JSThread::TerminateExecution() +{ + // set the TERMINATE_ERROR to exception + ObjectFactory *factory = GetEcmaVM()->GetFactory(); + JSHandle error = factory->GetJSError(ErrorType::TERMINATION_ERROR, "Terminate execution!"); + SetException(error.GetTaggedValue()); +} + bool JSThread::CheckSafepoint() { ResetCheckSafePointStatus(); + if (HasTerminationRequest()) { + TerminateExecution(); + SetVMTerminated(true); + SetTerminationRequest(false); + } if (vmThreadControl_->VMNeedSuspension()) { vmThreadControl_->SuspendVM(); } diff --git a/ecmascript/js_thread.h b/ecmascript/js_thread.h index eadaba88e7..53aa34a83e 100644 --- a/ecmascript/js_thread.h +++ b/ecmascript/js_thread.h @@ -70,6 +70,8 @@ public: using CheckSafePointBit = BitField; using VMNeedSuspensionBit = BitField; using VMHasSuspendedBit = VMNeedSuspensionBit::NextFlag; + using VMNeedTerminationBit = VMHasSuspendedBit::NextFlag; + using VMHasTerminatedBit = VMNeedTerminationBit::NextFlag; using PGOStatusBits = BitField; using BCStubStatusBits = PGOStatusBits::NextField; using ThreadId = uint32_t; @@ -445,36 +447,68 @@ public: void SetCheckSafePointStatus() { + LockHolder lock(interruptMutex_); ASSERT(static_cast(glueData_.interruptVector_ & 0xFF) <= 1); CheckSafePointBit::Set(true, &glueData_.interruptVector_); } void ResetCheckSafePointStatus() { + LockHolder lock(interruptMutex_); ASSERT(static_cast(glueData_.interruptVector_ & 0xFF) <= 1); CheckSafePointBit::Set(false, &glueData_.interruptVector_); } void SetVMNeedSuspension(bool flag) { + LockHolder lock(interruptMutex_); VMNeedSuspensionBit::Set(flag, &glueData_.interruptVector_); } bool VMNeedSuspension() { + LockHolder lock(interruptMutex_); return VMNeedSuspensionBit::Decode(glueData_.interruptVector_); } void SetVMSuspended(bool flag) { + LockHolder lock(interruptMutex_); VMHasSuspendedBit::Set(flag, &glueData_.interruptVector_); } bool IsVMSuspended() { + LockHolder lock(interruptMutex_); return VMHasSuspendedBit::Decode(glueData_.interruptVector_); } + bool HasTerminationRequest() const + { + LockHolder lock(interruptMutex_); + return VMNeedTerminationBit::Decode(glueData_.interruptVector_); + } + + void SetTerminationRequest(bool flag) + { + LockHolder lock(interruptMutex_); + VMNeedTerminationBit::Set(flag, &glueData_.interruptVector_); + } + + void SetVMTerminated(bool flag) + { + LockHolder lock(interruptMutex_); + VMHasTerminatedBit::Set(flag, &glueData_.interruptVector_); + } + + bool HasTerminated() const + { + LockHolder lock(interruptMutex_); + return VMHasTerminatedBit::Decode(glueData_.interruptVector_); + } + + void TerminateExecution(); + static uintptr_t GetCurrentStackPosition() { return reinterpret_cast(__builtin_frame_address(0)); @@ -953,6 +987,8 @@ private: CMap arrayHClassIndexMap_; CVector contexts_; + EcmaContext *currentContext_ {nullptr}; + mutable Mutex interruptMutex_; friend class GlobalHandleCollection; friend class EcmaVM; friend class EcmaContext; diff --git a/ecmascript/mem/object_xray.h b/ecmascript/mem/object_xray.h index 45b12e8118..8cc3f053fc 100644 --- a/ecmascript/mem/object_xray.h +++ b/ecmascript/mem/object_xray.h @@ -136,6 +136,7 @@ public: case JSType::JS_URI_ERROR: case JSType::JS_SYNTAX_ERROR: case JSType::JS_OOM_ERROR: + case JSType::JS_TERMINATION_ERROR: case JSType::JS_ASYNCITERATOR: case JSType::JS_ITERATOR: JSObject::Cast(object)->VisitRangeSlot(visitor); diff --git a/ecmascript/napi/dfx_jsnapi.cpp b/ecmascript/napi/dfx_jsnapi.cpp index af56c163dc..2a03a63fa8 100644 --- a/ecmascript/napi/dfx_jsnapi.cpp +++ b/ecmascript/napi/dfx_jsnapi.cpp @@ -525,6 +525,12 @@ bool DFXJSNApi::IsSuspended([[maybe_unused]] const EcmaVM *vm) #endif } +void DFXJSNApi::TerminateExecution(const EcmaVM *vm) +{ + ecmascript::VmThreadControl* vmThreadControl = vm->GetAssociatedJSThread()->GetVmThreadControl(); + vmThreadControl->RequestTerminateExecution(); +} + bool DFXJSNApi::CheckSafepoint([[maybe_unused]] const EcmaVM *vm) { #if defined(ECMASCRIPT_SUPPORT_SNAPSHOT) diff --git a/ecmascript/napi/include/dfx_jsnapi.h b/ecmascript/napi/include/dfx_jsnapi.h index eb29c74554..a904750147 100644 --- a/ecmascript/napi/include/dfx_jsnapi.h +++ b/ecmascript/napi/include/dfx_jsnapi.h @@ -109,6 +109,7 @@ public: static void ResumeVM(const EcmaVM *vm); static bool SuspendVM(const EcmaVM *vm); static bool IsSuspended(const EcmaVM *vm); + static void TerminateExecution(const EcmaVM *vm); static bool CheckSafepoint(const EcmaVM *vm); static void ResumeVMById(EcmaVM *vm, uint32_t tid); static bool SuspendVMById(EcmaVM *vm, uint32_t tid); diff --git a/ecmascript/napi/include/jsnapi.h b/ecmascript/napi/include/jsnapi.h index 10e33e9cb3..269bdef414 100644 --- a/ecmascript/napi/include/jsnapi.h +++ b/ecmascript/napi/include/jsnapi.h @@ -1004,6 +1004,7 @@ public: static Local AggregateError(const EcmaVM *vm, Local message); static Local EvalError(const EcmaVM *vm, Local message); static Local OOMError(const EcmaVM *vm, Local message); + static Local TerminationError(const EcmaVM *vm, Local message); }; using LOG_PRINT = int (*)(int id, int level, const char *tag, const char *fmt, const char *message); @@ -1384,6 +1385,7 @@ public: static void PrintExceptionInfo(const EcmaVM *vm); static Local GetAndClearUncaughtException(const EcmaVM *vm); static Local GetUncaughtException(const EcmaVM *vm); + static bool IsExecutingPendingJob(const EcmaVM *vm); static bool HasPendingException(const EcmaVM *vm); static bool HasPendingJob(const EcmaVM *vm); static void EnableUserUncaughtErrorHandler(EcmaVM *vm); diff --git a/ecmascript/napi/jsnapi.cpp b/ecmascript/napi/jsnapi.cpp index 1bf5d83f5d..b13ccefada 100644 --- a/ecmascript/napi/jsnapi.cpp +++ b/ecmascript/napi/jsnapi.cpp @@ -700,6 +700,11 @@ bool JSNApi::HasPendingException(const EcmaVM *vm) return vm->GetJSThread()->HasPendingException(); } +bool JSNApi::IsExecutingPendingJob(const EcmaVM *vm) +{ + return vm->GetJSThread()->GetCurrentEcmaContext()->IsExecutingPendingJob(); +} + bool JSNApi::HasPendingJob(const EcmaVM *vm) { return vm->GetJSThread()->GetCurrentEcmaContext()->HasPendingJob(); diff --git a/ecmascript/napi/jsnapi_helper.h b/ecmascript/napi/jsnapi_helper.h index 45e1d1a3e5..efe2c46273 100644 --- a/ecmascript/napi/jsnapi_helper.h +++ b/ecmascript/napi/jsnapi_helper.h @@ -65,7 +65,8 @@ V(TypeError, TYPE_ERROR) \ V(AggregateError, AGGREGATE_ERROR) \ V(EvalError, EVAL_ERROR) \ - V(OOMError, OOM_ERROR) + V(OOMError, OOM_ERROR) \ + V(TerminationError, TERMINATION_ERROR) namespace panda { using NativeFinalize = void (*)(EcmaVM *); diff --git a/ecmascript/object_factory.cpp b/ecmascript/object_factory.cpp index db41bd8f64..ee789c0d02 100644 --- a/ecmascript/object_factory.cpp +++ b/ecmascript/object_factory.cpp @@ -801,7 +801,8 @@ JSHandle ObjectFactory::GetJSError(const ErrorType &errorType, const c ASSERT_PRINT(errorType == ErrorType::ERROR || errorType == ErrorType::EVAL_ERROR || errorType == ErrorType::RANGE_ERROR || errorType == ErrorType::REFERENCE_ERROR || errorType == ErrorType::SYNTAX_ERROR || errorType == ErrorType::TYPE_ERROR || - errorType == ErrorType::URI_ERROR || errorType == ErrorType::OOM_ERROR, + errorType == ErrorType::URI_ERROR || errorType == ErrorType::OOM_ERROR || + errorType == ErrorType::TERMINATION_ERROR, "The error type is not in the valid range."); if (data != nullptr) { JSHandle handleMsg = NewFromUtf8(data); @@ -854,6 +855,9 @@ JSHandle ObjectFactory::NewJSError(const ErrorType &errorType, const J case ErrorType::OOM_ERROR: nativeConstructor = env->GetOOMErrorFunction(); break; + case ErrorType::TERMINATION_ERROR: + nativeConstructor = env->GetTerminationErrorFunction(); + break; default: nativeConstructor = env->GetErrorFunction(); break; @@ -937,6 +941,7 @@ void ObjectFactory::InitializeJSObject(const JSHandle &obj, const JSHa case JSType::JS_URI_ERROR: case JSType::JS_SYNTAX_ERROR: case JSType::JS_OOM_ERROR: + case JSType::JS_TERMINATION_ERROR: case JSType::JS_ASYNCITERATOR: case JSType::JS_ITERATOR: { break; diff --git a/ecmascript/runtime_call_id.h b/ecmascript/runtime_call_id.h index 0d7aa00a63..1c19379f75 100644 --- a/ecmascript/runtime_call_id.h +++ b/ecmascript/runtime_call_id.h @@ -391,6 +391,8 @@ namespace panda::ecmascript { V(Error, AggregateErrorToString) \ V(Error, OOMErrorConstructor) \ V(Error, OOMErrorToString) \ + V(Error, TerminationErrorConstructor) \ + V(Error, TerminationErrorToString) \ V(Function, Constructor) \ V(Function, PrototypeApply) \ V(Function, PrototypeBind) \ diff --git a/ecmascript/tests/dump_test.cpp b/ecmascript/tests/dump_test.cpp index 7a26c45eea..2ec1c87230 100644 --- a/ecmascript/tests/dump_test.cpp +++ b/ecmascript/tests/dump_test.cpp @@ -432,6 +432,7 @@ HWTEST_F_L0(EcmaDumpTest, HeapProfileDump) case JSType::JS_ARGUMENTS: case JSType::JS_SYNTAX_ERROR: case JSType::JS_OOM_ERROR: + case JSType::JS_TERMINATION_ERROR: case JSType::JS_OBJECT: { CHECK_DUMP_FIELDS(ECMAObject::SIZE, JSObject::SIZE, 2U); JSHandle jsObj = NewJSObject(thread, factory, globalEnv); diff --git a/test/executiontest/BUILD.gn b/test/executiontest/BUILD.gn new file mode 100644 index 0000000000..7ee50042e3 --- /dev/null +++ b/test/executiontest/BUILD.gn @@ -0,0 +1,95 @@ +# 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. + +import("//arkcompiler/ets_runtime/js_runtime_config.gni") +import("//arkcompiler/ets_runtime/test/test_helper.gni") + +test_js_path = "//arkcompiler/ets_runtime/test/executiontest/js/" + +test_js_files = [ + "termination_1", + "termination_2", + "termination_3", + "termination_4", + "termination_5", + "termination_6", +] + +foreach(file, test_js_files) { + es2abc_gen_abc("gen_${file}_abc") { + test_js = "${test_js_path}${file}.js" + test_abc = "$target_out_dir/${file}.abc" + + # Only targets in this file can depend on this. + extra_visibility = [ ":*" ] + src_js = rebase_path(test_js) + dst_file = rebase_path(test_abc) + extra_args = [] + extra_args += [ "--module" ] + extra_args += [ "--merge-abc" ] + in_puts = [ test_js ] + out_puts = [ test_abc ] + } +} + +module_output_path = "arkcompiler/ets_runtime" + +host_unittest_action("ThreadTerminationTest") { + module_out_path = module_output_path + + sources = [ + # test file + "thread_termination_test.cpp", + ] + + configs = [ + "$js_root:ecma_test_config", + "$ark_root/assembler:arkassembler_public_config", + "$ark_root/libpandafile:arkfile_public_config", + ] + + deps = [ + "$ark_root/assembler:libarkassembler_static", + "$js_root:libark_jsruntime_test", + sdk_libc_secshared_dep, + ] + + foreach(file, test_js_files) { + deps += [ ":gen_${file}_abc" ] + } + + if (is_ohos && is_standard_system) { + test_abc_dir = "/data/test" + } else { + test_abc_dir = rebase_path(target_out_dir) + } + defines = [ "MODULE_ABC_PATH=\"${test_abc_dir}/\"" ] + + # hiviewdfx libraries + external_deps = hiviewdfx_ext_deps + deps += hiviewdfx_deps +} + +group("ark_execution_test") { + testonly = true + + # deps file + deps = [ ":ThreadTerminationTestAction" ] +} + +group("host_unittest") { + testonly = true + + # deps file + deps = [ ":ThreadTerminationTestAction" ] +} diff --git a/test/executiontest/js/termination_1.js b/test/executiontest/js/termination_1.js new file mode 100644 index 0000000000..01b1e09fbe --- /dev/null +++ b/test/executiontest/js/termination_1.js @@ -0,0 +1,27 @@ +/* + * 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. + */ + +function func1() +{ + var flag = true; + while(true) { + if (flag) { + terminate(); + } + flag = false; + }; +} + +func1(); \ No newline at end of file diff --git a/test/executiontest/js/termination_2.js b/test/executiontest/js/termination_2.js new file mode 100644 index 0000000000..0e4d09157f --- /dev/null +++ b/test/executiontest/js/termination_2.js @@ -0,0 +1,27 @@ +/* + * 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. + */ + +function func1() +{ + var flag = true; + while(true) { + if (flag) { + signal(); + } + flag = false; + }; +} + +func1(); \ No newline at end of file diff --git a/test/executiontest/js/termination_3.js b/test/executiontest/js/termination_3.js new file mode 100644 index 0000000000..3223a01a0c --- /dev/null +++ b/test/executiontest/js/termination_3.js @@ -0,0 +1,24 @@ +/* + * 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. + */ + +function func1() +{ + var flag = true; + while(true) { + flag = false; + }; +} + +func1(); \ No newline at end of file diff --git a/test/executiontest/js/termination_4.js b/test/executiontest/js/termination_4.js new file mode 100644 index 0000000000..778bed54c1 --- /dev/null +++ b/test/executiontest/js/termination_4.js @@ -0,0 +1,37 @@ +/* + * 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. + */ + +function loop() +{ + var flag = true; + while(true) { + if (flag) { + terminate(); + } + flag = false; + }; +} + +function func1() +{ + var error = false; + var a = [{toString() {if(error) loop()}}]; + function Join() {return a.join()}; + Join(); + error = true; + Join(); +} + +func1(); \ No newline at end of file diff --git a/test/executiontest/js/termination_5.js b/test/executiontest/js/termination_5.js new file mode 100644 index 0000000000..34f6951b10 --- /dev/null +++ b/test/executiontest/js/termination_5.js @@ -0,0 +1,37 @@ +/* + * 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. + */ + +function loop() +{ + var flag = true; + while(true) { + if (flag) { + terminate(); + } + flag = false; + }; +} + +function func1() +{ + new Promise((resolve, reject) => { + resolve(true); + }).then((res) => { + loop(); + fail(); + }) +} + +func1(); \ No newline at end of file diff --git a/test/executiontest/js/termination_6.js b/test/executiontest/js/termination_6.js new file mode 100644 index 0000000000..920acc4ad1 --- /dev/null +++ b/test/executiontest/js/termination_6.js @@ -0,0 +1,32 @@ +/* + * 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. + */ + +function func1() +{ + new Promise((resolve, reject) => { + resolve(true); + }).then((res) => { + fail(); + }) + var flag = true; + while(true) { + if (flag) { + terminate(); + } + flag = false; + }; +} + +func1(); \ No newline at end of file diff --git a/test/executiontest/thread_termination_test.cpp b/test/executiontest/thread_termination_test.cpp new file mode 100644 index 0000000000..3d82a2803a --- /dev/null +++ b/test/executiontest/thread_termination_test.cpp @@ -0,0 +1,207 @@ +/* + * 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 + +#include "ecmascript/napi/include/dfx_jsnapi.h" +#include "ecmascript/napi/include/jsnapi.h" +#include "ecmascript/tests/test_helper.h" + +namespace panda::test { +using namespace panda; +using namespace panda::ecmascript; +using FunctionCallbackInfo = Local(*)(JsiRuntimeCallInfo *); +std::condition_variable semaphore; + +class ThreadTerminationTest : public testing::Test { +public: + static void SetUpTestCase() + { + GTEST_LOG_(INFO) << "SetUpTestCase"; + } + + static void TearDownTestCase() + { + GTEST_LOG_(INFO) << "TearDownCase"; + } + + void SetUp() override + { + TestHelper::CreateEcmaVMWithScope(vm_, thread_, scope_); + } + + void TearDown() override + { + TestHelper::DestroyEcmaVMWithScope(vm_, scope_); + } + + static Local TerminateThread(JsiRuntimeCallInfo *runtimeCallInfo); + static Local Fail(JsiRuntimeCallInfo *runtimeCallInfo); + static Local Signal(JsiRuntimeCallInfo *runtimeCallInfo); + + static void RegisterGlobalTemplate(const EcmaVM *vm, FunctionCallbackInfo terminate, FunctionCallbackInfo fail, + FunctionCallbackInfo signal) + { + [[maybe_unused]] EcmaHandleScope handleScope(vm->GetJSThread()); + Local globalObj = JSNApi::GetGlobalObject(vm); + globalObj->Set(vm, StringRef::NewFromUtf8(vm, "terminate"), FunctionRef::New( + const_cast(vm), terminate)); + globalObj->Set(vm, StringRef::NewFromUtf8(vm, "fail"), FunctionRef::New( + const_cast(vm), fail)); + globalObj->Set(vm, StringRef::NewFromUtf8(vm, "signal"), FunctionRef::New( + const_cast(vm), Signal)); + } + +protected: + EcmaVM *vm_ {nullptr}; + JSThread *thread_ = {nullptr}; + EcmaHandleScope *scope_ {nullptr}; +}; + +class TerminatorThread { +public: + explicit TerminatorThread(EcmaVM *vm) : vm_(vm) {} + ~TerminatorThread() = default; + + void Run() + { + thread_ = std::thread([this]() { + std::unique_lock lock(mutex_); + semaphore.wait(lock); + DFXJSNApi::TerminateExecution(vm_); + }); + } + + void RunWithSleep() + { + thread_ = std::thread([this]() { + usleep(1000000); // 1000000 : 1s for loop to execute + DFXJSNApi::TerminateExecution(vm_); + }); + } + + void Join() + { + thread_.join(); + } + +private: + EcmaVM *vm_ {nullptr}; + std::mutex mutex_; + std::thread thread_; +}; + +Local ThreadTerminationTest::TerminateThread(JsiRuntimeCallInfo *runtimeCallInfo) +{ + EcmaVM *vm = runtimeCallInfo->GetVM(); + DFXJSNApi::TerminateExecution(vm); + return JSValueRef::Undefined(vm); +} + +Local ThreadTerminationTest::Fail([[maybe_unused]]JsiRuntimeCallInfo *runtimeCallInfo) +{ + UNREACHABLE(); +} + +Local ThreadTerminationTest::Signal(JsiRuntimeCallInfo *runtimeCallInfo) +{ + EcmaVM *vm = runtimeCallInfo->GetVM(); + semaphore.notify_one(); + return JSValueRef::Undefined(vm); +} + +HWTEST_F_L0(ThreadTerminationTest, TerminateFromThreadItself) +{ + JSThread *thread = vm_->GetAssociatedJSThread(); + EcmaContext::MountContext(thread); + JSNApi::EnableUserUncaughtErrorHandler(vm_); + RegisterGlobalTemplate(vm_, TerminateThread, Fail, Signal); + TryCatch tryCatch(vm_); + std::string baseFileName = MODULE_ABC_PATH "termination_1.abc"; + JSNApi::Execute(vm_, baseFileName, "termination_1"); + EXPECT_TRUE(tryCatch.HasCaught()); + EcmaContext::UnmountContext(thread); +} + +HWTEST_F_L0(ThreadTerminationTest, TerminateFromOtherThread) +{ + JSThread *thread = vm_->GetAssociatedJSThread(); + EcmaContext::MountContext(thread); + TerminatorThread terminatorThread(vm_); + terminatorThread.Run(); + JSNApi::EnableUserUncaughtErrorHandler(vm_); + RegisterGlobalTemplate(vm_, TerminateThread, Fail, Signal); + TryCatch tryCatch(vm_); + std::string baseFileName = MODULE_ABC_PATH "termination_2.abc"; + JSNApi::Execute(vm_, baseFileName, "termination_2"); + EXPECT_TRUE(tryCatch.HasCaught()); + terminatorThread.Join(); + EcmaContext::UnmountContext(thread); +} + +HWTEST_F_L0(ThreadTerminationTest, TerminateFromOtherThreadWithoutCall) +{ + JSThread *thread = vm_->GetAssociatedJSThread(); + EcmaContext::MountContext(thread); + TerminatorThread terminatorThread(vm_); + terminatorThread.RunWithSleep(); + JSNApi::EnableUserUncaughtErrorHandler(vm_); + RegisterGlobalTemplate(vm_, TerminateThread, Fail, Signal); + TryCatch tryCatch(vm_); + std::string baseFileName = MODULE_ABC_PATH "termination_3.abc"; + JSNApi::Execute(vm_, baseFileName, "termination_3"); + EXPECT_TRUE(tryCatch.HasCaught()); + terminatorThread.Join(); + EcmaContext::UnmountContext(thread); +} + +HWTEST_F_L0(ThreadTerminationTest, TerminateClearArrayJoinStack) +{ + JSThread *thread = vm_->GetAssociatedJSThread(); + EcmaContext::MountContext(thread); + JSNApi::EnableUserUncaughtErrorHandler(vm_); + RegisterGlobalTemplate(vm_, TerminateThread, Fail, Signal); + TryCatch tryCatch(vm_); + std::string baseFileName = MODULE_ABC_PATH "termination_4.abc"; + JSNApi::Execute(vm_, baseFileName, "termination_4"); + EXPECT_TRUE(tryCatch.HasCaught()); + EcmaContext::UnmountContext(thread); +} + +HWTEST_F_L0(ThreadTerminationTest, TerminateInMicroTask) +{ + JSThread *thread = vm_->GetAssociatedJSThread(); + EcmaContext::MountContext(thread); + JSNApi::EnableUserUncaughtErrorHandler(vm_); + RegisterGlobalTemplate(vm_, TerminateThread, Fail, Signal); + TryCatch tryCatch(vm_); + std::string baseFileName = MODULE_ABC_PATH "termination_5.abc"; + JSNApi::Execute(vm_, baseFileName, "termination_5"); + EcmaContext::UnmountContext(thread); +} + +HWTEST_F_L0(ThreadTerminationTest, TerminateWithoutExecutingMicroTask) +{ + JSThread *thread = vm_->GetAssociatedJSThread(); + EcmaContext::MountContext(thread); + JSNApi::EnableUserUncaughtErrorHandler(vm_); + RegisterGlobalTemplate(vm_, TerminateThread, Fail, Signal); + TryCatch tryCatch(vm_); + std::string baseFileName = MODULE_ABC_PATH "termination_6.abc"; + JSNApi::Execute(vm_, baseFileName, "termination_6"); + EXPECT_TRUE(tryCatch.HasCaught()); + EcmaContext::UnmountContext(thread); +} +} // namespace panda::test diff --git a/test/resource/js_runtime/ohos_test.xml b/test/resource/js_runtime/ohos_test.xml index 22fef88a19..9f946fb9e1 100755 --- a/test/resource/js_runtime/ohos_test.xml +++ b/test/resource/js_runtime/ohos_test.xml @@ -1299,4 +1299,15 @@