/* * Copyright (c) 2025 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/builtins/builtins_promise_job.h" #include "ecmascript/global_env.h" #include "ecmascript/tests/test_helper.h" #include "ecmascript/module/napi_module_loader.h" #include "ecmascript/ecma_vm.h" #include "ecmascript/js_promise.h" #include "ecmascript/js_array.h" using namespace panda; using namespace panda::ecmascript; using namespace panda::ecmascript::builtins; using FunctionCallbackInfo = Local (*)(JsiRuntimeCallInfo *); namespace panda::test { class BuiltinsPromiseJobTest : public BaseTestWithScope { public: static Local MockGetModule(JsiRuntimeCallInfo *runtimeCallInfo); static Local MockGetModuleJSError(JsiRuntimeCallInfo *runtimeCallInfo); }; Local BuiltinsPromiseJobTest::MockGetModule(JsiRuntimeCallInfo *runtimeCallInfo) { auto *thread = runtimeCallInfo->GetThread(); auto vm = thread->GetEcmaVM(); Local requestPath = StringRef::NewFromUtf8(vm, "requestPath"); Local exportObejct = ObjectRef::New(vm); exportObejct->Set(vm, requestPath, runtimeCallInfo->GetCallArgRef(0)); return exportObejct; } Local BuiltinsPromiseJobTest::MockGetModuleJSError(JsiRuntimeCallInfo *runtimeCallInfo) { auto *thread = runtimeCallInfo->GetThread(); auto vm = thread->GetEcmaVM(); JsiFastNativeScope fastNativeScope(vm); Local error(JSValueRef::Undefined(vm)); error = Exception::Error(vm, runtimeCallInfo->GetCallArgRef(0)); Local codeKey = StringRef::NewFromUtf8(vm, "code"); Local codeValue = runtimeCallInfo->GetCallArgRef(0); Local errorObj(error); errorObj->Set(vm, codeKey, codeValue); JSNApi::ThrowException(vm, error); return runtimeCallInfo->GetCallArgRef(0); } // dynamic import static module after load 1.0 module failed HWTEST_F_L0(BuiltinsPromiseJobTest, DynamicImportJobCatchException) { /** * Both the handle and the stack are allocated using maloc. * When newJsError is called, the C interpreter will step back one frame before executing. * In the UT, there is only one frame, and stepping back causes it to step on the handle address. * This is a special scenario caused by the UT, and it would not occur during normal execution. */ if (!thread->IsAsmInterpreter()) { return; } auto vm = thread->GetEcmaVM(); ObjectFactory *factory = vm->GetFactory(); JSHandle env = vm->GetGlobalEnv(); auto globalConstants = thread->GlobalConstants(); JSArray *arr = JSArray::Cast(JSArray::ArrayCreate(thread, JSTaggedNumber(0)).GetTaggedValue().GetTaggedObject()); EXPECT_TRUE(arr != nullptr); JSHandle pandaObject(thread, arr); JSTaggedValue::SetProperty(thread, pandaObject, globalConstants->GetHandledGetModuleString(), JSNApiHelper::ToJSHandle(FunctionRef::New(const_cast(vm), MockGetModule))); Local globalObject = JSNApi::GetGlobalObject(vm); globalObject->Set(vm, JSNApiHelper::ToLocal(globalConstants->GetHandledPandaString()), JSNApiHelper::ToLocal(pandaObject)); JSHandle promiseFunc = env->GetPromiseFunction(); JSHandle jsPromise = JSHandle::Cast(factory->NewJSObjectByConstructor(JSHandle(promiseFunc), promiseFunc)); JSHandle resolvingFunctions = JSPromise::CreateResolvingFunctions(thread, jsPromise); JSHandle resolve(thread, resolvingFunctions->GetResolveFunction(thread)); JSHandle reject(thread, resolvingFunctions->GetRejectFunction(thread)); JSHandle dirPath(factory->NewFromASCII("./main.abc")); JSHandle specifier(factory->NewFromASCII("exportFile")); auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread, JSTaggedValue::Undefined(), 14); Local contextValue = JSNApi::GetCurrentContext(vm); JSHandle lexicalEnv(JSNApiHelper::ToJSHandle(contextValue)); JSHandle funHandle = factory->NewJSFunction(env); funHandle->SetLexicalEnv(thread, lexicalEnv.GetTaggedValue()); ecmaRuntimeCallInfo->SetFunction(funHandle.GetTaggedValue()); ecmaRuntimeCallInfo->SetThis(JSTaggedValue::Undefined()); ecmaRuntimeCallInfo->SetCallArg(0, resolve.GetTaggedValue()); ecmaRuntimeCallInfo->SetCallArg(1, reject.GetTaggedValue()); ecmaRuntimeCallInfo->SetCallArg(2, dirPath.GetTaggedValue()); ecmaRuntimeCallInfo->SetCallArg(3, specifier.GetTaggedValue()); ecmaRuntimeCallInfo->SetCallArg(4, JSTaggedValue::Undefined()); [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo); BuiltinsPromiseJob::DynamicImportJob(ecmaRuntimeCallInfo); TestHelper::TearDownFrame(thread, prev); JSHandle result(thread, jsPromise->GetPromiseResult(thread)); EXPECT_EQ(result->IsJSProxy(), true); JSHandle requestPath(factory->NewFromASCII("requestPath")); EXPECT_EQ(JSTaggedValue::SameValue(thread, JSTaggedValue::GetProperty(thread, result, requestPath).GetValue(), specifier), true); } // throw 1.2 load failed HWTEST_F_L0(BuiltinsPromiseJobTest, DynamicImportJobCatchException2) { /** * Both the handle and the stack are allocated using maloc. * When newJsError is called, the C interpreter will step back one frame before executing. * In the UT, there is only one frame, and stepping back causes it to step on the handle address. * This is a special scenario caused by the UT, and it would not occur during normal execution. */ if (!thread->IsAsmInterpreter()) { return; } auto vm = thread->GetEcmaVM(); ObjectFactory *factory = vm->GetFactory(); JSHandle env = vm->GetGlobalEnv(); auto globalConstants = thread->GlobalConstants(); JSArray *arr = JSArray::Cast(JSArray::ArrayCreate(thread, JSTaggedNumber(0)).GetTaggedValue().GetTaggedObject()); EXPECT_TRUE(arr != nullptr); JSHandle pandaObject(thread, arr); JSTaggedValue::SetProperty(thread, pandaObject, globalConstants->GetHandledGetModuleString(), JSNApiHelper::ToJSHandle(FunctionRef::New(const_cast(vm), MockGetModuleJSError))); Local globalObject = JSNApi::GetGlobalObject(vm); globalObject->Set(vm, JSNApiHelper::ToLocal(globalConstants->GetHandledPandaString()), JSNApiHelper::ToLocal(pandaObject)); JSHandle promiseFunc = env->GetPromiseFunction(); JSHandle jsPromise = JSHandle::Cast(factory->NewJSObjectByConstructor(JSHandle(promiseFunc), promiseFunc)); JSHandle resolvingFunctions = JSPromise::CreateResolvingFunctions(thread, jsPromise); JSHandle resolve(thread, resolvingFunctions->GetResolveFunction(thread)); JSHandle reject(thread, resolvingFunctions->GetRejectFunction(thread)); JSHandle dirPath(factory->NewFromASCII("./main.abc")); JSHandle specifier(factory->NewFromASCII("exportFile")); auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread, JSTaggedValue::Undefined(), 14); Local contextValue = JSNApi::GetCurrentContext(vm); JSHandle lexicalEnv(JSNApiHelper::ToJSHandle(contextValue)); JSHandle funHandle = factory->NewJSFunction(env); funHandle->SetLexicalEnv(thread, lexicalEnv.GetTaggedValue()); ecmaRuntimeCallInfo->SetFunction(funHandle.GetTaggedValue()); ecmaRuntimeCallInfo->SetThis(JSTaggedValue::Undefined()); ecmaRuntimeCallInfo->SetCallArg(0, resolve.GetTaggedValue()); ecmaRuntimeCallInfo->SetCallArg(1, reject.GetTaggedValue()); ecmaRuntimeCallInfo->SetCallArg(2, dirPath.GetTaggedValue()); ecmaRuntimeCallInfo->SetCallArg(3, specifier.GetTaggedValue()); ecmaRuntimeCallInfo->SetCallArg(4, JSTaggedValue::Undefined()); [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo); BuiltinsPromiseJob::DynamicImportJob(ecmaRuntimeCallInfo); TestHelper::TearDownFrame(thread, prev); JSHandle error(thread, jsPromise->GetPromiseResult(thread)); JSHandle code(factory->NewFromASCII("code")); JSHandle message = JSTaggedValue::GetProperty(thread, error, code).GetValue(); EXPECT_EQ(JSTaggedValue::SameValue(thread, message, specifier), true); thread->ClearException(); } }