diff --git a/BUILD.gn b/BUILD.gn index c2005e4c0917a9db70416017dacb5f7cddda3cf8..2185bad5d0f1dbd42403d1a4931ca862c3e5f745 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -494,8 +494,8 @@ if (!is_mingw && !is_mac) { ecma_debugger_source = [] if (!is_mingw && !is_mac) { ecma_debugger_source += [ - "ecmascript/tooling/interface/debugger_api.cpp", - "ecmascript/tooling/interface/js_debugger.cpp", + "ecmascript/tooling/backend/debugger_api.cpp", + "ecmascript/tooling/backend/js_debugger.cpp", ] } diff --git a/ecmascript/base/error_helper.cpp b/ecmascript/base/error_helper.cpp index 55b7d1970642806495859a980140396cea1b1bea..56770f6c66b62b511ee2ddba7775f20a716cb776 100644 --- a/ecmascript/base/error_helper.cpp +++ b/ecmascript/base/error_helper.cpp @@ -26,7 +26,7 @@ #include "ecmascript/js_tagged_value-inl.h" #include "ecmascript/jspandafile/js_pandafile_manager.h" #include "ecmascript/object_factory.h" -#include "ecmascript/tooling/js_pt_extractor.h" +#include "ecmascript/tooling/backend/js_pt_extractor.h" namespace panda::ecmascript::base { JSTaggedValue ErrorHelper::ErrorCommonToString(EcmaRuntimeCallInfo *argv, const ErrorType &errorType) diff --git a/ecmascript/jspandafile/js_pandafile_manager.h b/ecmascript/jspandafile/js_pandafile_manager.h index 0c9e679ef302a6e95421f0e09275f26772bc1e8f..b105757c5fab85ae5201bbeac7c510b2655ca9de 100644 --- a/ecmascript/jspandafile/js_pandafile_manager.h +++ b/ecmascript/jspandafile/js_pandafile_manager.h @@ -19,7 +19,7 @@ #include "ecmascript/mem/c_containers.h" #include "ecmascript/jspandafile/js_pandafile.h" #include "ecmascript/jspandafile/panda_file_translator.h" -#include "ecmascript/tooling/js_pt_extractor.h" +#include "ecmascript/tooling/backend/js_pt_extractor.h" #include "libpandafile/file.h" #include "libpandabase/utils/logger.h" diff --git a/ecmascript/napi/dfx_jsnapi.cpp b/ecmascript/napi/dfx_jsnapi.cpp index 5b9b2a667ec969735e04a892abaacd01719185d8..f1691c9144ba35889305d347a4e1c753d2a55694 100644 --- a/ecmascript/napi/dfx_jsnapi.cpp +++ b/ecmascript/napi/dfx_jsnapi.cpp @@ -40,13 +40,14 @@ using JSHandle = ecmascript::JSHandle; using ecmascript::FileStream; using ecmascript::FileDescriptorStream; -void DFXJSNApi::DumpHeapSnapshot(EcmaVM *vm, int dumpFormat, const std::string &path, bool isVmMode, bool isPrivate) +void DFXJSNApi::DumpHeapSnapshot(const EcmaVM *vm, int dumpFormat, + const std::string &path, bool isVmMode, bool isPrivate) { FileStream stream(path); DumpHeapSnapshot(vm, dumpFormat, &stream, nullptr, isVmMode, isPrivate); } -void DFXJSNApi::DumpHeapSnapshot(EcmaVM *vm, int dumpFormat, Stream *stream, Progress *progress, +void DFXJSNApi::DumpHeapSnapshot(const EcmaVM *vm, int dumpFormat, Stream *stream, Progress *progress, bool isVmMode, bool isPrivate) { ecmascript::HeapProfilerInterface *heapProfile = ecmascript::HeapProfilerInterface::GetInstance(vm); @@ -54,7 +55,7 @@ void DFXJSNApi::DumpHeapSnapshot(EcmaVM *vm, int dumpFormat, Stream *stream, Pro ecmascript::HeapProfilerInterface::Destroy(vm); } -void DFXJSNApi::DumpHeapSnapshot([[maybe_unused]] EcmaVM *vm, [[maybe_unused]] int dumpFormat, +void DFXJSNApi::DumpHeapSnapshot([[maybe_unused]] const EcmaVM *vm, [[maybe_unused]] int dumpFormat, [[maybe_unused]] bool isVmMode, [[maybe_unused]] bool isPrivate) { #if defined(ENABLE_DUMP_IN_FAULTLOG) @@ -69,7 +70,7 @@ void DFXJSNApi::DumpHeapSnapshot([[maybe_unused]] EcmaVM *vm, [[maybe_unused]] i #endif } -bool DFXJSNApi::BuildNativeAndJsBackStackTrace(EcmaVM *vm, std::string &stackTraceStr) +bool DFXJSNApi::BuildNativeAndJsBackStackTrace(const EcmaVM *vm, std::string &stackTraceStr) { CString trace = ecmascript::base::ErrorHelper::BuildNativeAndJsStackTrace(vm->GetJSThreadNoCheck()); stackTraceStr = CstringConvertToStdString(trace); @@ -79,19 +80,19 @@ bool DFXJSNApi::BuildNativeAndJsBackStackTrace(EcmaVM *vm, std::string &stackTra return true; } -bool DFXJSNApi::StartHeapTracking(EcmaVM *vm, double timeInterval, bool isVmMode) +bool DFXJSNApi::StartHeapTracking(const EcmaVM *vm, double timeInterval, bool isVmMode) { ecmascript::HeapProfilerInterface *heapProfile = ecmascript::HeapProfilerInterface::GetInstance(vm); return heapProfile->StartHeapTracking(timeInterval, isVmMode); } -bool DFXJSNApi::StopHeapTracking(EcmaVM *vm, const std::string &filePath) +bool DFXJSNApi::StopHeapTracking(const EcmaVM *vm, const std::string &filePath) { FileStream stream(filePath); return StopHeapTracking(vm, &stream, nullptr); } -bool DFXJSNApi::StopHeapTracking(EcmaVM *vm, Stream* stream, Progress *progress) +bool DFXJSNApi::StopHeapTracking(const EcmaVM *vm, Stream* stream, Progress *progress) { bool result = false; ecmascript::HeapProfilerInterface *heapProfile = ecmascript::HeapProfilerInterface::GetInstance(vm); @@ -116,17 +117,17 @@ void DFXJSNApi::StopRuntimeStat(EcmaVM *vm) vm->SetRuntimeStatEnable(false); } -size_t DFXJSNApi::GetArrayBufferSize(EcmaVM *vm) +size_t DFXJSNApi::GetArrayBufferSize(const EcmaVM *vm) { return vm->GetHeap()->GetArrayBufferSize(); } -size_t DFXJSNApi::GetHeapTotalSize(EcmaVM *vm) +size_t DFXJSNApi::GetHeapTotalSize(const EcmaVM *vm) { return vm->GetHeap()->GetCommittedSize(); } -size_t DFXJSNApi::GetHeapUsedSize(EcmaVM *vm) +size_t DFXJSNApi::GetHeapUsedSize(const EcmaVM *vm) { return vm->GetHeap()->GetHeapObjectSize(); } diff --git a/ecmascript/napi/include/dfx_jsnapi.h b/ecmascript/napi/include/dfx_jsnapi.h index 479b8fc57c6df7384804a156aa9eb1db2607b9ed..cf8a1ae76891661be67b9d787f5f28255dd950ba 100644 --- a/ecmascript/napi/include/dfx_jsnapi.h +++ b/ecmascript/napi/include/dfx_jsnapi.h @@ -43,22 +43,22 @@ class PUBLIC_API DFXJSNApi { public: // progress pointer is used to report the object number for IDE. // isVmMode means the internal class in vm is visible. isPrivate means the number and string is not visible. - static void DumpHeapSnapshot(EcmaVM *vm, int dumpFormat, const std::string &path, bool isVmMode = true, + static void DumpHeapSnapshot(const EcmaVM *vm, int dumpFormat, const std::string &path, bool isVmMode = true, bool isPrivate = false); - static void DumpHeapSnapshot(EcmaVM *vm, int dumpFormat, Stream *stream, Progress *progress = nullptr, + static void DumpHeapSnapshot(const EcmaVM *vm, int dumpFormat, Stream *stream, Progress *progress = nullptr, bool isVmMode = true, bool isPrivate = false); - static void DumpHeapSnapshot(EcmaVM *vm, int dumpFormat, bool isVmMode = true, bool isPrivate = false); + static void DumpHeapSnapshot(const EcmaVM *vm, int dumpFormat, bool isVmMode = true, bool isPrivate = false); - static bool BuildNativeAndJsBackStackTrace(EcmaVM *vm, std::string &stackTraceStr); - static bool StartHeapTracking(EcmaVM *vm, double timeInterval, bool isVmMode = true); - static bool StopHeapTracking(EcmaVM *vm, const std::string &filePath); - static bool StopHeapTracking(EcmaVM *vm, Stream *stream, Progress *progress = nullptr); + static bool BuildNativeAndJsBackStackTrace(const EcmaVM *vm, std::string &stackTraceStr); + static bool StartHeapTracking(const EcmaVM *vm, double timeInterval, bool isVmMode = true); + static bool StopHeapTracking(const EcmaVM *vm, const std::string &filePath); + static bool StopHeapTracking(const EcmaVM *vm, Stream *stream, Progress *progress = nullptr); static void PrintStatisticResult(const EcmaVM *vm); static void StartRuntimeStat(EcmaVM *vm); static void StopRuntimeStat(EcmaVM *vm); - static size_t GetArrayBufferSize(EcmaVM *vm); - static size_t GetHeapTotalSize(EcmaVM *vm); - static size_t GetHeapUsedSize(EcmaVM *vm); + static size_t GetArrayBufferSize(const EcmaVM *vm); + static size_t GetHeapTotalSize(const EcmaVM *vm); + static size_t GetHeapUsedSize(const EcmaVM *vm); // profile generator #if defined(ECMASCRIPT_SUPPORT_CPUPROFILER) diff --git a/ecmascript/napi/include/jsnapi.h b/ecmascript/napi/include/jsnapi.h index 77dae2041b60f9d85899d7a6e6efccf48065dede..0af9e28c187056e2abd10a4b18db51a91734031f 100644 --- a/ecmascript/napi/include/jsnapi.h +++ b/ecmascript/napi/include/jsnapi.h @@ -18,6 +18,7 @@ #include #include +#include #include #include diff --git a/ecmascript/stubs/runtime_stubs.cpp b/ecmascript/stubs/runtime_stubs.cpp index 021026118434c818f47930ecd630cfb53490b365..10464d0c7e97e0a2ef010cdb55f4341ff8ea6823 100644 --- a/ecmascript/stubs/runtime_stubs.cpp +++ b/ecmascript/stubs/runtime_stubs.cpp @@ -38,7 +38,6 @@ #include "ecmascript/message_string.h" #include "ecmascript/object_factory.h" #include "ecmascript/tagged_dictionary.h" -#include "ecmascript/tooling/test/utils/test_util.h" #include "libpandabase/utils/string_helpers.h" #include "ecmascript/ts_types/ts_loader.h" diff --git a/ecmascript/stubs/test_runtime_stubs.cpp b/ecmascript/stubs/test_runtime_stubs.cpp index 5569866b0ce7b4eb6c090ff2a537b93a576f9c95..7011eacc8efb4cf3a4929e5e0ae4fda86dfe15b6 100644 --- a/ecmascript/stubs/test_runtime_stubs.cpp +++ b/ecmascript/stubs/test_runtime_stubs.cpp @@ -20,7 +20,6 @@ #include "ecmascript/global_env.h" #include "ecmascript/runtime_call_id.h" #include "ecmascript/js_function.h" -#include "ecmascript/tooling/test/utils/test_util.h" namespace panda::ecmascript { #if defined(__clang__) @@ -107,14 +106,14 @@ DEF_RUNTIME_STUBS(DefineProxyFunc) // 1. handler has no "Call" JSFunction *function = env->GetObjectFunction().GetObject(); JSHandle dynclass(thread, function); - ASSERT_TRUE(targetHandle->IsECMAObject()); + ASSERT(targetHandle->IsECMAObject()); JSHandle handlerHandle(factory->NewJSObjectByConstructor(JSHandle(dynclass), dynclass)); - ASSERT_TRUE(handlerHandle->IsECMAObject()); - ASSERT_TRUE(targetHandle->IsECMAObject()); + ASSERT(handlerHandle->IsECMAObject()); + ASSERT(targetHandle->IsECMAObject()); JSHandle proxyHandle = JSProxy::ProxyCreate(thread, targetHandle, handlerHandle); - ASSERT_TRUE(*proxyHandle != nullptr); + ASSERT(*proxyHandle != nullptr); // check taggedvalue proxyHandle.GetTaggedValue().D(); return proxyHandle.GetTaggedValue().GetRawData(); @@ -124,14 +123,14 @@ DEF_RUNTIME_STUBS(DefineProxyHandler) { RUNTIME_STUBS_HEADER(DefineProxyHandler); CONVERT_ARG_HANDLE_CHECKED(JSTaggedValue, funcHandle, 0); - ASSERT_TRUE(funcHandle->IsECMAObject()); + ASSERT(funcHandle->IsECMAObject()); ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle env = thread->GetEcmaVM()->GetGlobalEnv(); JSFunction* function = env->GetObjectFunction().GetObject(); JSHandle dynclass(thread, function); JSHandle handlerHandle(factory->NewJSObjectByConstructor(JSHandle(dynclass), dynclass)); - ASSERT_TRUE(handlerHandle->IsECMAObject()); + ASSERT(handlerHandle->IsECMAObject()); // 1. handler has "Call" JSHandle funcKey = thread->GlobalConstants()->GetHandledApplyString(); JSObject::SetProperty(thread, JSHandle(handlerHandle), funcKey, funcHandle); @@ -145,14 +144,14 @@ DEF_RUNTIME_STUBS(DefineProxyFunc2) CONVERT_ARG_HANDLE_CHECKED(JSTaggedValue, targetHandle, 0); CONVERT_ARG_HANDLE_CHECKED(JSTaggedValue, handlerHandle, 1); // 1. handler has "Call" - ASSERT_TRUE(handlerHandle->IsECMAObject()); - ASSERT_TRUE(targetHandle->IsECMAObject()); + ASSERT(handlerHandle->IsECMAObject()); + ASSERT(targetHandle->IsECMAObject()); JSHandle proxyHandle = JSProxy::ProxyCreate(thread, targetHandle, handlerHandle); targetHandle.GetTaggedValue().D(); handlerHandle.GetTaggedValue().D(); proxyHandle.GetTaggedValue().D(); - ASSERT_TRUE(*proxyHandle != nullptr); + ASSERT(*proxyHandle != nullptr); return proxyHandle.GetTaggedValue().GetRawData(); } @@ -160,7 +159,7 @@ DEF_RUNTIME_STUBS(DumpTaggedType) { RUNTIME_STUBS_HEADER(DumpTaggedType); CONVERT_ARG_HANDLE_CHECKED(JSTaggedValue, value, 0); - ASSERT_TRUE(value->IsECMAObject()); + ASSERT(value->IsECMAObject()); value->D(); return value.GetTaggedValue().GetRawData(); } diff --git a/ecmascript/tooling/BUILD.gn b/ecmascript/tooling/BUILD.gn index b779cb5ac312d650794ff836173de370a96d8137..f829fc4f636e6add694556f5867603eb6e2afff0 100644 --- a/ecmascript/tooling/BUILD.gn +++ b/ecmascript/tooling/BUILD.gn @@ -29,10 +29,11 @@ config("ark_ecma_debugger_config") { debugger_sources = [ "agent/debugger_impl.cpp", "agent/heapprofiler_impl.cpp", - "agent/js_backend.cpp", - "agent/js_pt_hooks.cpp", "agent/profiler_impl.cpp", "agent/runtime_impl.cpp", + "backend/debugger_executor.cpp", + "backend/js_pt_extractor.cpp", + "backend/js_pt_hooks.cpp", "base/pt_events.cpp", "base/pt_params.cpp", "base/pt_returns.cpp", @@ -40,7 +41,6 @@ debugger_sources = [ "base/pt_types.cpp", "debugger_service.cpp", "dispatcher.cpp", - "js_pt_extractor.cpp", "protocol_handler.cpp", ] diff --git a/ecmascript/tooling/agent/debugger_impl.cpp b/ecmascript/tooling/agent/debugger_impl.cpp index da2718e684547406b87993d6257082ca59fe2c8a..0bf104a3398e710fee1bfec0e65a976bd3c612d0 100644 --- a/ecmascript/tooling/agent/debugger_impl.cpp +++ b/ecmascript/tooling/agent/debugger_impl.cpp @@ -15,40 +15,248 @@ #include "ecmascript/tooling/agent/debugger_impl.h" +#include + +#include "ecmascript/jspandafile/js_pandafile_manager.h" +#include "ecmascript/napi/jsnapi_helper.h" #include "ecmascript/tooling/base/pt_events.h" #include "ecmascript/tooling/base/pt_params.h" #include "ecmascript/tooling/base/pt_returns.h" +#include "ecmascript/tooling/base/pt_types.h" +#include "ecmascript/tooling/backend/debugger_executor.h" #include "ecmascript/tooling/dispatcher.h" -#include "ecmascript/tooling/front_end.h" +#include "ecmascript/tooling/protocol_channel.h" #include "libpandabase/utils/logger.h" namespace panda::ecmascript::tooling { -DebuggerImpl::DispatcherImpl::DispatcherImpl(FrontEnd *frontend, std::unique_ptr debugger) - : DispatcherBase(frontend), debugger_(std::move(debugger)) -{ - dispatcherTable_["enable"] = &DebuggerImpl::DispatcherImpl::Enable; - dispatcherTable_["disable"] = &DebuggerImpl::DispatcherImpl::Disable; - dispatcherTable_["evaluateOnCallFrame"] = &DebuggerImpl::DispatcherImpl::EvaluateOnCallFrame; - dispatcherTable_["getPossibleBreakpoints"] = &DebuggerImpl::DispatcherImpl::GetPossibleBreakpoints; - dispatcherTable_["getScriptSource"] = &DebuggerImpl::DispatcherImpl::GetScriptSource; - dispatcherTable_["pause"] = &DebuggerImpl::DispatcherImpl::Pause; - dispatcherTable_["removeBreakpoint"] = &DebuggerImpl::DispatcherImpl::RemoveBreakpoint; - dispatcherTable_["resume"] = &DebuggerImpl::DispatcherImpl::Resume; - dispatcherTable_["setAsyncCallStackDepth"] = &DebuggerImpl::DispatcherImpl::SetAsyncCallStackDepth; - dispatcherTable_["setBreakpointByUrl"] = &DebuggerImpl::DispatcherImpl::SetBreakpointByUrl; - dispatcherTable_["setPauseOnExceptions"] = &DebuggerImpl::DispatcherImpl::SetPauseOnExceptions; - dispatcherTable_["stepInto"] = &DebuggerImpl::DispatcherImpl::StepInto; - dispatcherTable_["stepOut"] = &DebuggerImpl::DispatcherImpl::StepOut; - dispatcherTable_["stepOver"] = &DebuggerImpl::DispatcherImpl::StepOver; - dispatcherTable_["setBlackboxPatterns"] = &DebuggerImpl::DispatcherImpl::SetBlackboxPatterns; +using namespace boost::beast::detail; +using namespace std::placeholders; + +using ObjectType = RemoteObject::TypeName; +using ObjectSubType = RemoteObject::SubTypeName; +using ObjectClassName = RemoteObject::ClassName; + +#ifdef DEBUGGER_TEST +const CString DATA_APP_PATH = "/"; +#else +const CString DATA_APP_PATH = "/data/"; +#endif + +DebuggerImpl::DebuggerImpl(const EcmaVM *vm, ProtocolChannel *channel, RuntimeImpl *runtime) + : vm_(vm), frontend_(channel), runtime_(runtime) +{ + hooks_ = std::make_unique(this); + + jsDebugger_ = DebuggerApi::CreateJSDebugger(vm_); + DebuggerApi::RegisterHooks(jsDebugger_, hooks_.get()); + + DebuggerExecutor::Initialize(vm_); + updaterFunc_ = std::bind(&DebuggerImpl::UpdateScopeObject, this, _1, _2, _3); + vm_->GetJsDebuggerManager()->SetLocalScopeUpdater(&updaterFunc_); +} + +DebuggerImpl::~DebuggerImpl() +{ + DebuggerApi::DestroyJSDebugger(jsDebugger_); +} + +bool DebuggerImpl::NotifyScriptParsed(ScriptId scriptId, const CString &fileName) +{ + if (fileName.substr(0, DATA_APP_PATH.length()) != DATA_APP_PATH) { + LOG(WARNING, DEBUGGER) << "NotifyScriptParsed: unsupport file: " << fileName; + return false; + } + + auto scriptFunc = []([[maybe_unused]] PtScript *script) -> bool { + return true; + }; + if (MatchScripts(scriptFunc, fileName, ScriptMatchType::FILE_NAME)) { + LOG(WARNING, DEBUGGER) << "NotifyScriptParsed: already loaded: " << fileName; + return false; + } + const JSPandaFile *jsPandaFile = nullptr; + JSPandaFileManager::GetInstance()->EnumerateJSPandaFiles([&jsPandaFile, &fileName]( + const panda::ecmascript::JSPandaFile *pf) { + if (pf->GetJSPandaFileDesc() == fileName) { + jsPandaFile = pf; + return false; + } + return true; + }); + if (jsPandaFile == nullptr) { + LOG(ERROR, DEBUGGER) << "NotifyScriptParsed: unknown file: " << fileName; + return false; + } + + JSPtExtractor *extractor = GetExtractor(jsPandaFile); + if (extractor == nullptr) { + LOG(ERROR, DEBUGGER) << "NotifyScriptParsed: Unsupported file: " << fileName; + return false; + } + + auto mainMethodIndex = panda_file::File::EntityId(jsPandaFile->GetMainMethodIndex()); + const CString &source = extractor->GetSourceCode(mainMethodIndex); + const CString &url = extractor->GetSourceFile(mainMethodIndex); + const uint32_t MIN_SOURCE_CODE_LENGTH = 5; // maybe return 'ANDA' when source code is empty + if (source.size() < MIN_SOURCE_CODE_LENGTH) { + LOG(ERROR, DEBUGGER) << "NotifyScriptParsed: invalid file: " << fileName; + return false; + } + // store here for performance of get extractor from url + extractors_[url] = extractor; + + // Notify script parsed event + std::unique_ptr script = std::make_unique(scriptId, fileName, url, source); + + frontend_.ScriptParsed(vm_, script); + + // Store parsed script in map + scripts_[script->GetScriptId()] = std::move(script); + return true; +} + +bool DebuggerImpl::NotifySingleStep(const JSPtLocation &location) +{ + if (UNLIKELY(pauseOnNextByteCode_)) { + if (IsSkipLine(location)) { + return false; + } + pauseOnNextByteCode_ = false; + LOG(INFO, DEBUGGER) << "StepComplete: pause on next bytecode"; + return true; + } + + if (LIKELY(singleStepper_ == nullptr)) { + return false; + } + + // step not complete + if (!singleStepper_->StepComplete(location.GetBytecodeOffset())) { + return false; + } + + // skip unknown file or special line -1 + if (IsSkipLine(location)) { + return false; + } + + LOG(INFO, DEBUGGER) << "StepComplete: pause on current byte_code"; + return true; +} + +bool DebuggerImpl::IsSkipLine(const JSPtLocation &location) +{ + JSPtExtractor *extractor = nullptr; + auto scriptFunc = [this, &extractor](PtScript *script) -> bool { + extractor = GetExtractor(script->GetUrl()); + return true; + }; + if (!MatchScripts(scriptFunc, location.GetPandaFile(), ScriptMatchType::FILE_NAME) || extractor == nullptr) { + LOG(INFO, DEBUGGER) << "StepComplete: skip unknown file"; + return true; + } + + auto callbackFunc = [](int32_t line) -> bool { + return line == JSPtExtractor::SPECIAL_LINE_MARK; + }; + File::EntityId methodId = location.GetMethodId(); + uint32_t offset = location.GetBytecodeOffset(); + if (extractor->MatchLineWithOffset(callbackFunc, methodId, offset)) { + LOG(INFO, DEBUGGER) << "StepComplete: skip -1"; + return true; + } + + return false; +} + +void DebuggerImpl::NotifyPaused(std::optional location, PauseReason reason) +{ + if (!pauseOnException_ && reason == EXCEPTION) { + return; + } + Local exception = DebuggerApi::GetAndClearException(vm_); + + CVector hitBreakpoints; + if (location.has_value()) { + BreakpointDetails detail; + JSPtExtractor *extractor = nullptr; + auto scriptFunc = [this, &extractor, &detail](PtScript *script) -> bool { + detail.url_ = script->GetUrl(); + extractor = GetExtractor(detail.url_); + return true; + }; + auto callbackLineFunc = [&detail](int32_t line) -> bool { + detail.line_ = line; + return true; + }; + auto callbackColumnFunc = [&detail](int32_t column) -> bool { + detail.column_ = column; + return true; + }; + File::EntityId methodId = location->GetMethodId(); + uint32_t offset = location->GetBytecodeOffset(); + if (!MatchScripts(scriptFunc, location->GetPandaFile(), ScriptMatchType::FILE_NAME) || + extractor == nullptr || !extractor->MatchLineWithOffset(callbackLineFunc, methodId, offset) || + !extractor->MatchColumnWithOffset(callbackColumnFunc, methodId, offset)) { + LOG(ERROR, DEBUGGER) << "NotifyPaused: unknown " << location->GetPandaFile(); + return; + } + hitBreakpoints.emplace_back(BreakpointDetails::ToString(detail)); + } + + // Do something cleaning on paused + CleanUpOnPaused(); + + // Notify paused event + CVector> callFrames; + if (!GenerateCallFrames(&callFrames)) { + LOG(ERROR, DEBUGGER) << "NotifyPaused: GenerateCallFrames failed"; + return; + } + std::unique_ptr paused = std::make_unique(); + paused->SetCallFrames(std::move(callFrames)).SetReason(reason).SetHitBreakpoints(std::move(hitBreakpoints)); + if (reason == EXCEPTION && exception->IsError()) { + std::unique_ptr tmpException = RemoteObject::FromTagged(vm_, exception); + paused->SetData(std::move(tmpException)); + } + frontend_.Paused(vm_, std::move(paused)); + + // Waiting for Debugger + frontend_.WaitForDebugger(vm_); + DebuggerApi::SetException(vm_, exception); +} + +void DebuggerImpl::NotifyPendingJobEntry() +{ + if (singleStepper_ != nullptr) { + singleStepper_.reset(); + pauseOnNextByteCode_ = true; + } } void DebuggerImpl::DispatcherImpl::Dispatch(const DispatchRequest &request) { - CString method = request.GetMethod(); + static CUnorderedMap dispatcherTable { + { "enable", &DebuggerImpl::DispatcherImpl::Enable }, + { "disable", &DebuggerImpl::DispatcherImpl::Disable }, + { "evaluateOnCallFrame", &DebuggerImpl::DispatcherImpl::EvaluateOnCallFrame }, + { "getPossibleBreakpoints", &DebuggerImpl::DispatcherImpl::GetPossibleBreakpoints }, + { "getScriptSource", &DebuggerImpl::DispatcherImpl::GetScriptSource }, + { "pause", &DebuggerImpl::DispatcherImpl::Pause }, + { "removeBreakpoint", &DebuggerImpl::DispatcherImpl::RemoveBreakpoint }, + { "resume", &DebuggerImpl::DispatcherImpl::Resume }, + { "setAsyncCallStackDepth", &DebuggerImpl::DispatcherImpl::SetAsyncCallStackDepth }, + { "setBreakpointByUrl", &DebuggerImpl::DispatcherImpl::SetBreakpointByUrl }, + { "setPauseOnExceptions", &DebuggerImpl::DispatcherImpl::SetPauseOnExceptions }, + { "stepInto", &DebuggerImpl::DispatcherImpl::StepInto }, + { "stepOut", &DebuggerImpl::DispatcherImpl::StepOut }, + { "stepOver", &DebuggerImpl::DispatcherImpl::StepOver } + }; + + const CString &method = request.GetMethod(); LOG(DEBUG, DEBUGGER) << "dispatch [" << method << "] to DebuggerImpl"; - auto entry = dispatcherTable_.find(method); - if (entry != dispatcherTable_.end() && entry->second != nullptr) { + auto entry = dispatcherTable.find(method); + if (entry != dispatcherTable.end() && entry->second != nullptr) { (this->*(entry->second))(request); } else { SendResponse(request, DispatchResponse::Fail("Unknown method: " + method), nullptr); @@ -229,20 +437,84 @@ void DebuggerImpl::DispatcherImpl::SetBlackboxPatterns(const DispatchRequest &re SendResponse(request, response, std::move(result)); } +bool DebuggerImpl::Frontend::AllowNotify(const EcmaVM *vm) const +{ + return vm->GetJsDebuggerManager()->IsDebugMode() && channel_ != nullptr; +} + +void DebuggerImpl::Frontend::BreakpointResolved(const EcmaVM *vm) +{ + if (!AllowNotify(vm)) { + return; + } + + auto breakpointResolved = std::make_unique(); + channel_->SendNotification(std::move(breakpointResolved)); +} + +void DebuggerImpl::Frontend::Paused(const EcmaVM *vm, std::unique_ptr paused) +{ + if (!AllowNotify(vm)) { + return; + } + + channel_->SendNotification(std::move(paused)); +} + +void DebuggerImpl::Frontend::Resumed(const EcmaVM *vm) +{ + if (!AllowNotify(vm)) { + return; + } + + channel_->RunIfWaitingForDebugger(); + auto resumed = std::make_unique(); + channel_->SendNotification(std::move(resumed)); +} + +void DebuggerImpl::Frontend::ScriptFailedToParse(const EcmaVM *vm) +{ + if (!AllowNotify(vm)) { + return; + } + + auto scriptFailedToParse = std::make_unique(); + channel_->SendNotification(std::move(scriptFailedToParse)); +} + +void DebuggerImpl::Frontend::ScriptParsed(const EcmaVM *vm, const std::unique_ptr &script) +{ + if (!AllowNotify(vm)) { + return; + } + + auto scriptParsed = ScriptParsed::Create(script); + channel_->SendNotification(std::move(scriptParsed)); +} + +void DebuggerImpl::Frontend::WaitForDebugger(const EcmaVM *vm) +{ + if (!AllowNotify(vm)) { + return; + } + + channel_->WaitForDebugger(); +} + DispatchResponse DebuggerImpl::Enable([[maybe_unused]] std::unique_ptr params, UniqueDebuggerId *id) { ASSERT(id != nullptr); *id = 0; - auto ecmaVm = const_cast(backend_->GetEcmaVm()); - ecmaVm->GetJsDebuggerManager()->SetDebugMode(true); - backend_->NotifyAllScriptParsed(); + vm_->GetJsDebuggerManager()->SetDebugMode(true); + for (auto &script : scripts_) { + frontend_.ScriptParsed(vm_, script.second); + } return DispatchResponse::Ok(); } DispatchResponse DebuggerImpl::Disable() { - auto ecmaVm = const_cast(backend_->GetEcmaVm()); - ecmaVm->GetJsDebuggerManager()->SetDebugMode(false); + vm_->GetJsDebuggerManager()->SetDebugMode(false); return DispatchResponse::Ok(); } @@ -251,30 +523,78 @@ DispatchResponse DebuggerImpl::EvaluateOnCallFrame(std::unique_ptrGetCallFrameId(); CString expression = params->GetExpression(); - return DispatchResponse::Create(backend_->EvaluateValue(callFrameId, expression, result)); + if (callFrameId < 0 || callFrameId >= callFrameHandlers_.size()) { + return DispatchResponse::Fail("Invalid callFrameId."); + } + + CString dest; + if (!DecodeAndCheckBase64(expression, dest)) { + LOG(ERROR, DEBUGGER) << "EvaluateValue: base64 decode failed"; + return DispatchResponse::Create(CmptEvaluateValue(callFrameId, expression, result)); + } + + auto funcRef = DebuggerApi::GenerateFuncFromBuffer(vm_, dest.data(), dest.size(), + JSPandaFile::ENTRY_MAIN_FUNCTION); + auto res = DebuggerApi::EvaluateViaFuncCall(const_cast(vm_), funcRef, + callFrameHandlers_[callFrameId]); + if (vm_->GetJSThread()->HasPendingException()) { + LOG(ERROR, DEBUGGER) << "EvaluateValue: has pending exception"; + CString msg; + DebuggerApi::HandleUncaughtException(vm_, msg); + *result = RemoteObject::FromTagged(vm_, + Exception::EvalError(vm_, StringRef::NewFromUtf8(vm_, msg.data()))); + return DispatchResponse::Fail(msg); + } + + *result = RemoteObject::FromTagged(vm_, res); + runtime_->CacheObjectIfNeeded(res, (*result).get()); + return DispatchResponse::Ok(); } DispatchResponse DebuggerImpl::GetPossibleBreakpoints(std::unique_ptr params, CVector> *locations) { - Location *locationStart = params->GetStart(); - Location *locationEnd = params->GetEnd(); + Location *start = params->GetStart(); + auto iter = scripts_.find(start->GetScriptId()); + if (iter == scripts_.end()) { + return DispatchResponse::Fail("Unknown file name."); + } + JSPtExtractor *extractor = GetExtractor(iter->second->GetUrl()); + if (extractor == nullptr) { + LOG(ERROR, DEBUGGER) << "GetPossibleBreakpoints: extractor is null"; + return DispatchResponse::Fail("Unknown file name."); + } - return DispatchResponse::Create(backend_->GetPossibleBreakpoints(locationStart, locationEnd, locations)); + int32_t line = start->GetLine(); + int32_t column = start->GetColumn(); + auto callbackFunc = []([[maybe_unused]] File::EntityId id, [[maybe_unused]] uint32_t offset) -> bool { + return true; + }; + if (extractor->MatchWithLocation(callbackFunc, line, column)) { + std::unique_ptr location = std::make_unique(); + location->SetScriptId(start->GetScriptId()).SetLine(line).SetColumn(column); + locations->emplace_back(std::move(location)); + } + return DispatchResponse::Ok(); } DispatchResponse DebuggerImpl::GetScriptSource(std::unique_ptr params, CString *source) { - if (!backend_->GetScriptSource(params->GetScriptId(), source)) { - return DispatchResponse::Fail("unknown script id: " + ToCString(params->GetScriptId())); + ScriptId scriptId = params->GetScriptId(); + auto iter = scripts_.find(scriptId); + if (iter == scripts_.end()) { + *source = ""; + return DispatchResponse::Fail("unknown script id: " + ToCString(scriptId)); } + *source = iter->second->GetScriptSource(); return DispatchResponse::Ok(); } DispatchResponse DebuggerImpl::Pause() { - return DispatchResponse::Create(backend_->Pause()); + pauseOnNextByteCode_ = true; + return DispatchResponse::Ok(); } DispatchResponse DebuggerImpl::RemoveBreakpoint(std::unique_ptr params) @@ -285,16 +605,46 @@ DispatchResponse DebuggerImpl::RemoveBreakpoint(std::unique_ptrRemoveBreakpoint(metaData)); + JSPtExtractor *extractor = GetExtractor(metaData.url_); + if (extractor == nullptr) { + LOG(ERROR, DEBUGGER) << "RemoveBreakpoint: extractor is null"; + return DispatchResponse::Fail("Unknown file name."); + } + + CString fileName; + auto scriptFunc = [&fileName](PtScript *script) -> bool { + fileName = script->GetFileName(); + return true; + }; + if (!MatchScripts(scriptFunc, metaData.url_, ScriptMatchType::URL)) { + LOG(ERROR, DEBUGGER) << "RemoveBreakpoint: Unknown url: " << metaData.url_; + return DispatchResponse::Fail("Unknown file name."); + } + + auto callbackFunc = [this, fileName](File::EntityId id, uint32_t offset) -> bool { + JSPtLocation location {fileName.c_str(), id, offset}; + return DebuggerApi::RemoveBreakpoint(jsDebugger_, location); + }; + if (!extractor->MatchWithLocation(callbackFunc, metaData.line_, metaData.column_)) { + LOG(ERROR, DEBUGGER) << "failed to set breakpoint location number: " + << metaData.line_ << ":" << metaData.column_; + return DispatchResponse::Fail("Breakpoint not found."); + } + + LOG(INFO, DEBUGGER) << "remove breakpoint32_t line number:" << metaData.line_; + return DispatchResponse::Ok(); } DispatchResponse DebuggerImpl::Resume([[maybe_unused]] std::unique_ptr params) { - return DispatchResponse::Create(backend_->Resume()); + frontend_.Resumed(vm_); + singleStepper_.reset(); + return DispatchResponse::Ok(); } DispatchResponse DebuggerImpl::SetAsyncCallStackDepth() { + LOG(ERROR, DEBUGGER) << "SetAsyncCallStackDepth not support now."; return DispatchResponse::Ok(); } @@ -302,39 +652,467 @@ DispatchResponse DebuggerImpl::SetBreakpointByUrl(std::unique_ptr> *outLocations) { - return DispatchResponse::Create( - backend_->SetBreakpointByUrl(params->GetUrl(), params->GetLine(), params->GetColumn(), - (params->HasCondition() ? params->GetCondition() : std::optional {}), outId, outLocations)); + const CString &url = params->GetUrl(); + int32_t lineNumber = params->GetLine(); + int32_t columnNumber = params->GetColumn(); + std::optional condition = params->HasCondition() ? params->GetCondition() : std::optional {}; + + JSPtExtractor *extractor = GetExtractor(url); + if (extractor == nullptr) { + LOG(ERROR, DEBUGGER) << "SetBreakpointByUrl: extractor is null"; + return DispatchResponse::Fail("Unknown file name."); + } + + ScriptId scriptId; + CString fileName; + auto scriptFunc = [&scriptId, &fileName](PtScript *script) -> bool { + scriptId = script->GetScriptId(); + fileName = script->GetFileName(); + return true; + }; + if (!MatchScripts(scriptFunc, url, ScriptMatchType::URL)) { + LOG(ERROR, DEBUGGER) << "SetBreakpointByUrl: Unknown url: " << url; + return DispatchResponse::Fail("Unknown file name."); + } + + auto callbackFunc = [this, fileName, &condition](File::EntityId id, uint32_t offset) -> bool { + JSPtLocation location {fileName.c_str(), id, offset}; + Local condFuncRef = FunctionRef::Undefined(vm_); + if (condition.has_value() && !condition.value().empty()) { + CString dest; + if (!DecodeAndCheckBase64(condition.value(), dest)) { + LOG(ERROR, DEBUGGER) << "SetBreakpointByUrl: base64 decode failed"; + return false; + } + condFuncRef = DebuggerApi::GenerateFuncFromBuffer(vm_, dest.data(), dest.size(), + JSPandaFile::ENTRY_MAIN_FUNCTION); + if (condFuncRef->IsUndefined()) { + LOG(ERROR, DEBUGGER) << "SetBreakpointByUrl: generate function failed"; + return false; + } + } + return DebuggerApi::SetBreakpoint(jsDebugger_, location, condFuncRef); + }; + if (!extractor->MatchWithLocation(callbackFunc, lineNumber, columnNumber)) { + LOG(ERROR, DEBUGGER) << "failed to set breakpoint location number: " << lineNumber << ":" << columnNumber; + return DispatchResponse::Fail("Breakpoint not found."); + } + + BreakpointDetails metaData{lineNumber, 0, url}; + *outId = BreakpointDetails::ToString(metaData); + *outLocations = CVector>(); + std::unique_ptr location = std::make_unique(); + location->SetScriptId(scriptId).SetLine(lineNumber).SetColumn(0); + outLocations->emplace_back(std::move(location)); + + return DispatchResponse::Ok(); } DispatchResponse DebuggerImpl::SetPauseOnExceptions(std::unique_ptr params) { PauseOnExceptionsState state = params->GetState(); - if (state == PauseOnExceptionsState::UNCAUGHT) { - backend_->SetPauseOnException(false); - } else { - backend_->SetPauseOnException(true); - } + pauseOnException_ = (state != PauseOnExceptionsState::UNCAUGHT); + return DispatchResponse::Ok(); } DispatchResponse DebuggerImpl::StepInto([[maybe_unused]] std::unique_ptr params) { - return DispatchResponse::Create(backend_->StepInto()); + JSMethod *method = DebuggerApi::GetMethod(vm_); + JSPtExtractor *extractor = GetExtractor(method->GetJSPandaFile()); + if (extractor == nullptr) { + LOG(ERROR, DEBUGGER) << "StepOver: extractor is null"; + return DispatchResponse::Fail("Unknown file name."); + } + singleStepper_ = extractor->GetStepIntoStepper(vm_); + + frontend_.Resumed(vm_); + return DispatchResponse::Ok(); } DispatchResponse DebuggerImpl::StepOut() { - return DispatchResponse::Create(backend_->StepOut()); + JSMethod *method = DebuggerApi::GetMethod(vm_); + JSPtExtractor *extractor = GetExtractor(method->GetJSPandaFile()); + if (extractor == nullptr) { + LOG(ERROR, DEBUGGER) << "StepOut: extractor is null"; + return DispatchResponse::Fail("Unknown file name."); + } + singleStepper_ = extractor->GetStepOutStepper(vm_); + + frontend_.Resumed(vm_); + return DispatchResponse::Ok(); } DispatchResponse DebuggerImpl::StepOver([[maybe_unused]] std::unique_ptr params) { - return DispatchResponse::Create(backend_->StepOver()); + JSMethod *method = DebuggerApi::GetMethod(vm_); + JSPtExtractor *extractor = GetExtractor(method->GetJSPandaFile()); + if (extractor == nullptr) { + LOG(ERROR, DEBUGGER) << "StepOver: extractor is null"; + return DispatchResponse::Fail("Unknown file name."); + } + singleStepper_ = extractor->GetStepOverStepper(vm_); + + frontend_.Resumed(vm_); + return DispatchResponse::Ok(); } DispatchResponse DebuggerImpl::SetBlackboxPatterns() { + LOG(ERROR, DEBUGGER) << "SetBlackboxPatterns not support now."; return DispatchResponse::Ok(); } + +void DebuggerImpl::CleanUpOnPaused() +{ + runtime_->curObjectId_ = 0; + runtime_->properties_.clear(); + + callFrameHandlers_.clear(); + scopeObjects_.clear(); +} + +CString DebuggerImpl::Trim(const CString &str) +{ + CString ret = str; + // If ret has only ' ', remove all charactors. + ret.erase(ret.find_last_not_of(' ') + 1); + // If ret has only ' ', remove all charactors. + ret.erase(0, ret.find_first_not_of(' ')); + return ret; +} + +JSPtExtractor *DebuggerImpl::GetExtractor(const JSPandaFile *jsPandaFile) +{ + return JSPandaFileManager::GetInstance()->GetJSPtExtractor(jsPandaFile); +} + +JSPtExtractor *DebuggerImpl::GetExtractor(const CString &url) +{ + auto iter = extractors_.find(url); + if (iter == extractors_.end()) { + return nullptr; + } + + return iter->second; +} + +bool DebuggerImpl::GenerateCallFrames(CVector> *callFrames) +{ + CallFrameId callFrameId = 0; + auto walkerFunc = [this, &callFrameId, &callFrames](const FrameHandler *frameHandler) -> StackState { + JSMethod *method = DebuggerApi::GetMethod(frameHandler); + if (method->IsNativeWithCallField()) { + LOG(INFO, DEBUGGER) << "GenerateCallFrames: Skip CFrame and Native method"; + return StackState::CONTINUE; + } + std::unique_ptr callFrame = std::make_unique(); + if (!GenerateCallFrame(callFrame.get(), frameHandler, callFrameId)) { + if (callFrameId == 0) { + return StackState::FAILED; + } + } else { + SaveCallFrameHandler(frameHandler); + callFrames->emplace_back(std::move(callFrame)); + callFrameId++; + } + return StackState::CONTINUE; + }; + return DebuggerApi::StackWalker(vm_, walkerFunc); +} + +void DebuggerImpl::SaveCallFrameHandler(const FrameHandler *frameHandler) +{ + auto handlerPtr = DebuggerApi::NewFrameHandler(vm_); + *handlerPtr = *frameHandler; + callFrameHandlers_.emplace_back(handlerPtr); +} + +bool DebuggerImpl::GenerateCallFrame(CallFrame *callFrame, + const FrameHandler *frameHandler, CallFrameId callFrameId) +{ + JSMethod *method = DebuggerApi::GetMethod(frameHandler); + JSPtExtractor *extractor = GetExtractor(method->GetJSPandaFile()); + if (extractor == nullptr) { + LOG(ERROR, DEBUGGER) << "GenerateCallFrame: extractor is null"; + return false; + } + + // location + std::unique_ptr location = std::make_unique(); + CString url = extractor->GetSourceFile(method->GetMethodId()); + auto scriptFunc = [&location](PtScript *script) -> bool { + location->SetScriptId(script->GetScriptId()); + return true; + }; + if (!MatchScripts(scriptFunc, url, ScriptMatchType::URL)) { + LOG(ERROR, DEBUGGER) << "GenerateCallFrame: Unknown url: " << url; + return false; + } + auto callbackLineFunc = [&location](int32_t line) -> bool { + location->SetLine(line); + return true; + }; + auto callbackColumnFunc = [&location](int32_t column) -> bool { + location->SetColumn(column); + return true; + }; + File::EntityId methodId = method->GetMethodId(); + if (!extractor->MatchLineWithOffset(callbackLineFunc, methodId, DebuggerApi::GetBytecodeOffset(frameHandler)) || + !extractor->MatchColumnWithOffset(callbackColumnFunc, methodId, DebuggerApi::GetBytecodeOffset(frameHandler))) { + LOG(ERROR, DEBUGGER) << "GenerateCallFrame: unknown offset: " << DebuggerApi::GetBytecodeOffset(frameHandler); + return false; + } + + // scopeChain & this + std::unique_ptr thisObj = std::make_unique(); + thisObj->SetType(ObjectType::Undefined); + + CVector> scopeChain; + scopeChain.emplace_back(GetLocalScopeChain(frameHandler, &thisObj)); + scopeChain.emplace_back(GetGlobalScopeChain()); + + // functionName + CString functionName = DebuggerApi::ParseFunctionName(method); + + callFrame->SetCallFrameId(callFrameId) + .SetFunctionName(functionName) + .SetLocation(std::move(location)) + .SetUrl(url) + .SetScopeChain(std::move(scopeChain)) + .SetThis(std::move(thisObj)); + return true; +} + +std::unique_ptr DebuggerImpl::GetLocalScopeChain(const FrameHandler *frameHandler, + std::unique_ptr *thisObj) +{ + auto localScope = std::make_unique(); + + JSMethod *method = DebuggerApi::GetMethod(frameHandler); + JSPtExtractor *extractor = GetExtractor(method->GetJSPandaFile()); + if (extractor == nullptr) { + LOG(ERROR, DEBUGGER) << "GetScopeChain: extractor is null"; + return localScope; + } + + std::unique_ptr local = std::make_unique(); + Local localObj(local->NewObject(vm_)); + local->SetType(ObjectType::Object) + .SetObjectId(runtime_->curObjectId_) + .SetClassName(ObjectClassName::Object) + .SetDescription(RemoteObject::ObjectDescription); + auto *sp = DebuggerApi::GetSp(frameHandler); + scopeObjects_[sp] = runtime_->curObjectId_; + runtime_->properties_[runtime_->curObjectId_++] = Global(vm_, localObj); + + Local thisVal = JSValueRef::Undefined(vm_); + GetLocalVariables(frameHandler, method, thisVal, localObj); + *thisObj = RemoteObject::FromTagged(vm_, thisVal); + runtime_->CacheObjectIfNeeded(thisVal, (*thisObj).get()); + + auto methodId = method->GetMethodId(); + const LineNumberTable &lines = extractor->GetLineNumberTable(methodId); + std::unique_ptr startLoc = std::make_unique(); + std::unique_ptr endLoc = std::make_unique(); + auto scriptFunc = [&startLoc, &endLoc, lines](PtScript *script) -> bool { + startLoc->SetScriptId(script->GetScriptId()) + .SetLine(lines.front().line) + .SetColumn(0); + endLoc->SetScriptId(script->GetScriptId()) + .SetLine(lines.back().line + 1) + .SetColumn(0); + return true; + }; + if (MatchScripts(scriptFunc, extractor->GetSourceFile(methodId), ScriptMatchType::URL)) { + localScope->SetType(Scope::Type::Local()) + .SetObject(std::move(local)) + .SetStartLocation(std::move(startLoc)) + .SetEndLocation(std::move(endLoc)); + } + + return localScope; +} + +void DebuggerImpl::GetLocalVariables(const FrameHandler *frameHandler, const JSMethod *method, + Local &thisVal, Local &localObj) +{ + auto methodId = method->GetMethodId(); + auto *extractor = GetExtractor(method->GetJSPandaFile()); + Local value = JSValueRef::Undefined(vm_); + // in case of arrow function, which doesn't have this in local variable table + bool hasThis = false; + for (const auto &[varName, regIndex] : extractor->GetLocalVariableTable(methodId)) { + value = DebuggerApi::GetVRegValue(vm_, frameHandler, regIndex); + if (varName == "4newTarget") { + continue; + } + + if (varName == "this") { + thisVal = value; + hasThis = true; + continue; + } + Local name = JSValueRef::Undefined(vm_); + if (varName == "4funcObj") { + if (value->IsFunction()) { + auto funcName = Local(value)->GetName(vm_)->ToString(); + name = StringRef::NewFromUtf8(vm_, funcName.c_str()); + } else { + continue; + } + } else { + name = StringRef::NewFromUtf8(vm_, varName.c_str()); + } + PropertyAttribute descriptor(value, true, true, true); + localObj->DefineProperty(vm_, name, descriptor); + } + + // closure variables are stored in env + JSTaggedValue env = DebuggerApi::GetEnv(frameHandler); + if (env.IsTaggedArray() && DebuggerApi::GetBytecodeOffset(frameHandler) != 0) { + LexicalEnv *lexEnv = LexicalEnv::Cast(env.GetTaggedObject()); + if (lexEnv->GetScopeInfo().IsHole()) { + return; + } + auto ptr = JSNativePointer::Cast(lexEnv->GetScopeInfo().GetTaggedObject())->GetExternalPointer(); + auto *scopeDebugInfo = reinterpret_cast(ptr); + JSThread *thread = vm_->GetJSThread(); + for (const auto &[varName, slot] : scopeDebugInfo->scopeInfo) { + // skip possible duplicate variables both in local variable table and env + if (varName == "4newTarget") { + continue; + } + value = JSNApiHelper::ToLocal( + JSHandle(thread, lexEnv->GetProperties(slot))); + if (varName == "this") { + if (!hasThis) { + thisVal = value; + } + continue; + } + Local name = StringRef::NewFromUtf8(vm_, varName.c_str()); + PropertyAttribute descriptor(value, true, true, true); + localObj->DefineProperty(vm_, name, descriptor); + } + } +} + +std::unique_ptr DebuggerImpl::GetGlobalScopeChain() +{ + auto globalScope = std::make_unique(); + + std::unique_ptr global = std::make_unique(); + global->SetType(ObjectType::Object) + .SetObjectId(runtime_->curObjectId_) + .SetClassName(ObjectClassName::Global) + .SetDescription(RemoteObject::GlobalDescription); + globalScope->SetType(Scope::Type::Global()).SetObject(std::move(global)); + runtime_->properties_[runtime_->curObjectId_++] = Global(vm_, JSNApi::GetGlobalObject(vm_)); + return globalScope; +} + +void DebuggerImpl::UpdateScopeObject(const FrameHandler *frameHandler, + std::string_view varName, const Local &newVal) +{ + auto *sp = DebuggerApi::GetSp(frameHandler); + auto iter = scopeObjects_.find(sp); + if (iter == scopeObjects_.end()) { + LOG(ERROR, DEBUGGER) << "UpdateScopeObject: object not found"; + return; + } + + auto objectId = iter->second; + Local localObj = runtime_->properties_[objectId].ToLocal(vm_); + Local name = StringRef::NewFromUtf8(vm_, varName.data()); + if (localObj->Has(vm_, name)) { + LOG(DEBUG, DEBUGGER) << "UpdateScopeObject: set new value"; + PropertyAttribute descriptor(newVal, true, true, true); + localObj->DefineProperty(vm_, name, descriptor); + } else { + LOG(ERROR, DEBUGGER) << "UpdateScopeObject: not found " << varName; + } +} + +std::optional DebuggerImpl::CmptEvaluateValue(CallFrameId callFrameId, const CString &expression, + std::unique_ptr *result) +{ + JSMethod *method = DebuggerApi::GetMethod(vm_); + if (method->IsNativeWithCallField()) { + *result = RemoteObject::FromTagged(vm_, + Exception::EvalError(vm_, StringRef::NewFromUtf8(vm_, "Native Frame not support."))); + return "Native Frame not support."; + } + JSPtExtractor *extractor = GetExtractor(method->GetJSPandaFile()); + if (extractor == nullptr) { + *result = RemoteObject::FromTagged(vm_, + Exception::EvalError(vm_, StringRef::NewFromUtf8(vm_, "Internal error."))); + return "Internal error."; + } + CString varName = expression; + CString varValue; + CString::size_type indexEqual = expression.find_first_of('=', 0); + if (indexEqual != CString::npos) { + varName = Trim(expression.substr(0, indexEqual)); + varValue = Trim(expression.substr(indexEqual + 1, expression.length())); + } + + Local name = StringRef::NewFromUtf8(vm_, varName.c_str()); + FrameHandler *frameHandler = callFrameHandlers_[callFrameId].get(); + if (varValue.empty()) { + Local ret = DebuggerExecutor::GetValue(vm_, frameHandler, name); + if (!ret.IsEmpty() && !ret->IsException()) { + *result = RemoteObject::FromTagged(vm_, ret); + runtime_->CacheObjectIfNeeded(ret, (*result).get()); + return {}; + } + } else { + Local value = ConvertToLocal(varValue); + bool ret = DebuggerExecutor::SetValue(vm_, frameHandler, name, value); + if (ret) { + *result = RemoteObject::FromTagged(vm_, value); + return {}; + } + } + + *result = RemoteObject::FromTagged(vm_, + Exception::EvalError(vm_, StringRef::NewFromUtf8(vm_, "Unsupported expression."))); + return "Unsupported expression."; +} + +Local DebuggerImpl::ConvertToLocal(const CString &varValue) +{ + Local taggedValue; + if (varValue == "false") { + taggedValue = JSValueRef::False(vm_); + } else if (varValue == "true") { + taggedValue = JSValueRef::True(vm_); + } else if (varValue == "undefined") { + taggedValue = JSValueRef::Undefined(vm_); + } else if (varValue[0] == '\"' && varValue[varValue.length() - 1] == '\"') { + // 2 : 2 means length + taggedValue = StringRef::NewFromUtf8(vm_, varValue.substr(1, varValue.length() - 2).c_str()); + } else { + auto begin = reinterpret_cast((varValue.c_str())); + auto end = begin + varValue.length(); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + double d = DebuggerApi::StringToDouble(begin, end, 0); + if (!std::isnan(d)) { + taggedValue = NumberRef::New(vm_, d); + } + } + return taggedValue; +} + +bool DebuggerImpl::DecodeAndCheckBase64(const CString &src, CString &dest) +{ + dest.resize(base64::decoded_size(src.size())); + auto [numOctets, _] = base64::decode(dest.data(), src.data(), src.size()); + dest.resize(numOctets); + if (numOctets > File::MAGIC_SIZE && + memcmp(dest.data(), File::MAGIC.data(), File::MAGIC_SIZE) == 0) { + return true; + } + return false; +} } // namespace panda::ecmascript::tooling diff --git a/ecmascript/tooling/agent/debugger_impl.h b/ecmascript/tooling/agent/debugger_impl.h index 564aaf78f55fb4bc116b7fb61d15022c88d883af..04dfa0ddf738880bdb251773bb07d9390a5bc76c 100644 --- a/ecmascript/tooling/agent/debugger_impl.h +++ b/ecmascript/tooling/agent/debugger_impl.h @@ -17,15 +17,24 @@ #define ECMASCRIPT_TOOLING_AGENT_DEBUGGER_IMPL_H #include "libpandabase/macros.h" -#include "ecmascript/tooling/agent/js_backend.h" +#include "ecmascript/tooling/agent/runtime_impl.h" +#include "ecmascript/tooling/backend/js_pt_hooks.h" #include "ecmascript/tooling/base/pt_params.h" +#include "ecmascript/tooling/backend/js_pt_extractor.h" #include "ecmascript/tooling/dispatcher.h" +#include "ecmascript/tooling/interface/js_debugger_manager.h" namespace panda::ecmascript::tooling { class DebuggerImpl final { public: - explicit DebuggerImpl(std::unique_ptr backend) : backend_(std::move(backend)) {} - ~DebuggerImpl() = default; + DebuggerImpl(const EcmaVM *vm, ProtocolChannel *channel, RuntimeImpl *runtime); + ~DebuggerImpl(); + + // event + bool NotifyScriptParsed(ScriptId scriptId, const CString &fileName); + bool NotifySingleStep(const JSPtLocation &location); + void NotifyPaused(std::optional location, PauseReason reason); + void NotifyPendingJobEntry(); DispatchResponse Enable(std::unique_ptr params, UniqueDebuggerId *id); DispatchResponse Disable(); @@ -46,10 +55,53 @@ public: DispatchResponse StepOver(std::unique_ptr params); DispatchResponse SetBlackboxPatterns(); + // for testcase + JSDebugger *GetDebugger() const + { + return jsDebugger_; + } + bool GenerateCallFrames(CVector> *callFrames); + + /** + * @brief: match first script and callback + * + * @return: true means matched and callback execute success + */ + template + bool MatchScripts(const Callback &cb, const CString &matchStr, ScriptMatchType type) const + { + for (const auto &script : scripts_) { + CString value; + switch (type) { + case ScriptMatchType::URL: { + value = script.second->GetUrl(); + break; + } + case ScriptMatchType::FILE_NAME: { + value = script.second->GetFileName(); + break; + } + case ScriptMatchType::HASH: { + value = script.second->GetHash(); + break; + } + default: { + return false; + } + } + if (matchStr == value) { + return cb(script.second.get()); + } + } + return false; + } + class DispatcherImpl final : public DispatcherBase { public: - DispatcherImpl(FrontEnd *frontend, std::unique_ptr debugger); + DispatcherImpl(ProtocolChannel *channel, std::unique_ptr debugger) + : DispatcherBase(channel), debugger_(std::move(debugger)) {} ~DispatcherImpl() override = default; + void Dispatch(const DispatchRequest &request) override; void Enable(const DispatchRequest &request); void Disable(const DispatchRequest &request); @@ -72,7 +124,6 @@ public: NO_MOVE_SEMANTIC(DispatcherImpl); using AgentHandler = void (DebuggerImpl::DispatcherImpl::*)(const DispatchRequest &request); - CUnorderedMap dispatcherTable_ {}; std::unique_ptr debugger_ {}; }; @@ -80,7 +131,59 @@ private: NO_COPY_SEMANTIC(DebuggerImpl); NO_MOVE_SEMANTIC(DebuggerImpl); - std::unique_ptr backend_ {nullptr}; + CString Trim(const CString &str); + JSPtExtractor *GetExtractor(const JSPandaFile *jsPandaFile); + JSPtExtractor *GetExtractor(const CString &url); + std::optional CmptEvaluateValue(CallFrameId callFrameId, const CString &expression, + std::unique_ptr *result); + bool GenerateCallFrame(CallFrame *callFrame, const FrameHandler *frameHandler, CallFrameId frameId); + void SaveCallFrameHandler(const FrameHandler *frameHandler); + std::unique_ptr GetLocalScopeChain(const FrameHandler *frameHandler, + std::unique_ptr *thisObj); + std::unique_ptr GetGlobalScopeChain(); + void GetLocalVariables(const FrameHandler *frameHandler, const JSMethod *method, + Local &thisVal, Local &localObj); + void CleanUpOnPaused(); + void UpdateScopeObject(const FrameHandler *frameHandler, std::string_view varName, const Local &newVal); + Local ConvertToLocal(const CString &varValue); + bool DecodeAndCheckBase64(const CString &src, CString &dest); + bool IsSkipLine(const JSPtLocation &location); + + class Frontend { + public: + explicit Frontend(ProtocolChannel *channel) : channel_(channel) {} + + void BreakpointResolved(const EcmaVM *vm); + void Paused(const EcmaVM *vm, std::unique_ptr paused); + void Resumed(const EcmaVM *vm); + void ScriptFailedToParse(const EcmaVM *vm); + void ScriptParsed(const EcmaVM *vm, const std::unique_ptr &script); + void WaitForDebugger(const EcmaVM *vm); + + private: + bool AllowNotify(const EcmaVM *vm) const; + + ProtocolChannel *channel_ {nullptr}; + }; + + const EcmaVM *vm_ {nullptr}; + Frontend frontend_; + + RuntimeImpl *runtime_ {nullptr}; + std::unique_ptr hooks_ {nullptr}; + JSDebugger *jsDebugger_ {nullptr}; + + CUnorderedMap extractors_ {}; + CUnorderedMap> scripts_ {}; + bool pauseOnException_ {false}; + bool pauseOnNextByteCode_ {false}; + std::unique_ptr singleStepper_ {nullptr}; + + CUnorderedMap scopeObjects_ {}; + CVector> callFrameHandlers_; + JsDebuggerManager::ObjectUpdaterFunc updaterFunc_ {nullptr}; + + friend class JSPtHooks; }; } // namespace panda::ecmascript::tooling #endif \ No newline at end of file diff --git a/ecmascript/tooling/agent/heapprofiler_impl.cpp b/ecmascript/tooling/agent/heapprofiler_impl.cpp index 33ae00320b1f5018e770997d986d2c31ca760d67..678309b14737f7c6d954b70c76b406a97fd1c913 100644 --- a/ecmascript/tooling/agent/heapprofiler_impl.cpp +++ b/ecmascript/tooling/agent/heapprofiler_impl.cpp @@ -17,29 +17,27 @@ namespace panda::ecmascript::tooling { -HeapProfilerImpl::DispatcherImpl::DispatcherImpl(FrontEnd *frontend, std::unique_ptr heapprofiler) - : DispatcherBase(frontend), heapprofiler_(std::move(heapprofiler)) -{ - dispatcherTable_["addInspectedHeapObject"] = &HeapProfilerImpl::DispatcherImpl::AddInspectedHeapObject; - dispatcherTable_["collectGarbage"] = &HeapProfilerImpl::DispatcherImpl::CollectGarbage; - dispatcherTable_["enable"] = &HeapProfilerImpl::DispatcherImpl::Enable; - dispatcherTable_["disable"] = &HeapProfilerImpl::DispatcherImpl::Disable; - dispatcherTable_["getHeapObjectId"] = &HeapProfilerImpl::DispatcherImpl::GetHeapObjectId; - dispatcherTable_["getObjectByHeapObjectId"] = &HeapProfilerImpl::DispatcherImpl::GetObjectByHeapObjectId; - dispatcherTable_["getSamplingProfile"] = &HeapProfilerImpl::DispatcherImpl::GetSamplingProfile; - dispatcherTable_["startSampling"] = &HeapProfilerImpl::DispatcherImpl::StartSampling; - dispatcherTable_["startTrackingHeapObjects"] = &HeapProfilerImpl::DispatcherImpl::StartTrackingHeapObjects; - dispatcherTable_["stopSampling"] = &HeapProfilerImpl::DispatcherImpl::StopSampling; - dispatcherTable_["stopTrackingHeapObjects"] = &HeapProfilerImpl::DispatcherImpl::StopTrackingHeapObjects; - dispatcherTable_["takeHeapSnapshot"] = &HeapProfilerImpl::DispatcherImpl::TakeHeapSnapshot; -} - void HeapProfilerImpl::DispatcherImpl::Dispatch(const DispatchRequest &request) { - CString method = request.GetMethod(); + static CUnorderedMap dispatcherTable { + { "addInspectedHeapObject", &HeapProfilerImpl::DispatcherImpl::AddInspectedHeapObject }, + { "collectGarbage", &HeapProfilerImpl::DispatcherImpl::CollectGarbage }, + { "enable", &HeapProfilerImpl::DispatcherImpl::Enable }, + { "disable", &HeapProfilerImpl::DispatcherImpl::Disable }, + { "getHeapObjectId", &HeapProfilerImpl::DispatcherImpl::GetHeapObjectId }, + { "getObjectByHeapObjectId", &HeapProfilerImpl::DispatcherImpl::GetObjectByHeapObjectId }, + { "getSamplingProfile", &HeapProfilerImpl::DispatcherImpl::GetSamplingProfile }, + { "startSampling", &HeapProfilerImpl::DispatcherImpl::StartSampling }, + { "startTrackingHeapObjects", &HeapProfilerImpl::DispatcherImpl::StartTrackingHeapObjects }, + { "stopSampling", &HeapProfilerImpl::DispatcherImpl::StopSampling }, + { "stopTrackingHeapObjects", &HeapProfilerImpl::DispatcherImpl::StopTrackingHeapObjects }, + { "takeHeapSnapshot", &HeapProfilerImpl::DispatcherImpl::TakeHeapSnapshot } + }; + + const CString &method = request.GetMethod(); LOG(DEBUG, DEBUGGER) << "dispatch [" << method << "] to HeapProfilerImpl"; - auto entry = dispatcherTable_.find(method); - if (entry != dispatcherTable_.end() && entry->second != nullptr) { + auto entry = dispatcherTable.find(method); + if (entry != dispatcherTable.end() && entry->second != nullptr) { (this->*(entry->second))(request); } else { SendResponse(request, DispatchResponse::Fail("Unknown method: " + method), nullptr); @@ -178,6 +176,110 @@ void HeapProfilerImpl::DispatcherImpl::TakeHeapSnapshot(const DispatchRequest &r SendResponse(request, response, std::move(result)); } +class HeapProfilerStream final : public Stream { +public: + explicit HeapProfilerStream(HeapProfilerImpl::Frontend *frontend) + : frontend_(frontend) {} + + void EndOfStream() override {} + int GetSize() override + { + static const int heapProfilerChunkSise = 102400; + return heapProfilerChunkSise; + } + bool WriteChunk(char *data, int size) override + { + if (!Good()) { + return false; + } + frontend_->AddHeapSnapshotChunk(data, size); + return true; + } + bool Good() override + { + return frontend_ != nullptr; + } + +private: + NO_COPY_SEMANTIC(HeapProfilerStream); + NO_MOVE_SEMANTIC(HeapProfilerStream); + + HeapProfilerImpl::Frontend *frontend_ {nullptr}; +}; + +class HeapProfilerProgress final : public Progress { +public: + explicit HeapProfilerProgress(HeapProfilerImpl::Frontend *frontend) + : frontend_(frontend) {} + + void ReportProgress(int32_t done, int32_t total) override + { + frontend_->ReportHeapSnapshotProgress(done, total); + } + +private: + NO_COPY_SEMANTIC(HeapProfilerProgress); + NO_MOVE_SEMANTIC(HeapProfilerProgress); + + HeapProfilerImpl::Frontend *frontend_ {nullptr}; +}; + +bool HeapProfilerImpl::Frontend::AllowNotify() const +{ + return channel_ != nullptr; +} + +void HeapProfilerImpl::Frontend::AddHeapSnapshotChunk(char *data, int size) +{ + if (!AllowNotify()) { + return; + } + + channel_->SendNotification(AddHeapSnapshotChunk::Create(data, size)); +} + +void HeapProfilerImpl::Frontend::ReportHeapSnapshotProgress(int32_t done, int32_t total) +{ + if (!AllowNotify()) { + return; + } + + auto reportHeapSnapshotProgress = std::make_unique(); + reportHeapSnapshotProgress->SetDone(done); + reportHeapSnapshotProgress->SetTotal(total); + if (done >= total) { + reportHeapSnapshotProgress->SetFinished(true); + } + channel_->SendNotification(std::move(reportHeapSnapshotProgress)); +} + +void HeapProfilerImpl::Frontend::HeapStatsUpdate() +{ + if (!AllowNotify()) { + return; + } + + auto heapStatsUpdate = std::make_unique(); + channel_->SendNotification(std::move(heapStatsUpdate)); +} + +void HeapProfilerImpl::Frontend::LastSeenObjectId() +{ + if (!AllowNotify()) { + return; + } + + auto lastSeenObjectId = std::make_unique(); + channel_->SendNotification(std::move(lastSeenObjectId)); +} + +void HeapProfilerImpl::Frontend::ResetProfiles() +{ + if (!AllowNotify()) { + return; + } +} + DispatchResponse HeapProfilerImpl::AddInspectedHeapObject( [[maybe_unused]] std::unique_ptr params) { @@ -235,8 +337,7 @@ DispatchResponse HeapProfilerImpl::StartSampling([[maybe_unused]]std::unique_ptr DispatchResponse HeapProfilerImpl::StartTrackingHeapObjects( [[maybe_unused]] std::unique_ptr params) { - auto ecmaVm = const_cast(static_cast(frontend_)->GetEcmaVM()); - bool result = panda::DFXJSNApi::StartHeapTracking(ecmaVm, INTERVAL, true); + bool result = panda::DFXJSNApi::StartHeapTracking(vm_, INTERVAL, true); if (result) { return DispatchResponse::Ok(); } else { @@ -244,7 +345,6 @@ DispatchResponse HeapProfilerImpl::StartTrackingHeapObjects( } } - DispatchResponse HeapProfilerImpl::StopSampling([[maybe_unused]]std::unique_ptr *profile) { LOG(ERROR, DEBUGGER) << "StopSampling not support now."; @@ -253,14 +353,13 @@ DispatchResponse HeapProfilerImpl::StopSampling([[maybe_unused]]std::unique_ptr< DispatchResponse HeapProfilerImpl::StopTrackingHeapObjects(std::unique_ptr params) { - HeapProfilerStream stream(frontend_); - auto ecmaVm = (panda::EcmaVM *)static_cast(frontend_)->GetEcmaVM(); + HeapProfilerStream stream(&frontend_); bool result = false; if (params->GetReportProgress()) { - HeapProfilerProgress progress(frontend_); - result = panda::DFXJSNApi::StopHeapTracking(ecmaVm, &stream, &progress); + HeapProfilerProgress progress(&frontend_); + result = panda::DFXJSNApi::StopHeapTracking(vm_, &stream, &progress); } else { - result = panda::DFXJSNApi::StopHeapTracking(ecmaVm, &stream, nullptr); + result = panda::DFXJSNApi::StopHeapTracking(vm_, &stream, nullptr); } if (result) { return DispatchResponse::Ok(); @@ -271,13 +370,12 @@ DispatchResponse HeapProfilerImpl::StopTrackingHeapObjects(std::unique_ptr params) { - HeapProfilerStream stream(frontend_); - auto ecmaVm = (panda::EcmaVM *)static_cast(frontend_)->GetEcmaVM(); + HeapProfilerStream stream(&frontend_); if (params->GetReportProgress()) { - HeapProfilerProgress progress(frontend_); - panda::DFXJSNApi::DumpHeapSnapshot(ecmaVm, 0, &stream, &progress, true); + HeapProfilerProgress progress(&frontend_); + panda::DFXJSNApi::DumpHeapSnapshot(vm_, 0, &stream, &progress, true); } else { - panda::DFXJSNApi::DumpHeapSnapshot(ecmaVm, 0, &stream, nullptr, true); + panda::DFXJSNApi::DumpHeapSnapshot(vm_, 0, &stream, nullptr, true); } return DispatchResponse::Ok(); } diff --git a/ecmascript/tooling/agent/heapprofiler_impl.h b/ecmascript/tooling/agent/heapprofiler_impl.h index bc2d77aa404681c2d8c1407b32a5fcfcde1caa71..25cbb41b9f998d309ed969b23d1d1c4371c5a23a 100644 --- a/ecmascript/tooling/agent/heapprofiler_impl.h +++ b/ecmascript/tooling/agent/heapprofiler_impl.h @@ -17,7 +17,6 @@ #define ECMASCRIPT_TOOLING_AGENT_HEAPPROFILER_IMPL_H #include "libpandabase/macros.h" -#include "ecmascript/tooling/agent/js_backend.h" #include "ecmascript/tooling/base/pt_params.h" #include "ecmascript/tooling/base/pt_events.h" #include "ecmascript/tooling/base/pt_returns.h" @@ -25,17 +24,16 @@ #include "ecmascript/tooling/interface/stream.h" #include "ecmascript/tooling/interface/progress.h" #include "ecmascript/tooling/protocol_handler.h" -#include "ecmascript/tooling/front_end.h" +#include "ecmascript/tooling/protocol_channel.h" #include "ecmascript/napi/include/dfx_jsnapi.h" #include "libpandabase/utils/logger.h" static const double INTERVAL = 0.05; -static const int MAX_HEAPPROFILER_CHUNK_SIZE = 102400; namespace panda::ecmascript::tooling { class HeapProfilerImpl final { public: - explicit HeapProfilerImpl(FrontEnd *frontend) : frontend_(frontend) {} + explicit HeapProfilerImpl(const EcmaVM *vm, ProtocolChannel *channel) : vm_(vm), frontend_(channel) {} ~HeapProfilerImpl() = default; DispatchResponse AddInspectedHeapObject(std::unique_ptr params); @@ -55,8 +53,10 @@ public: class DispatcherImpl final : public DispatcherBase { public: - DispatcherImpl(FrontEnd *frontend, std::unique_ptr heapprofiler); + DispatcherImpl(ProtocolChannel *channel, std::unique_ptr heapprofiler) + : DispatcherBase(channel), heapprofiler_(std::move(heapprofiler)) {} ~DispatcherImpl() override = default; + void Dispatch(const DispatchRequest &request) override; void AddInspectedHeapObject(const DispatchRequest &request); void CollectGarbage(const DispatchRequest &request); @@ -76,67 +76,31 @@ public: NO_MOVE_SEMANTIC(DispatcherImpl); using AgentHandler = void (HeapProfilerImpl::DispatcherImpl::*)(const DispatchRequest &request); - CUnorderedMap dispatcherTable_ {}; std::unique_ptr heapprofiler_ {}; }; -private: - NO_COPY_SEMANTIC(HeapProfilerImpl); - NO_MOVE_SEMANTIC(HeapProfilerImpl); - - FrontEnd* frontend_ {nullptr}; -}; - -class HeapProfilerStream final : public Stream { -public: - explicit HeapProfilerStream(FrontEnd* frontend) - : frontend_(frontend) {} - - void EndOfStream() override {} - int GetSize() override - { - return MAX_HEAPPROFILER_CHUNK_SIZE; - } - bool WriteChunk(char* data, int size) override - { - auto ecmaVm = static_cast(frontend_)->GetEcmaVM(); - frontend_->SendProfilerNotify(ecmaVm, AddHeapSnapshotChunk::Create(data, size)); - return true; - } - bool Good() override - { - return frontend_ != nullptr; - } + class Frontend { + public: + explicit Frontend(ProtocolChannel *channel) : channel_(channel) {} -private: - NO_COPY_SEMANTIC(HeapProfilerStream); - NO_MOVE_SEMANTIC(HeapProfilerStream); + void AddHeapSnapshotChunk(char *data, int size); + void ReportHeapSnapshotProgress(int32_t done, int32_t total); + void HeapStatsUpdate(); + void LastSeenObjectId(); + void ResetProfiles(); - FrontEnd* frontend_ {nullptr}; -}; + private: + bool AllowNotify() const; -class HeapProfilerProgress final : public Progress { -public: - explicit HeapProfilerProgress(FrontEnd* frontend) - : frontend_(frontend) {} - - void ReportProgress(int32_t done, int32_t total) override - { - auto ecmaVm = static_cast(frontend_)->GetEcmaVM(); - auto reportHeapSnapshotProgress = std::make_unique(); - reportHeapSnapshotProgress->SetDone(done); - reportHeapSnapshotProgress->SetTotal(total); - if (done == total) { - reportHeapSnapshotProgress->SetFinished(true); - } - frontend_->SendProfilerNotify(ecmaVm, std::move(reportHeapSnapshotProgress)); - } + ProtocolChannel *channel_ {nullptr}; + }; private: - NO_COPY_SEMANTIC(HeapProfilerProgress); - NO_MOVE_SEMANTIC(HeapProfilerProgress); + NO_COPY_SEMANTIC(HeapProfilerImpl); + NO_MOVE_SEMANTIC(HeapProfilerImpl); - FrontEnd* frontend_ {nullptr}; + const EcmaVM *vm_ {nullptr}; + Frontend frontend_; }; } // namespace panda::ecmascript::tooling #endif \ No newline at end of file diff --git a/ecmascript/tooling/agent/js_backend.cpp b/ecmascript/tooling/agent/js_backend.cpp deleted file mode 100644 index 94c7573bbd850ebf62efdc91b8db08f95e4f17df..0000000000000000000000000000000000000000 --- a/ecmascript/tooling/agent/js_backend.cpp +++ /dev/null @@ -1,1084 +0,0 @@ -/* - * Copyright (c) 2021-2022 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/tooling/agent/js_backend.h" - -#include -#include - -#include "ecmascript/jspandafile/js_pandafile_manager.h" -#include "ecmascript/napi/jsnapi_helper.h" -#include "ecmascript/tooling/base/pt_events.h" -#include "ecmascript/tooling/front_end.h" -#include "ecmascript/tooling/protocol_handler.h" -#include "libpandafile/class_data_accessor-inl.h" - -namespace panda::ecmascript::tooling { -using namespace boost::beast::detail; -using namespace std::placeholders; - -using ObjectType = RemoteObject::TypeName; -using ObjectSubType = RemoteObject::SubTypeName; -using ObjectClassName = RemoteObject::ClassName; - -#ifdef DEBUGGER_TEST -const CString DATA_APP_PATH = "/"; -#else -const CString DATA_APP_PATH = "/data/"; -#endif - -JSBackend::JSBackend(FrontEnd *frontend) : frontend_(frontend) -{ - ecmaVm_ = static_cast(frontend)->GetEcmaVM(); - hooks_ = std::make_unique(this); - - debugger_ = DebuggerApi::CreateJSDebugger(ecmaVm_); - DebuggerApi::InitJSDebugger(debugger_); - DebuggerApi::RegisterHooks(debugger_, hooks_.get()); - - updaterFunc_ = std::bind(&JSBackend::UpdateScopeObject, this, _1, _2, _3); - ecmaVm_->GetJsDebuggerManager()->SetLocalScopeUpdater(&updaterFunc_); -} - -JSBackend::JSBackend(const EcmaVM *vm) : ecmaVm_(vm) -{ - // For testcases - debugger_ = DebuggerApi::CreateJSDebugger(ecmaVm_); -} - -JSBackend::~JSBackend() -{ - DebuggerApi::DestroyJSDebugger(debugger_); -} - -void JSBackend::WaitForDebugger() -{ - frontend_->WaitForDebugger(); -} - -void JSBackend::NotifyPaused(std::optional location, PauseReason reason) -{ - if (!pauseOnException_ && reason == EXCEPTION) { - return; - } - Local exception = DebuggerApi::GetAndClearException(ecmaVm_); - - CVector hitBreakpoints; - if (location.has_value()) { - BreakpointDetails detail; - JSPtExtractor *extractor = nullptr; - auto scriptFunc = [this, &extractor, &detail](PtScript *script) -> bool { - detail.url_ = script->GetUrl(); - extractor = GetExtractor(detail.url_); - return true; - }; - auto callbackLineFunc = [&detail](int32_t line) -> bool { - detail.line_ = line; - return true; - }; - auto callbackColumnFunc = [&detail](int32_t column) -> bool { - detail.column_ = column; - return true; - }; - File::EntityId methodId = location->GetMethodId(); - uint32_t offset = location->GetBytecodeOffset(); - if (!MatchScripts(scriptFunc, location->GetPandaFile(), ScriptMatchType::FILE_NAME) || - extractor == nullptr || !extractor->MatchLineWithOffset(callbackLineFunc, methodId, offset) || - !extractor->MatchColumnWithOffset(callbackColumnFunc, methodId, offset)) { - LOG(ERROR, DEBUGGER) << "NotifyPaused: unknown " << location->GetPandaFile(); - return; - } - hitBreakpoints.emplace_back(BreakpointDetails::ToString(detail)); - } - - // Do something cleaning on paused - CleanUpOnPaused(); - - // Notify paused event - CVector> callFrames; - if (!GenerateCallFrames(&callFrames)) { - LOG(ERROR, DEBUGGER) << "NotifyPaused: GenerateCallFrames failed"; - return; - } - std::unique_ptr paused = std::make_unique(); - paused->SetCallFrames(std::move(callFrames)).SetReason(reason).SetHitBreakpoints(std::move(hitBreakpoints)); - if (reason == EXCEPTION && exception->IsError()) { - std::unique_ptr tmpException = RemoteObject::FromTagged(ecmaVm_, exception); - paused->SetData(std::move(tmpException)); - } - frontend_->SendNotification(ecmaVm_, std::move(paused)); - - // Waiting for Debugger - frontend_->WaitForDebugger(); - if (!exception->IsHole()) { - DebuggerApi::SetException(ecmaVm_, exception); - } -} - -void JSBackend::NotifyResume() -{ - frontend_->RunIfWaitingForDebugger(); - // Notify resumed event - frontend_->SendNotification(ecmaVm_, std::make_unique()); -} - -void JSBackend::NotifyAllScriptParsed() -{ - for (auto &script : scripts_) { - if (frontend_ != nullptr) { - frontend_->SendNotification(ecmaVm_, ScriptParsed::Create(script.second)); - } - } -} - -bool JSBackend::NotifyScriptParsed(ScriptId scriptId, const CString &fileName) -{ - if (fileName.substr(0, DATA_APP_PATH.length()) != DATA_APP_PATH) { - LOG(WARNING, DEBUGGER) << "NotifyScriptParsed: unsupport file: " << fileName; - return false; - } - - auto scriptFunc = []([[maybe_unused]] PtScript *script) -> bool { - return true; - }; - if (MatchScripts(scriptFunc, fileName, ScriptMatchType::FILE_NAME)) { - LOG(WARNING, DEBUGGER) << "NotifyScriptParsed: already loaded: " << fileName; - return false; - } - const JSPandaFile *jsPandaFile = nullptr; - JSPandaFileManager::GetInstance()->EnumerateJSPandaFiles([&jsPandaFile, &fileName]( - const panda::ecmascript::JSPandaFile *pf) { - if (pf->GetJSPandaFileDesc() == fileName) { - jsPandaFile = pf; - return false; - } - return true; - }); - if (jsPandaFile == nullptr) { - LOG(ERROR, DEBUGGER) << "NotifyScriptParsed: unknown file: " << fileName; - return false; - } - - JSPtExtractor *extractor = GetExtractor(jsPandaFile); - if (extractor == nullptr) { - LOG(ERROR, DEBUGGER) << "NotifyScriptParsed: Unsupported file: " << fileName; - return false; - } - - auto mainMethodIndex = panda_file::File::EntityId(jsPandaFile->GetMainMethodIndex()); - const CString &source = extractor->GetSourceCode(mainMethodIndex); - const CString &url = extractor->GetSourceFile(mainMethodIndex); - const uint32_t MIN_SOURCE_CODE_LENGTH = 5; // maybe return 'ANDA' when source code is empty - if (source.size() < MIN_SOURCE_CODE_LENGTH) { - LOG(ERROR, DEBUGGER) << "NotifyScriptParsed: invalid file: " << fileName; - return false; - } - // store here for performance of get extractor from url - extractors_[url] = extractor; - - // Notify script parsed event - std::unique_ptr script = std::make_unique(scriptId, fileName, url, source); - - if (frontend_ != nullptr) { - frontend_->SendNotification(ecmaVm_, ScriptParsed::Create(script)); - } - - // Store parsed script in map - scripts_[script->GetScriptId()] = std::move(script); - return true; -} - -bool JSBackend::StepComplete(const JSPtLocation &location) -{ - if (UNLIKELY(pauseOnNextByteCode_)) { - if (IsSkipLine(location)) { - return false; - } - pauseOnNextByteCode_ = false; - LOG(INFO, DEBUGGER) << "StepComplete: pause on next bytecode"; - return true; - } - - if (LIKELY(singleStepper_ == nullptr)) { - return false; - } - - // step not complete - if (!singleStepper_->StepComplete(location.GetBytecodeOffset())) { - return false; - } - - // skip unknown file or special line -1 - if (IsSkipLine(location)) { - return false; - } - - LOG(INFO, DEBUGGER) << "StepComplete: pause on current byte_code"; - return true; -} - -bool JSBackend::IsSkipLine(const JSPtLocation &location) -{ - JSPtExtractor *extractor = nullptr; - auto scriptFunc = [this, &extractor](PtScript *script) -> bool { - extractor = GetExtractor(script->GetUrl()); - return true; - }; - if (!MatchScripts(scriptFunc, location.GetPandaFile(), ScriptMatchType::FILE_NAME) || extractor == nullptr) { - LOG(INFO, DEBUGGER) << "StepComplete: skip unknown file"; - return true; - } - - auto callbackFunc = [](int32_t line) -> bool { - return line == JSPtExtractor::SPECIAL_LINE_MARK; - }; - File::EntityId methodId = location.GetMethodId(); - uint32_t offset = location.GetBytecodeOffset(); - if (extractor->MatchLineWithOffset(callbackFunc, methodId, offset)) { - LOG(INFO, DEBUGGER) << "StepComplete: skip -1"; - return true; - } - - return false; -} - -void JSBackend::PendingJobEntry() -{ - if (singleStepper_ != nullptr) { - singleStepper_.reset(); - pauseOnNextByteCode_ = true; - } -} - -std::optional JSBackend::GetPossibleBreakpoints(Location *start, [[maybe_unused]] Location *end, - CVector> *locations) -{ - auto iter = scripts_.find(start->GetScriptId()); - if (iter == scripts_.end()) { - return "Unknown file name."; - } - JSPtExtractor *extractor = GetExtractor(iter->second->GetUrl()); - if (extractor == nullptr) { - LOG(ERROR, DEBUGGER) << "GetPossibleBreakpoints: extractor is null"; - return "Unknown file name."; - } - - int32_t line = start->GetLine(); - int32_t column = start->GetColumn(); - auto callbackFunc = []([[maybe_unused]] File::EntityId id, [[maybe_unused]] uint32_t offset) -> bool { - return true; - }; - if (extractor->MatchWithLocation(callbackFunc, line, column)) { - std::unique_ptr location = std::make_unique(); - location->SetScriptId(start->GetScriptId()).SetLine(line).SetColumn(column); - locations->emplace_back(std::move(location)); - } - - return {}; -} - -std::optional JSBackend::SetBreakpointByUrl(const CString &url, int32_t lineNumber, - int32_t columnNumber, const std::optional &condition, CString *outId, - CVector> *outLocations) -{ - JSPtExtractor *extractor = GetExtractor(url); - if (extractor == nullptr) { - LOG(ERROR, DEBUGGER) << "SetBreakpointByUrl: extractor is null"; - return "Unknown file name."; - } - - ScriptId scriptId; - CString fileName; - auto scriptFunc = [&scriptId, &fileName](PtScript *script) -> bool { - scriptId = script->GetScriptId(); - fileName = script->GetFileName(); - return true; - }; - if (!MatchScripts(scriptFunc, url, ScriptMatchType::URL)) { - LOG(ERROR, DEBUGGER) << "SetBreakpointByUrl: Unknown url: " << url; - return "Unknown file name."; - } - - auto callbackFunc = [this, fileName, &condition](File::EntityId id, uint32_t offset) -> bool { - JSPtLocation location {fileName.c_str(), id, offset}; - Local condFuncRef = FunctionRef::Undefined(ecmaVm_); - if (condition.has_value() && !condition.value().empty()) { - CString dest; - if (!DecodeAndCheckBase64(condition.value(), dest)) { - LOG(ERROR, DEBUGGER) << "SetBreakpointByUrl: base64 decode failed"; - return false; - } - condFuncRef = DebuggerApi::GenerateFuncFromBuffer(ecmaVm_, dest.data(), dest.size(), - JSPandaFile::ENTRY_MAIN_FUNCTION); - if (condFuncRef->IsUndefined()) { - LOG(ERROR, DEBUGGER) << "SetBreakpointByUrl: generate function failed"; - return false; - } - } - return DebuggerApi::SetBreakpoint(debugger_, location, condFuncRef); - }; - if (!extractor->MatchWithLocation(callbackFunc, lineNumber, columnNumber)) { - LOG(ERROR, DEBUGGER) << "failed to set breakpoint location number: " << lineNumber << ":" << columnNumber; - return "Breakpoint not found."; - } - - BreakpointDetails metaData{lineNumber, 0, url}; - *outId = BreakpointDetails::ToString(metaData); - *outLocations = CVector>(); - std::unique_ptr location = std::make_unique(); - location->SetScriptId(scriptId).SetLine(lineNumber).SetColumn(0); - outLocations->emplace_back(std::move(location)); - - return {}; -} - -std::optional JSBackend::RemoveBreakpoint(const BreakpointDetails &metaData) -{ - JSPtExtractor *extractor = GetExtractor(metaData.url_); - if (extractor == nullptr) { - LOG(ERROR, DEBUGGER) << "RemoveBreakpoint: extractor is null"; - return "Unknown file name."; - } - - CString fileName; - auto scriptFunc = [&fileName](PtScript *script) -> bool { - fileName = script->GetFileName(); - return true; - }; - if (!MatchScripts(scriptFunc, metaData.url_, ScriptMatchType::URL)) { - LOG(ERROR, DEBUGGER) << "RemoveBreakpoint: Unknown url: " << metaData.url_; - return "Unknown file name."; - } - - auto callbackFunc = [this, fileName](File::EntityId id, uint32_t offset) -> bool { - JSPtLocation location {fileName.c_str(), id, offset}; - return DebuggerApi::RemoveBreakpoint(debugger_, location); - }; - if (!extractor->MatchWithLocation(callbackFunc, metaData.line_, metaData.column_)) { - LOG(ERROR, DEBUGGER) << "failed to set breakpoint location number: " - << metaData.line_ << ":" << metaData.column_; - return "Breakpoint not found."; - } - - LOG(INFO, DEBUGGER) << "remove breakpoint32_t line number:" << metaData.line_; - return {}; -} - -std::optional JSBackend::Pause() -{ - pauseOnNextByteCode_ = true; - return {}; -} - -std::optional JSBackend::Resume() -{ - singleStepper_.reset(); - - NotifyResume(); - return {}; -} - -std::optional JSBackend::StepInto() -{ - JSMethod *method = DebuggerApi::GetMethod(ecmaVm_); - JSPtExtractor *extractor = GetExtractor(method->GetJSPandaFile()); - if (extractor == nullptr) { - LOG(ERROR, DEBUGGER) << "StepInto: extractor is null"; - return "Unknown file name."; - } - - singleStepper_ = extractor->GetStepIntoStepper(ecmaVm_); - - NotifyResume(); - return {}; -} - -std::optional JSBackend::StepOver() -{ - JSMethod *method = DebuggerApi::GetMethod(ecmaVm_); - JSPtExtractor *extractor = GetExtractor(method->GetJSPandaFile()); - if (extractor == nullptr) { - LOG(ERROR, DEBUGGER) << "StepOver: extractor is null"; - return "Unknown file name."; - } - - singleStepper_ = extractor->GetStepOverStepper(ecmaVm_); - - NotifyResume(); - return {}; -} - -std::optional JSBackend::StepOut() -{ - JSMethod *method = DebuggerApi::GetMethod(ecmaVm_); - JSPtExtractor *extractor = GetExtractor(method->GetJSPandaFile()); - if (extractor == nullptr) { - LOG(ERROR, DEBUGGER) << "StepOut: extractor is null"; - return "Unknown file name."; - } - - singleStepper_ = extractor->GetStepOutStepper(ecmaVm_); - - NotifyResume(); - return {}; -} - -std::optional JSBackend::CmptEvaluateValue(CallFrameId callFrameId, const CString &expression, - std::unique_ptr *result) -{ - JSMethod *method = DebuggerApi::GetMethod(ecmaVm_); - if (method->IsNativeWithCallField()) { - *result = RemoteObject::FromTagged(ecmaVm_, - Exception::EvalError(ecmaVm_, StringRef::NewFromUtf8(ecmaVm_, "Native Frame not support."))); - return "Native Frame not support."; - } - JSPtExtractor *extractor = GetExtractor(method->GetJSPandaFile()); - if (extractor == nullptr) { - *result = RemoteObject::FromTagged(ecmaVm_, - Exception::EvalError(ecmaVm_, StringRef::NewFromUtf8(ecmaVm_, "Internal error."))); - return "Internal error."; - } - CString varName = expression; - CString varValue; - CString::size_type indexEqual = expression.find_first_of('=', 0); - if (indexEqual != CString::npos) { - varName = Trim(expression.substr(0, indexEqual)); - varValue = Trim(expression.substr(indexEqual + 1, expression.length())); - } - - if (!varValue.empty() && callFrameId != 0) { - *result = RemoteObject::FromTagged(ecmaVm_, - Exception::EvalError(ecmaVm_, StringRef::NewFromUtf8(ecmaVm_, "Native Frame not support."))); - return "Native Frame not support."; - } - - int32_t regIndex = -1; - auto varInfos = extractor->GetLocalVariableTable(method->GetMethodId()); - auto iter = varInfos.find(varName.c_str()); - if (iter != varInfos.end()) { - regIndex = iter->second; - } - if (regIndex != -1) { - if (varValue.empty()) { - return GetVregValue(regIndex, result); - } - return SetVregValue(regIndex, varValue, result); - } - int32_t level = 0; - uint32_t slot = 0; - if (!DebuggerApi::EvaluateLexicalValue(ecmaVm_, varName.c_str(), level, slot)) { - *result = RemoteObject::FromTagged(ecmaVm_, - Exception::EvalError(ecmaVm_, StringRef::NewFromUtf8(ecmaVm_, "Unsupported expression."))); - return "Unsupported expression."; - } - if (varValue.empty()) { - return GetLexicalValue(level, slot, result); - } - return SetLexicalValue(level, slot, varValue, result); -} - -std::optional JSBackend::EvaluateValue(CallFrameId callFrameId, const CString &expression, - std::unique_ptr *result) -{ - if (callFrameId < 0 || callFrameId >= callFrameHandlers_.size()) { - return "Invalid callFrameId."; - } - - CString dest; - if (!DecodeAndCheckBase64(expression, dest)) { - LOG(ERROR, DEBUGGER) << "EvaluateValue: base64 decode failed"; - return CmptEvaluateValue(callFrameId, expression, result); - } - - auto funcRef = DebuggerApi::GenerateFuncFromBuffer(ecmaVm_, dest.data(), dest.size(), - JSPandaFile::ENTRY_MAIN_FUNCTION); - auto res = DebuggerApi::EvaluateViaFuncCall(const_cast(ecmaVm_), funcRef, - callFrameHandlers_[callFrameId]); - if (ecmaVm_->GetJSThread()->HasPendingException()) { - LOG(ERROR, DEBUGGER) << "EvaluateValue: has pending exception"; - CString msg; - DebuggerApi::HandleUncaughtException(ecmaVm_, msg); - *result = RemoteObject::FromTagged(ecmaVm_, - Exception::EvalError(ecmaVm_, StringRef::NewFromUtf8(ecmaVm_, msg.data()))); - return msg; - } - - CacheObjectIfNeeded(res, result); - return {}; -} - -CString JSBackend::Trim(const CString &str) -{ - CString ret = str; - // If ret has only ' ', remove all charactors. - ret.erase(ret.find_last_not_of(' ') + 1); - // If ret has only ' ', remove all charactors. - ret.erase(0, ret.find_first_not_of(' ')); - return ret; -} - -JSPtExtractor *JSBackend::GetExtractor(const JSPandaFile *jsPandaFile) -{ - return JSPandaFileManager::GetInstance()->GetJSPtExtractor(jsPandaFile); -} - -JSPtExtractor *JSBackend::GetExtractor(const CString &url) -{ - auto iter = extractors_.find(url); - if (iter == extractors_.end()) { - return nullptr; - } - - return iter->second; -} - -bool JSBackend::GenerateCallFrames(CVector> *callFrames) -{ - CallFrameId callFrameId = 0; - auto walkerFunc = [this, &callFrameId, &callFrames](const FrameHandler *frameHandler) -> StackState { - JSMethod *method = DebuggerApi::GetMethod(frameHandler); - if (method->IsNativeWithCallField()) { - LOG(INFO, DEBUGGER) << "GenerateCallFrames: Skip CFrame and Native method"; - return StackState::CONTINUE; - } - std::unique_ptr callFrame = std::make_unique(); - if (!GenerateCallFrame(callFrame.get(), frameHandler, callFrameId)) { - if (callFrameId == 0) { - return StackState::FAILED; - } - } else { - SaveCallFrameHandler(frameHandler); - callFrames->emplace_back(std::move(callFrame)); - callFrameId++; - } - return StackState::CONTINUE; - }; - return DebuggerApi::StackWalker(ecmaVm_, walkerFunc); -} - -void JSBackend::SaveCallFrameHandler(const FrameHandler *frameHandler) -{ - auto handlerPtr = DebuggerApi::NewFrameHandler(ecmaVm_); - *handlerPtr = *frameHandler; - callFrameHandlers_.emplace_back(handlerPtr); -} - -bool JSBackend::GenerateCallFrame(CallFrame *callFrame, - const FrameHandler *frameHandler, CallFrameId callFrameId) -{ - JSMethod *method = DebuggerApi::GetMethod(frameHandler); - JSPtExtractor *extractor = GetExtractor(method->GetJSPandaFile()); - if (extractor == nullptr) { - LOG(ERROR, DEBUGGER) << "GenerateCallFrame: extractor is null"; - return false; - } - - // location - std::unique_ptr location = std::make_unique(); - CString url = extractor->GetSourceFile(method->GetMethodId()); - auto scriptFunc = [&location](PtScript *script) -> bool { - location->SetScriptId(script->GetScriptId()); - return true; - }; - if (!MatchScripts(scriptFunc, url, ScriptMatchType::URL)) { - LOG(ERROR, DEBUGGER) << "GenerateCallFrame: Unknown url: " << url; - return false; - } - auto callbackLineFunc = [&location](int32_t line) -> bool { - location->SetLine(line); - return true; - }; - auto callbackColumnFunc = [&location](int32_t column) -> bool { - location->SetColumn(column); - return true; - }; - File::EntityId methodId = method->GetMethodId(); - if (!extractor->MatchLineWithOffset(callbackLineFunc, methodId, DebuggerApi::GetBytecodeOffset(frameHandler)) || - !extractor->MatchColumnWithOffset(callbackColumnFunc, methodId, DebuggerApi::GetBytecodeOffset(frameHandler))) { - LOG(ERROR, DEBUGGER) << "GenerateCallFrame: unknown offset: " << DebuggerApi::GetBytecodeOffset(frameHandler); - return false; - } - - // scopeChain & this - std::unique_ptr thisObj = std::make_unique(); - thisObj->SetType(ObjectType::Undefined); - - CVector> scopeChain; - scopeChain.emplace_back(GetLocalScopeChain(frameHandler, &thisObj)); - scopeChain.emplace_back(GetGlobalScopeChain()); - - // functionName - CString functionName = DebuggerApi::ParseFunctionName(method); - - callFrame->SetCallFrameId(callFrameId) - .SetFunctionName(functionName) - .SetLocation(std::move(location)) - .SetUrl(url) - .SetScopeChain(std::move(scopeChain)) - .SetThis(std::move(thisObj)); - return true; -} - -std::unique_ptr JSBackend::GetLocalScopeChain(const FrameHandler *frameHandler, - std::unique_ptr *thisObj) -{ - auto localScope = std::make_unique(); - - JSMethod *method = DebuggerApi::GetMethod(frameHandler); - JSPtExtractor *extractor = GetExtractor(method->GetJSPandaFile()); - if (extractor == nullptr) { - LOG(ERROR, DEBUGGER) << "GetScopeChain: extractor is null"; - return localScope; - } - - std::unique_ptr local = std::make_unique(); - Local localObj(local->NewObject(ecmaVm_)); - local->SetType(ObjectType::Object) - .SetObjectId(curObjectId_) - .SetClassName(ObjectClassName::Object) - .SetDescription(RemoteObject::ObjectDescription); - auto *sp = DebuggerApi::GetSp(frameHandler); - scopeObjects_[sp] = curObjectId_; - propertiesPair_[curObjectId_++] = Global(ecmaVm_, localObj); - - Local thisVal = JSValueRef::Undefined(ecmaVm_); - GetLocalVariables(frameHandler, method, thisVal, localObj); - CacheObjectIfNeeded(thisVal, thisObj); - - auto methodId = method->GetMethodId(); - const LineNumberTable &lines = extractor->GetLineNumberTable(methodId); - std::unique_ptr startLoc = std::make_unique(); - std::unique_ptr endLoc = std::make_unique(); - auto scriptFunc = [&startLoc, &endLoc, lines](PtScript *script) -> bool { - startLoc->SetScriptId(script->GetScriptId()) - .SetLine(lines.front().line) - .SetColumn(0); - endLoc->SetScriptId(script->GetScriptId()) - .SetLine(lines.back().line + 1) - .SetColumn(0); - return true; - }; - if (MatchScripts(scriptFunc, extractor->GetSourceFile(methodId), ScriptMatchType::URL)) { - localScope->SetType(Scope::Type::Local()) - .SetObject(std::move(local)) - .SetStartLocation(std::move(startLoc)) - .SetEndLocation(std::move(endLoc)); - } - - return localScope; -} - -void JSBackend::GetLocalVariables(const FrameHandler *frameHandler, const JSMethod *method, - Local &thisVal, Local &localObj) -{ - auto methodId = method->GetMethodId(); - auto *extractor = GetExtractor(method->GetJSPandaFile()); - Local value = JSValueRef::Undefined(ecmaVm_); - // in case of arrow function, which doesn't have this in local variable table - bool hasThis = false; - for (const auto &[varName, regIndex] : extractor->GetLocalVariableTable(methodId)) { - value = DebuggerApi::GetVRegValue(ecmaVm_, frameHandler, regIndex); - if (varName == "4newTarget") { - continue; - } - - if (varName == "this") { - thisVal = value; - hasThis = true; - } else { - Local name = JSValueRef::Undefined(ecmaVm_); - if (varName == "4funcObj" && value->IsFunction()) { - auto funcName = Local(value)->GetName(ecmaVm_)->ToString(); - name = StringRef::NewFromUtf8(ecmaVm_, funcName.c_str()); - } else { - name = StringRef::NewFromUtf8(ecmaVm_, varName.c_str()); - } - PropertyAttribute descriptor(value, true, true, true); - localObj->DefineProperty(ecmaVm_, name, descriptor); - } - } - - if (!hasThis) { - thisVal = DebuggerApi::GetLexicalValueInfo(ecmaVm_, "this"); - } - - // closure variables are stored in env - JSTaggedValue env = DebuggerApi::GetEnv(frameHandler); - if (env.IsTaggedArray() && DebuggerApi::GetBytecodeOffset(frameHandler) != 0) { - LexicalEnv *lexEnv = LexicalEnv::Cast(env.GetTaggedObject()); - if (lexEnv->GetScopeInfo().IsHole()) { - return; - } - auto ptr = JSNativePointer::Cast(lexEnv->GetScopeInfo().GetTaggedObject())->GetExternalPointer(); - auto *scopeDebugInfo = reinterpret_cast(ptr); - JSThread *thread = ecmaVm_->GetJSThread(); - for (const auto &[varName, slot] : scopeDebugInfo->scopeInfo) { - // skip possible duplicate variables both in local variable table and env - if (varName == "this" || varName == "4newTarget") { - continue; - } - Local name = StringRef::NewFromUtf8(ecmaVm_, varName.c_str()); - value = JSNApiHelper::ToLocal( - JSHandle(thread, lexEnv->GetProperties(slot))); - PropertyAttribute descriptor(value, true, true, true); - localObj->DefineProperty(ecmaVm_, name, descriptor); - } - } -} - -void JSBackend::UpdateScopeObject(const FrameHandler *frameHandler, - const CString &varName, const Local &newVal) -{ - auto *sp = DebuggerApi::GetSp(frameHandler); - auto iter = scopeObjects_.find(sp); - if (iter == scopeObjects_.end()) { - LOG(ERROR, DEBUGGER) << "UpdateScopeObject: object not found"; - return; - } - - auto objectId = iter->second; - Local localObj = propertiesPair_[objectId].ToLocal(ecmaVm_); - Local name = StringRef::NewFromUtf8(ecmaVm_, varName.c_str()); - if (localObj->Has(ecmaVm_, name)) { - LOG(DEBUG, DEBUGGER) << "UpdateScopeObject: set new value"; - PropertyAttribute descriptor(newVal, true, true, true); - localObj->DefineProperty(ecmaVm_, name, descriptor); - } else { - LOG(ERROR, DEBUGGER) << "UpdateScopeObject: not found " << varName; - } -} - -std::unique_ptr JSBackend::GetGlobalScopeChain() -{ - auto globalScope = std::make_unique(); - - std::unique_ptr global = std::make_unique(); - global->SetType(ObjectType::Object) - .SetObjectId(curObjectId_) - .SetClassName(ObjectClassName::Global) - .SetDescription(RemoteObject::GlobalDescription); - globalScope->SetType(Scope::Type::Global()).SetObject(std::move(global)); - propertiesPair_[curObjectId_++] = Global(ecmaVm_, JSNApi::GetGlobalObject(ecmaVm_)); - return globalScope; -} - -bool JSBackend::GetScriptSource(ScriptId scriptId, CString *source) -{ - auto iter = scripts_.find(scriptId); - if (iter == scripts_.end()) { - *source = ""; - return false; - } - - *source = iter->second->GetScriptSource(); - return true; -} - -void JSBackend::SetPauseOnException(bool flag) -{ - pauseOnException_ = flag; -} - -std::optional JSBackend::ConvertToLocal(Local &taggedValue, std::unique_ptr *result, - const CString &varValue) -{ - if (varValue == "false") { - taggedValue = JSValueRef::False(ecmaVm_); - } else if (varValue == "true") { - taggedValue = JSValueRef::True(ecmaVm_); - } else if (varValue == "undefined") { - taggedValue = JSValueRef::Undefined(ecmaVm_); - } else if (varValue[0] == '\"' && varValue[varValue.length() - 1] == '\"') { - // 2 : 2 means length - taggedValue = StringRef::NewFromUtf8(ecmaVm_, varValue.substr(1, varValue.length() - 2).c_str()); - } else { - auto begin = reinterpret_cast((varValue.c_str())); - auto end = begin + varValue.length(); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) - double d = DebuggerApi::StringToDouble(begin, end, 0); - if (std::isnan(d)) { - *result = RemoteObject::FromTagged(ecmaVm_, - Exception::EvalError(ecmaVm_, StringRef::NewFromUtf8(ecmaVm_, "Unsupported expression."))); - return "Unsupported expression."; - } - taggedValue = NumberRef::New(ecmaVm_, d); - } - return {}; -} - -std::optional JSBackend::SetVregValue(int32_t regIndex, const CString &varValue, - std::unique_ptr *result) -{ - Local taggedValue; - std::optional ret = ConvertToLocal(taggedValue, result, varValue); - if (ret.has_value()) { - return ret; - } - DebuggerApi::SetVRegValue(ecmaVm_, regIndex, taggedValue); - *result = RemoteObject::FromTagged(ecmaVm_, taggedValue); - return {}; -} - -std::optional JSBackend::SetLexicalValue(int32_t level, uint32_t slot, const CString &varValue, - std::unique_ptr *result) -{ - Local taggedValue; - std::optional ret = ConvertToLocal(taggedValue, result, varValue); - if (ret.has_value()) { - return ret; - } - DebuggerApi::SetProperties(ecmaVm_, level, slot, taggedValue); - *result = RemoteObject::FromTagged(ecmaVm_, taggedValue); - return {}; -} - -std::optional JSBackend::GetVregValue(int32_t regIndex, std::unique_ptr *result) -{ - CacheObjectIfNeeded(DebuggerApi::GetVRegValue(ecmaVm_, regIndex), result); - return {}; -} - -std::optional JSBackend::GetLexicalValue(int32_t level, uint32_t slot, std::unique_ptr *result) -{ - CacheObjectIfNeeded(DebuggerApi::GetProperties(ecmaVm_, level, slot), result); - return {}; -} - -void JSBackend::GetProtoOrProtoType(const Local &value, bool isOwn, bool isAccessorOnly, - CVector> *outPropertyDesc) -{ - if (!isAccessorOnly && isOwn && !value->IsProxy()) { - return; - } - // Get Function ProtoOrDynClass - if (value->IsConstructor()) { - Local prototype = Local(value)->GetFunctionPrototype(ecmaVm_); - std::unique_ptr protoObj = std::make_unique(); - CacheObjectIfNeeded(prototype, &protoObj); - std::unique_ptr debuggerProperty = std::make_unique(); - debuggerProperty->SetName("prototype") - .SetWritable(false) - .SetConfigurable(false) - .SetEnumerable(false) - .SetIsOwn(true) - .SetValue(std::move(protoObj)); - outPropertyDesc->emplace_back(std::move(debuggerProperty)); - } - // Get __proto__ - Local proto = Local(value)->GetPrototype(ecmaVm_); - std::unique_ptr protoObj = std::make_unique(); - CacheObjectIfNeeded(proto, &protoObj); - std::unique_ptr debuggerProperty = std::make_unique(); - debuggerProperty->SetName("__proto__") - .SetWritable(true) - .SetConfigurable(true) - .SetEnumerable(false) - .SetIsOwn(true) - .SetValue(std::move(protoObj)); - outPropertyDesc->emplace_back(std::move(debuggerProperty)); -} - -void JSBackend::GetProperties(RemoteObjectId objectId, bool isOwn, bool isAccessorOnly, - CVector> *outPropertyDesc) -{ - auto iter = propertiesPair_.find(objectId); - if (iter == propertiesPair_.end()) { - LOG(ERROR, DEBUGGER) << "JSBackend::GetProperties Unknown object id: " << objectId; - return; - } - Local value = Local(ecmaVm_, iter->second); - if (value.IsEmpty() || !value->IsObject()) { - LOG(ERROR, DEBUGGER) << "JSBackend::GetProperties should a js object"; - return; - } - if (value->IsArrayBuffer()) { - Local arrayBufferRef(value); - AddTypedArrayRefs(arrayBufferRef, outPropertyDesc); - } - Local keys = Local(value)->GetOwnPropertyNames(ecmaVm_); - int32_t length = keys->Length(ecmaVm_); - Local name = JSValueRef::Undefined(ecmaVm_); - for (int32_t i = 0; i < length; ++i) { - name = keys->Get(ecmaVm_, i); - PropertyAttribute jsProperty = PropertyAttribute::Default(); - if (!Local(value)->GetOwnProperty(ecmaVm_, name, jsProperty)) { - continue; - } - std::unique_ptr debuggerProperty = - PropertyDescriptor::FromProperty(ecmaVm_, name, jsProperty); - if (isAccessorOnly && !jsProperty.HasGetter() && !jsProperty.HasSetter()) { - continue; - } - if (jsProperty.HasGetter()) { - debuggerProperty->GetGet()->SetObjectId(curObjectId_); - propertiesPair_[curObjectId_++] = Global(ecmaVm_, jsProperty.GetGetter(ecmaVm_)); - } - if (jsProperty.HasSetter()) { - debuggerProperty->GetSet()->SetObjectId(curObjectId_); - propertiesPair_[curObjectId_++] = Global(ecmaVm_, jsProperty.GetSetter(ecmaVm_)); - } - if (jsProperty.HasValue()) { - Local vValue = jsProperty.GetValue(ecmaVm_); - if (vValue->IsObject() && !vValue->IsProxy()) { - debuggerProperty->GetValue()->SetObjectId(curObjectId_); - propertiesPair_[curObjectId_++] = Global(ecmaVm_, vValue); - } - } - if (name->IsSymbol()) { - debuggerProperty->GetSymbol()->SetObjectId(curObjectId_); - propertiesPair_[curObjectId_++] = Global(ecmaVm_, name); - } - outPropertyDesc->emplace_back(std::move(debuggerProperty)); - } - GetProtoOrProtoType(value, isOwn, isAccessorOnly, outPropertyDesc); - GetAdditionalProperties(value, outPropertyDesc); -} - -template -void JSBackend::AddTypedArrayRef(Local arrayBufferRef, int32_t length, const char* name, - CVector> *outPropertyDesc) -{ - Local jsValueRefTypedArray(TypedArrayRef::New(ecmaVm_, arrayBufferRef, 0, length)); - std::unique_ptr remoteObjectTypedArray = RemoteObject::FromTagged(ecmaVm_, jsValueRefTypedArray); - remoteObjectTypedArray->SetObjectId(curObjectId_); - propertiesPair_[curObjectId_++] = Global(ecmaVm_, jsValueRefTypedArray); - std::unique_ptr debuggerProperty = std::make_unique(); - debuggerProperty->SetName(name) - .SetWritable(true) - .SetConfigurable(true) - .SetEnumerable(false) - .SetIsOwn(true) - .SetValue(std::move(remoteObjectTypedArray)); - outPropertyDesc->emplace_back(std::move(debuggerProperty)); -} - -void JSBackend::AddTypedArrayRefs(Local arrayBufferRef, - CVector> *outPropertyDesc) -{ - int32_t arrayBufferByteLength = arrayBufferRef->ByteLength(ecmaVm_); - - int32_t typedArrayLength = arrayBufferByteLength; - if (typedArrayLength > JSTypedArray::MAX_TYPED_ARRAY_INDEX) { - return; - } - AddTypedArrayRef(arrayBufferRef, typedArrayLength, "[[Int8Array]]", outPropertyDesc); - AddTypedArrayRef(arrayBufferRef, typedArrayLength, "[[Uint8Array]]", outPropertyDesc); - AddTypedArrayRef(arrayBufferRef, typedArrayLength, "[[Uint8ClampedArray]]", outPropertyDesc); - - if ((arrayBufferByteLength % NumberSize::BYTES_OF_16BITS) == 0) { - typedArrayLength = arrayBufferByteLength / NumberSize::BYTES_OF_16BITS; - AddTypedArrayRef(arrayBufferRef, typedArrayLength, "[[Int16Array]]", outPropertyDesc); - AddTypedArrayRef(arrayBufferRef, typedArrayLength, "[[Uint16Array]]", outPropertyDesc); - } - - if ((arrayBufferByteLength % NumberSize::BYTES_OF_32BITS) == 0) { - typedArrayLength = arrayBufferByteLength / NumberSize::BYTES_OF_32BITS; - AddTypedArrayRef(arrayBufferRef, typedArrayLength, "[[Int32Array]]", outPropertyDesc); - AddTypedArrayRef(arrayBufferRef, typedArrayLength, "[[Uint32Array]]", outPropertyDesc); - AddTypedArrayRef(arrayBufferRef, typedArrayLength, "[[Float32Array]]", outPropertyDesc); - } - - if ((arrayBufferByteLength % NumberSize::BYTES_OF_64BITS) == 0) { - typedArrayLength = arrayBufferByteLength / NumberSize::BYTES_OF_64BITS; - AddTypedArrayRef(arrayBufferRef, typedArrayLength, "[[Float64Array]]", outPropertyDesc); - AddTypedArrayRef(arrayBufferRef, typedArrayLength, "[[BigInt64Array]]", outPropertyDesc); - AddTypedArrayRef(arrayBufferRef, typedArrayLength, "[[BigUint64Array]]", outPropertyDesc); - } -} - -void JSBackend::GetAdditionalProperties(const Local &value, - CVector> *outPropertyDesc) -{ - // The length of the TypedArray have to be limited(less than or equal to lengthTypedArrayLimit) until we construct - // the PropertyPreview class. Let lengthTypedArrayLimit be 10000 temporarily. - static const int32_t lengthTypedArrayLimit = 10000; - - // The width of the string-expression for JSTypedArray::MAX_TYPED_ARRAY_INDEX which is euqal to - // JSObject::MAX_ELEMENT_INDEX which is equal to std::numeric_limits::max(). (42,9496,7295) - static const int32_t widthStrExprMaxElementIndex = 10; - - if (value->IsTypedArray()) { - Local localTypedArrayRef(value); - int32_t lengthTypedArray = static_cast(localTypedArrayRef->ArrayLength(ecmaVm_)); - if (lengthTypedArray < 0 || lengthTypedArray > lengthTypedArrayLimit) { - LOG(ERROR, DEBUGGER) << "The length of the TypedArray is non-compliant or unsupported."; - return; - } - for (int32_t i = 0; i < lengthTypedArray; i++) { - Local localValRefElement = localTypedArrayRef->Get(ecmaVm_, i); - std::unique_ptr remoteObjElement = RemoteObject::FromTagged(ecmaVm_, localValRefElement); - remoteObjElement->SetObjectId(curObjectId_); - propertiesPair_[curObjectId_++] = Global(ecmaVm_, localValRefElement); - std::unique_ptr debuggerProperty = std::make_unique(); - - std::ostringstream osNameElement; - osNameElement << std::right << std::setw(widthStrExprMaxElementIndex) << i; - CString cStrNameElement = CString(osNameElement.str()); - debuggerProperty->SetName(cStrNameElement) - .SetWritable(true) - .SetConfigurable(true) - .SetEnumerable(false) - .SetIsOwn(true) - .SetValue(std::move(remoteObjElement)); - outPropertyDesc->emplace_back(std::move(debuggerProperty)); - } - } -} - -void JSBackend::CallFunctionOn([[maybe_unused]] const CString &functionDeclaration, - std::unique_ptr *outRemoteObject) -{ - // Return EvalError temporarily. - auto error = Exception::EvalError(ecmaVm_, StringRef::NewFromUtf8(ecmaVm_, "Unsupport eval now")); - - *outRemoteObject = RemoteObject::FromTagged(ecmaVm_, error); -} - -void JSBackend::GetHeapUsage(double *usedSize, double *totalSize) -{ - auto ecmaVm = const_cast(static_cast(frontend_)->GetEcmaVM()); - *totalSize = static_cast(DFXJSNApi::GetHeapTotalSize(ecmaVm)); - *usedSize = static_cast(DFXJSNApi::GetHeapUsedSize(ecmaVm)); -} - -void JSBackend::CacheObjectIfNeeded(const Local &valRef, std::unique_ptr *remoteObj) -{ - *remoteObj = RemoteObject::FromTagged(ecmaVm_, valRef); - if (valRef->IsObject() && !valRef->IsProxy()) { - (*remoteObj)->SetObjectId(curObjectId_); - propertiesPair_[curObjectId_++] = Global(ecmaVm_, valRef); - } -} - -void JSBackend::CleanUpOnPaused() -{ - curObjectId_ = 0; - propertiesPair_.clear(); - - callFrameHandlers_.clear(); - scopeObjects_.clear(); -} - -bool JSBackend::DecodeAndCheckBase64(const CString &src, CString &dest) -{ - dest.resize(base64::decoded_size(src.size())); - auto [numOctets, _] = base64::decode(dest.data(), src.data(), src.size()); - dest.resize(numOctets); - if (numOctets > File::MAGIC_SIZE && - memcmp(dest.data(), File::MAGIC.data(), File::MAGIC_SIZE) == 0) { - return true; - } - return false; -} -} // namespace panda::ecmascript::tooling diff --git a/ecmascript/tooling/agent/js_backend.h b/ecmascript/tooling/agent/js_backend.h deleted file mode 100644 index 406f4c15bc9fcea4ecd94e1eac7d74a558fa572d..0000000000000000000000000000000000000000 --- a/ecmascript/tooling/agent/js_backend.h +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (c) 2021-2022 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. - */ - -#ifndef ECMASCRIPT_TOOLING_AGENT_JS_BACKEND_H -#define ECMASCRIPT_TOOLING_AGENT_JS_BACKEND_H - -#include "ecmascript/tooling/agent/js_pt_hooks.h" -#include "ecmascript/tooling/base/pt_types.h" -#include "ecmascript/tooling/dispatcher.h" -#include "ecmascript/tooling/interface/js_debugger.h" -#include "ecmascript/tooling/interface/js_debugger_manager.h" -#include "ecmascript/tooling/js_pt_extractor.h" -#include "libpandabase/macros.h" -#include "ecmascript/napi/include/dfx_jsnapi.h" - -namespace panda::ecmascript::tooling { -class JSBackend { -public: - enum NumberSize : uint8_t { BYTES_OF_16BITS = 2, BYTES_OF_32BITS = 4, BYTES_OF_64BITS = 8 }; - - explicit JSBackend(FrontEnd *frontend); - explicit JSBackend(const EcmaVM *vm); - ~JSBackend(); - - // add for hooks - void WaitForDebugger(); - void NotifyPaused(std::optional location, PauseReason reason); - void NotifyResume(); - void NotifyAllScriptParsed(); - bool NotifyScriptParsed(ScriptId scriptId, const CString &fileName); - bool StepComplete(const JSPtLocation &location); - void PendingJobEntry(); - - std::optional GetPossibleBreakpoints(Location *start, Location *end, - CVector> *locations); - JSDebugger *GetDebugger() const - { - return debugger_; - } - - std::optional SetBreakpointByUrl(const CString &url, int32_t lineNumber, int32_t columnNumber, - const std::optional &condition, CString *outId, - CVector> *outLocations); - std::optional RemoveBreakpoint(const BreakpointDetails &metaData); - - std::optional Pause(); - std::optional Resume(); - std::optional StepInto(); - std::optional StepOver(); - std::optional StepOut(); - std::optional EvaluateValue(CallFrameId callFrameId, const CString &expression, - std::unique_ptr *result); - std::optional CmptEvaluateValue(CallFrameId callFrameId, const CString &expression, - std::unique_ptr *result); - - /** - * @brief: match first script and callback - * - * @return: true means matched and callback execute success - */ - template - bool MatchScripts(const Callback &cb, const CString &matchStr, ScriptMatchType type) const - { - for (const auto &script : scripts_) { - CString value; - switch (type) { - case ScriptMatchType::URL: { - value = script.second->GetUrl(); - break; - } - case ScriptMatchType::FILE_NAME: { - value = script.second->GetFileName(); - break; - } - case ScriptMatchType::HASH: { - value = script.second->GetHash(); - break; - } - default: { - return false; - } - } - if (matchStr == value) { - return cb(script.second.get()); - } - } - return false; - } - - bool GetScriptSource(ScriptId scriptId, CString *source); - - void SetPauseOnException(bool flag); - void GetProperties(RemoteObjectId objectId, bool isOwn, bool isAccessorOnly, - CVector> *outPropertyDesc); - void CallFunctionOn(const CString &functionDeclaration, std::unique_ptr *outRemoteObject); - void GetHeapUsage(double *usedSize, double *totalSize); - // public for testcases - bool GenerateCallFrames(CVector> *callFrames); - const EcmaVM *GetEcmaVm() const - { - return ecmaVm_; - } - -private: - NO_MOVE_SEMANTIC(JSBackend); - NO_COPY_SEMANTIC(JSBackend); - CString Trim(const CString &str); - JSPtExtractor *GenerateExtractor(const JSPandaFile *jsPandaFile); - JSPtExtractor *GetExtractor(const JSPandaFile *jsPandaFile); - JSPtExtractor *GetExtractor(const CString &url); - bool GenerateCallFrame(CallFrame *callFrame, const FrameHandler *frameHandler, CallFrameId frameId); - void SaveCallFrameHandler(const FrameHandler *frameHandler); - std::unique_ptr GetLocalScopeChain(const FrameHandler *frameHandler, - std::unique_ptr *thisObj); - void GetLocalVariables(const FrameHandler *frameHandler, const JSMethod *method, - Local &thisVal, Local &localObj); - void CacheObjectIfNeeded(const Local &valRef, std::unique_ptr *remoteObj); - void CleanUpOnPaused(); - std::unique_ptr GetGlobalScopeChain(); - void UpdateScopeObject(const FrameHandler *frameHandler, const CString &varName, const Local &newVal); - std::optional ConvertToLocal(Local &taggedValue, std::unique_ptr *result, - const CString &varValue); - std::optional SetVregValue(int32_t regIndex, const CString &varValue, - std::unique_ptr *result); - std::optional SetLexicalValue(int32_t level, uint32_t slot, const CString &varValue, - std::unique_ptr *result); - std::optional GetVregValue(int32_t regIndex, std::unique_ptr *result); - std::optional GetLexicalValue(int32_t level, uint32_t slot, std::unique_ptr *result); - void GetProtoOrProtoType(const Local &value, bool isOwn, bool isAccessorOnly, - CVector> *outPropertyDesc); - bool DecodeAndCheckBase64(const CString &src, CString &dest); - void AddTypedArrayRefs(Local arrayBufferRef, - CVector> *outPropertyDesc); - template - void AddTypedArrayRef(Local arrayBufferRef, int32_t length, - const char* name, CVector> *outPropertyDesc); - void GetAdditionalProperties(const Local &value, - CVector> *outPropertyDesc); - bool IsSkipLine(const JSPtLocation &location); - - FrontEnd *frontend_ {nullptr}; - const EcmaVM *ecmaVm_ {nullptr}; - std::unique_ptr hooks_ {nullptr}; - JSDebugger *debugger_ {nullptr}; - CUnorderedMap extractors_ {}; - CUnorderedMap> scripts_ {}; - CUnorderedMap> propertiesPair_ {}; - CUnorderedMap scopeObjects_ {}; - CVector> callFrameHandlers_; - RemoteObjectId curObjectId_ {0}; - bool pauseOnException_ {false}; - bool pauseOnNextByteCode_ {false}; - std::unique_ptr singleStepper_ {nullptr}; - JsDebuggerManager::ObjectUpdaterFunc updaterFunc_ {nullptr}; - - friend class JSPtHooks; -}; -} // namespace panda::ecmascript::tooling -#endif diff --git a/ecmascript/tooling/agent/profiler_impl.cpp b/ecmascript/tooling/agent/profiler_impl.cpp index 5116191b55182c7392f6f67e96579efa46ef12bc..3384d8a336b4e1f43cb6b8e94b41a7f6c11bdb17 100644 --- a/ecmascript/tooling/agent/profiler_impl.cpp +++ b/ecmascript/tooling/agent/profiler_impl.cpp @@ -15,29 +15,32 @@ #include "ecmascript/tooling/agent/profiler_impl.h" -namespace panda::ecmascript::tooling { -ProfilerImpl::DispatcherImpl::DispatcherImpl(FrontEnd *frontend, std::unique_ptr profiler) - : DispatcherBase(frontend), profiler_(std::move(profiler)) -{ - dispatcherTable_["disable"] = &ProfilerImpl::DispatcherImpl::Disable; - dispatcherTable_["enable"] = &ProfilerImpl::DispatcherImpl::Enable; - dispatcherTable_["start"] = &ProfilerImpl::DispatcherImpl::Start; - dispatcherTable_["stop"] = &ProfilerImpl::DispatcherImpl::Stop; - dispatcherTable_["getBestEffortCoverage"] = &ProfilerImpl::DispatcherImpl::GetBestEffortCoverage; - dispatcherTable_["stopPreciseCoverage"] = &ProfilerImpl::DispatcherImpl::StopPreciseCoverage; - dispatcherTable_["takePreciseCoverage"] = &ProfilerImpl::DispatcherImpl::TakePreciseCoverage; - dispatcherTable_["startPreciseCoverage"] = &ProfilerImpl::DispatcherImpl::StartPreciseCoverage; - dispatcherTable_["startTypeProfile"] = &ProfilerImpl::DispatcherImpl::StartTypeProfile; - dispatcherTable_["stopTypeProfile"] = &ProfilerImpl::DispatcherImpl::StopTypeProfile; - dispatcherTable_["takeTypeProfile"] = &ProfilerImpl::DispatcherImpl::TakeTypeProfile; -} +#include "ecmascript/napi/include/dfx_jsnapi.h" +#include "ecmascript/tooling/base/pt_events.h" +#include "ecmascript/tooling/protocol_channel.h" +#include "libpandabase/utils/logger.h" +namespace panda::ecmascript::tooling { void ProfilerImpl::DispatcherImpl::Dispatch(const DispatchRequest &request) { - CString method = request.GetMethod(); + static CUnorderedMap dispatcherTable { + { "disable", &ProfilerImpl::DispatcherImpl::Disable }, + { "enable", &ProfilerImpl::DispatcherImpl::Enable }, + { "start", &ProfilerImpl::DispatcherImpl::Start }, + { "stop", &ProfilerImpl::DispatcherImpl::Stop }, + { "getBestEffortCoverage", &ProfilerImpl::DispatcherImpl::GetBestEffortCoverage }, + { "stopPreciseCoverage", &ProfilerImpl::DispatcherImpl::StopPreciseCoverage }, + { "takePreciseCoverage", &ProfilerImpl::DispatcherImpl::TakePreciseCoverage }, + { "startPreciseCoverage", &ProfilerImpl::DispatcherImpl::StartPreciseCoverage }, + { "startTypeProfile", &ProfilerImpl::DispatcherImpl::StartTypeProfile }, + { "stopTypeProfile", &ProfilerImpl::DispatcherImpl::StopTypeProfile }, + { "takeTypeProfile", &ProfilerImpl::DispatcherImpl::TakeTypeProfile } + }; + + const CString &method = request.GetMethod(); LOG(DEBUG, DEBUGGER) << "dispatch [" << method << "] to ProfilerImpl"; - auto entry = dispatcherTable_.find(method); - if (entry != dispatcherTable_.end() && entry->second != nullptr) { + auto entry = dispatcherTable.find(method); + if (entry != dispatcherTable.end() && entry->second != nullptr) { (this->*(entry->second))(request); } else { SendResponse(request, DispatchResponse::Fail("Unknown method: " + method), nullptr); @@ -128,6 +131,41 @@ void ProfilerImpl::DispatcherImpl::TakeTypeProfile(const DispatchRequest &reques SendResponse(request, response, std::move(result)); } +bool ProfilerImpl::Frontend::AllowNotify() const +{ + return channel_ != nullptr; +} + +void ProfilerImpl::Frontend::ConsoleProfileFinished() +{ + if (!AllowNotify()) { + return; + } + + auto consoleProfileFinished = std::make_unique(); + channel_->SendNotification(std::move(consoleProfileFinished)); +} + +void ProfilerImpl::Frontend::ConsoleProfileStarted() +{ + if (!AllowNotify()) { + return; + } + + auto consoleProfileStarted = std::make_unique(); + channel_->SendNotification(std::move(consoleProfileStarted)); +} + +void ProfilerImpl::Frontend::PreciseCoverageDeltaUpdate() +{ + if (!AllowNotify()) { + return; + } + + auto preciseCoverageDeltaUpdate = std::make_unique(); + channel_->SendNotification(std::move(preciseCoverageDeltaUpdate)); +} + DispatchResponse ProfilerImpl::Disable() { LOG(ERROR, DEBUGGER) << "Disable not support now."; @@ -142,8 +180,7 @@ DispatchResponse ProfilerImpl::Enable() DispatchResponse ProfilerImpl::Start() { - auto ecmaVm = const_cast(backend_->GetEcmaVm()); - panda::DFXJSNApi::StartCpuProfilerForInfo(ecmaVm); + panda::DFXJSNApi::StartCpuProfilerForInfo(vm_); return DispatchResponse::Ok(); } diff --git a/ecmascript/tooling/agent/profiler_impl.h b/ecmascript/tooling/agent/profiler_impl.h index a88c79e61c87c180c6db5c51e39bcf2b68505fee..0153672fd9daf78da294c263611290647347af98 100644 --- a/ecmascript/tooling/agent/profiler_impl.h +++ b/ecmascript/tooling/agent/profiler_impl.h @@ -16,21 +16,17 @@ #ifndef ECMASCRIPT_TOOLING_AGENT_PROFILER_IMPL_H #define ECMASCRIPT_TOOLING_AGENT_PROFILER_IMPL_H -#include "libpandabase/macros.h" -#include "libpandabase/utils/logger.h" #include "ecmascript/dfx/cpu_profiler/samples_record.h" -#include "ecmascript/napi/include/dfx_jsnapi.h" -#include "ecmascript/tooling/agent/js_backend.h" #include "ecmascript/tooling/base/pt_params.h" #include "ecmascript/tooling/base/pt_returns.h" #include "ecmascript/tooling/dispatcher.h" -#include "ecmascript/tooling/front_end.h" +#include "libpandabase/macros.h" namespace panda::ecmascript::tooling { using CpuProfileNode = ecmascript::ProfileNode; class ProfilerImpl final { public: - explicit ProfilerImpl(JSBackend *backend) : backend_(backend) {} + ProfilerImpl(const EcmaVM *vm, ProtocolChannel *channel) : vm_(vm), frontend_(channel) {} ~ProfilerImpl() = default; DispatchResponse Disable(); @@ -50,8 +46,10 @@ public: class DispatcherImpl final : public DispatcherBase { public: - DispatcherImpl(FrontEnd *frontend, std::unique_ptr profiler); + DispatcherImpl(ProtocolChannel *channel, std::unique_ptr profiler) + : DispatcherBase(channel), profiler_(std::move(profiler)) {} ~DispatcherImpl() override = default; + void Dispatch(const DispatchRequest &request) override; void Enable(const DispatchRequest &request); void Disable(const DispatchRequest &request); @@ -70,15 +68,29 @@ public: NO_MOVE_SEMANTIC(DispatcherImpl); using AgentHandler = void (ProfilerImpl::DispatcherImpl::*)(const DispatchRequest &request); - CUnorderedMap dispatcherTable_ {}; std::unique_ptr profiler_ {}; }; + class Frontend { + public: + explicit Frontend(ProtocolChannel *channel) : channel_(channel) {} + + void ConsoleProfileFinished(); + void ConsoleProfileStarted(); + void PreciseCoverageDeltaUpdate(); + + private: + bool AllowNotify() const; + + ProtocolChannel *channel_ {nullptr}; + }; + private: NO_COPY_SEMANTIC(ProfilerImpl); NO_MOVE_SEMANTIC(ProfilerImpl); - JSBackend *backend_ {nullptr}; + const EcmaVM *vm_ {nullptr}; + [[maybe_unused]] Frontend frontend_; }; } // namespace panda::ecmascript::tooling #endif diff --git a/ecmascript/tooling/agent/runtime_impl.cpp b/ecmascript/tooling/agent/runtime_impl.cpp index 474c6cd820fd6fb3a10e23baa3d8b8f37e9d9773..9ad5d8a0b3153b407deda7b91359b6987470c72c 100644 --- a/ecmascript/tooling/agent/runtime_impl.cpp +++ b/ecmascript/tooling/agent/runtime_impl.cpp @@ -15,28 +15,29 @@ #include "ecmascript/tooling/agent/runtime_impl.h" +#include + +#include "ecmascript/napi/include/dfx_jsnapi.h" #include "ecmascript/tooling/base/pt_returns.h" +#include "ecmascript/tooling/protocol_channel.h" #include "libpandabase/utils/logger.h" namespace panda::ecmascript::tooling { -RuntimeImpl::DispatcherImpl::DispatcherImpl(FrontEnd *frontend, std::unique_ptr runtime) - : DispatcherBase(frontend), runtime_(std::move(runtime)) -{ - dispatcherTable_["enable"] = &RuntimeImpl::DispatcherImpl::Enable; - dispatcherTable_["disable"] = &RuntimeImpl::DispatcherImpl::Disable; - dispatcherTable_["getProperties"] = &RuntimeImpl::DispatcherImpl::GetProperties; - dispatcherTable_["runIfWaitingForDebugger"] = &RuntimeImpl::DispatcherImpl::RunIfWaitingForDebugger; - dispatcherTable_["callFunctionOn"] = &RuntimeImpl::DispatcherImpl::CallFunctionOn; - dispatcherTable_["getHeapUsage"] = &RuntimeImpl::DispatcherImpl::GetHeapUsage; -} - void RuntimeImpl::DispatcherImpl::Dispatch(const DispatchRequest &request) { - CString method = request.GetMethod(); + static CUnorderedMap dispatcherTable { + { "enable", &RuntimeImpl::DispatcherImpl::Enable }, + { "getProperties", &RuntimeImpl::DispatcherImpl::GetProperties }, + { "runIfWaitingForDebugger", &RuntimeImpl::DispatcherImpl::RunIfWaitingForDebugger }, + { "callFunctionOn", &RuntimeImpl::DispatcherImpl::CallFunctionOn }, + { "getHeapUsage", &RuntimeImpl::DispatcherImpl::GetHeapUsage } + }; + + const CString &method = request.GetMethod(); LOG(DEBUG, DEBUGGER) << "dispatch [" << method << "] to RuntimeImpl"; - auto entry = dispatcherTable_.find(method); - if (entry != dispatcherTable_.end()) { + auto entry = dispatcherTable.find(method); + if (entry != dispatcherTable.end()) { (this->*(entry->second))(request); } else { LOG(ERROR, DEBUGGER) << "unknown method: " << method; @@ -116,6 +117,20 @@ void RuntimeImpl::DispatcherImpl::GetHeapUsage(const DispatchRequest &request) SendResponse(request, response, std::move(result)); } +bool RuntimeImpl::Frontend::AllowNotify() const +{ + return channel_ != nullptr; +} + +void RuntimeImpl::Frontend::RunIfWaitingForDebugger() +{ + if (!AllowNotify()) { + return; + } + + channel_->RunIfWaitingForDebugger(); +} + DispatchResponse RuntimeImpl::Enable() { return DispatchResponse::Ok(); @@ -128,7 +143,25 @@ DispatchResponse RuntimeImpl::Disable() DispatchResponse RuntimeImpl::RunIfWaitingForDebugger() { - return DispatchResponse::Create(backend_->Resume()); + frontend_.RunIfWaitingForDebugger(); + return DispatchResponse::Ok(); +} + +DispatchResponse RuntimeImpl::CallFunctionOn([[maybe_unused]] std::unique_ptr params, + std::unique_ptr *outRemoteObject, + [[maybe_unused]] std::optional> *outExceptionDetails) +{ + // Return EvalError temporarily. + auto error = Exception::EvalError(vm_, StringRef::NewFromUtf8(vm_, "Unsupport eval now")); + *outRemoteObject = RemoteObject::FromTagged(vm_, error); + return DispatchResponse::Ok(); +} + +DispatchResponse RuntimeImpl::GetHeapUsage(double *usedSize, double *totalSize) +{ + *totalSize = static_cast(DFXJSNApi::GetHeapTotalSize(vm_)); + *usedSize = static_cast(DFXJSNApi::GetHeapUsedSize(vm_)); + return DispatchResponse::Ok(); } DispatchResponse RuntimeImpl::GetProperties(std::unique_ptr params, @@ -137,24 +170,190 @@ DispatchResponse RuntimeImpl::GetProperties(std::unique_ptr [[maybe_unused]] std::optional>> *outPrivateProps, [[maybe_unused]] std::optional> *outExceptionDetails) { - backend_->GetProperties(params->GetObjectId(), - params->GetOwnProperties(), - params->GetAccessPropertiesOnly(), - outPropertyDesc); + RemoteObjectId objectId = params->GetObjectId(); + bool isOwn = params->GetOwnProperties(); + bool isAccessorOnly = params->GetAccessPropertiesOnly(); + auto iter = properties_.find(objectId); + if (iter == properties_.end()) { + LOG(ERROR, DEBUGGER) << "RuntimeImpl::GetProperties Unknown object id: " << objectId; + return DispatchResponse::Fail("Unknown object id"); + } + Local value = Local(vm_, iter->second); + if (value.IsEmpty() || !value->IsObject()) { + LOG(ERROR, DEBUGGER) << "RuntimeImpl::GetProperties should a js object"; + return DispatchResponse::Fail("Not a object"); + } + if (value->IsArrayBuffer()) { + Local arrayBufferRef(value); + AddTypedArrayRefs(arrayBufferRef, outPropertyDesc); + } + Local keys = Local(value)->GetOwnPropertyNames(vm_); + int32_t length = keys->Length(vm_); + Local name = JSValueRef::Undefined(vm_); + for (int32_t i = 0; i < length; ++i) { + name = keys->Get(vm_, i); + PropertyAttribute jsProperty = PropertyAttribute::Default(); + if (!Local(value)->GetOwnProperty(vm_, name, jsProperty)) { + continue; + } + std::unique_ptr debuggerProperty = + PropertyDescriptor::FromProperty(vm_, name, jsProperty); + if (isAccessorOnly && !jsProperty.HasGetter() && !jsProperty.HasSetter()) { + continue; + } + if (jsProperty.HasGetter()) { + debuggerProperty->GetGet()->SetObjectId(curObjectId_); + properties_[curObjectId_++] = Global(vm_, jsProperty.GetGetter(vm_)); + } + if (jsProperty.HasSetter()) { + debuggerProperty->GetSet()->SetObjectId(curObjectId_); + properties_[curObjectId_++] = Global(vm_, jsProperty.GetSetter(vm_)); + } + if (jsProperty.HasValue()) { + Local vValue = jsProperty.GetValue(vm_); + if (vValue->IsObject() && !vValue->IsProxy()) { + debuggerProperty->GetValue()->SetObjectId(curObjectId_); + properties_[curObjectId_++] = Global(vm_, vValue); + } + } + if (name->IsSymbol()) { + debuggerProperty->GetSymbol()->SetObjectId(curObjectId_); + properties_[curObjectId_++] = Global(vm_, name); + } + outPropertyDesc->emplace_back(std::move(debuggerProperty)); + } + GetProtoOrProtoType(value, isOwn, isAccessorOnly, outPropertyDesc); + GetAdditionalProperties(value, outPropertyDesc); + return DispatchResponse::Ok(); } -DispatchResponse RuntimeImpl::CallFunctionOn(std::unique_ptr params, - std::unique_ptr *outRemoteObject, - [[maybe_unused]] std::optional> *outExceptionDetails) +void RuntimeImpl::AddTypedArrayRefs(Local arrayBufferRef, + CVector> *outPropertyDesc) { - backend_->CallFunctionOn(params->GetFunctionDeclaration(), outRemoteObject); - return DispatchResponse::Ok(); + int32_t arrayBufferByteLength = arrayBufferRef->ByteLength(vm_); + int32_t typedArrayLength = arrayBufferByteLength; + AddTypedArrayRef(arrayBufferRef, typedArrayLength, "[[Int8Array]]", outPropertyDesc); + AddTypedArrayRef(arrayBufferRef, typedArrayLength, "[[Uint8Array]]", outPropertyDesc); + AddTypedArrayRef(arrayBufferRef, typedArrayLength, "[[Uint8ClampedArray]]", outPropertyDesc); + + if ((arrayBufferByteLength % NumberSize::BYTES_OF_16BITS) == 0) { + typedArrayLength = arrayBufferByteLength / NumberSize::BYTES_OF_16BITS; + AddTypedArrayRef(arrayBufferRef, typedArrayLength, "[[Int16Array]]", outPropertyDesc); + AddTypedArrayRef(arrayBufferRef, typedArrayLength, "[[Uint16Array]]", outPropertyDesc); + } + + if ((arrayBufferByteLength % NumberSize::BYTES_OF_32BITS) == 0) { + typedArrayLength = arrayBufferByteLength / NumberSize::BYTES_OF_32BITS; + AddTypedArrayRef(arrayBufferRef, typedArrayLength, "[[Int32Array]]", outPropertyDesc); + AddTypedArrayRef(arrayBufferRef, typedArrayLength, "[[Uint32Array]]", outPropertyDesc); + AddTypedArrayRef(arrayBufferRef, typedArrayLength, "[[Float32Array]]", outPropertyDesc); + } + + if ((arrayBufferByteLength % NumberSize::BYTES_OF_64BITS) == 0) { + typedArrayLength = arrayBufferByteLength / NumberSize::BYTES_OF_64BITS; + AddTypedArrayRef(arrayBufferRef, typedArrayLength, "[[Float64Array]]", outPropertyDesc); + AddTypedArrayRef(arrayBufferRef, typedArrayLength, "[[BigInt64Array]]", outPropertyDesc); + AddTypedArrayRef(arrayBufferRef, typedArrayLength, "[[BigUint64Array]]", outPropertyDesc); + } } -DispatchResponse RuntimeImpl::GetHeapUsage(double *usedSize, double *totalSize) +template +void RuntimeImpl::AddTypedArrayRef(Local arrayBufferRef, int32_t length, const char* name, + CVector> *outPropertyDesc) { - backend_->GetHeapUsage(usedSize, totalSize); - return DispatchResponse::Ok(); + Local jsValueRefTypedArray(TypedArrayRef::New(vm_, arrayBufferRef, 0, length)); + std::unique_ptr remoteObjectTypedArray = RemoteObject::FromTagged(vm_, jsValueRefTypedArray); + remoteObjectTypedArray->SetObjectId(curObjectId_); + properties_[curObjectId_++] = Global(vm_, jsValueRefTypedArray); + std::unique_ptr debuggerProperty = std::make_unique(); + debuggerProperty->SetName(name) + .SetWritable(true) + .SetConfigurable(true) + .SetEnumerable(false) + .SetIsOwn(true) + .SetValue(std::move(remoteObjectTypedArray)); + outPropertyDesc->emplace_back(std::move(debuggerProperty)); +} + +void RuntimeImpl::CacheObjectIfNeeded(Local valRef, RemoteObject *remoteObj) +{ + if (valRef->IsObject() && !valRef->IsProxy()) { + remoteObj->SetObjectId(curObjectId_); + properties_[curObjectId_++] = Global(vm_, valRef); + } +} + +void RuntimeImpl::GetProtoOrProtoType(const Local &value, bool isOwn, bool isAccessorOnly, + CVector> *outPropertyDesc) +{ + if (!isAccessorOnly && isOwn && !value->IsProxy()) { + return; + } + // Get Function ProtoOrDynClass + if (value->IsConstructor()) { + Local prototype = Local(value)->GetFunctionPrototype(vm_); + std::unique_ptr protoObj = RemoteObject::FromTagged(vm_, prototype); + CacheObjectIfNeeded(prototype, protoObj.get()); + std::unique_ptr debuggerProperty = std::make_unique(); + debuggerProperty->SetName("prototype") + .SetWritable(false) + .SetConfigurable(false) + .SetEnumerable(false) + .SetIsOwn(true) + .SetValue(std::move(protoObj)); + outPropertyDesc->emplace_back(std::move(debuggerProperty)); + } + // Get __proto__ + Local proto = Local(value)->GetPrototype(vm_); + std::unique_ptr protoObj = RemoteObject::FromTagged(vm_, proto); + CacheObjectIfNeeded(proto, protoObj.get()); + std::unique_ptr debuggerProperty = std::make_unique(); + debuggerProperty->SetName("__proto__") + .SetWritable(true) + .SetConfigurable(true) + .SetEnumerable(false) + .SetIsOwn(true) + .SetValue(std::move(protoObj)); + outPropertyDesc->emplace_back(std::move(debuggerProperty)); +} + +void RuntimeImpl::GetAdditionalProperties(const Local &value, + CVector> *outPropertyDesc) +{ + // The length of the TypedArray have to be limited(less than or equal to lengthTypedArrayLimit) until we construct + // the PropertyPreview class. Let lengthTypedArrayLimit be 10000 temporarily. + static const int32_t lengthTypedArrayLimit = 10000; + + // The width of the string-expression for JSTypedArray::MAX_TYPED_ARRAY_INDEX which is euqal to + // JSObject::MAX_ELEMENT_INDEX which is equal to std::numeric_limits::max(). (42,9496,7295) + static const int32_t widthStrExprMaxElementIndex = 10; + + if (value->IsTypedArray()) { + Local localTypedArrayRef(value); + int32_t lengthTypedArray = localTypedArrayRef->ArrayLength(vm_); + if (lengthTypedArray < 0 || lengthTypedArray > lengthTypedArrayLimit) { + LOG(ERROR, DEBUGGER) << "The length of the TypedArray is non-compliant or unsupported."; + return; + } + for (int32_t i = 0; i < lengthTypedArray; i++) { + Local localValRefElement = localTypedArrayRef->Get(vm_, i); + std::unique_ptr remoteObjElement = RemoteObject::FromTagged(vm_, localValRefElement); + remoteObjElement->SetObjectId(curObjectId_); + properties_[curObjectId_++] = Global(vm_, localValRefElement); + std::unique_ptr debuggerProperty = std::make_unique(); + + std::ostringstream osNameElement; + osNameElement << std::right << std::setw(widthStrExprMaxElementIndex) << i; + CString cStrNameElement = CString(osNameElement.str()); + debuggerProperty->SetName(cStrNameElement) + .SetWritable(true) + .SetConfigurable(true) + .SetEnumerable(false) + .SetIsOwn(true) + .SetValue(std::move(remoteObjElement)); + outPropertyDesc->emplace_back(std::move(debuggerProperty)); + } + } } } // namespace panda::ecmascript::tooling \ No newline at end of file diff --git a/ecmascript/tooling/agent/runtime_impl.h b/ecmascript/tooling/agent/runtime_impl.h index 1ca1f5c1574e3d65640df4999d1615ac0c15fee8..41ee6181ff2d7769d05910f20bb16e166fb8607e 100644 --- a/ecmascript/tooling/agent/runtime_impl.h +++ b/ecmascript/tooling/agent/runtime_impl.h @@ -17,34 +17,35 @@ #define ECMASCRIPT_TOOLING_AGENT_RUNTIME_IMPL_H #include "libpandabase/macros.h" -#include "ecmascript/tooling/agent/js_backend.h" #include "ecmascript/tooling/base/pt_params.h" #include "ecmascript/tooling/dispatcher.h" namespace panda::ecmascript::tooling { class RuntimeImpl final { public: - explicit RuntimeImpl(JSBackend *backend) : backend_(backend) {} + RuntimeImpl(const EcmaVM *vm, ProtocolChannel *channel) + : vm_(vm), frontend_(channel) {} ~RuntimeImpl() = default; DispatchResponse Enable(); DispatchResponse Disable(); DispatchResponse RunIfWaitingForDebugger(); + DispatchResponse CallFunctionOn( + std::unique_ptr params, + std::unique_ptr *outRemoteObject, + std::optional> *outExceptionDetails); + DispatchResponse GetHeapUsage(double *usedSize, double *totalSize); DispatchResponse GetProperties( std::unique_ptr params, CVector> *outPropertyDesc, std::optional>> *outInternalDescs, std::optional>> *outPrivateProps, std::optional> *outExceptionDetails); - DispatchResponse CallFunctionOn( - std::unique_ptr params, - std::unique_ptr *outRemoteObject, - std::optional> *outExceptionDetails); - DispatchResponse GetHeapUsage(double *usedSize, double *totalSize); class DispatcherImpl final : public DispatcherBase { public: - DispatcherImpl(FrontEnd *frontend, std::unique_ptr runtime); + DispatcherImpl(ProtocolChannel *channel, std::unique_ptr runtime) + : DispatcherBase(channel), runtime_(std::move(runtime)) {} ~DispatcherImpl() override = default; void Dispatch(const DispatchRequest &request) override; @@ -57,7 +58,6 @@ public: private: using AgentHandler = void (RuntimeImpl::DispatcherImpl::*)(const DispatchRequest &request); - CUnorderedMap dispatcherTable_ {}; std::unique_ptr runtime_ {}; NO_COPY_SEMANTIC(DispatcherImpl); @@ -67,8 +67,39 @@ public: private: NO_COPY_SEMANTIC(RuntimeImpl); NO_MOVE_SEMANTIC(RuntimeImpl); + enum NumberSize : uint8_t { BYTES_OF_16BITS = 2, BYTES_OF_32BITS = 4, BYTES_OF_64BITS = 8 }; + + void CacheObjectIfNeeded(Local valRef, RemoteObject *remoteObj); + + template + void AddTypedArrayRef(Local arrayBufferRef, int32_t length, + const char* name, CVector> *outPropertyDesc); + void AddTypedArrayRefs(Local arrayBufferRef, + CVector> *outPropertyDesc); + void GetProtoOrProtoType(const Local &value, bool isOwn, bool isAccessorOnly, + CVector> *outPropertyDesc); + void GetAdditionalProperties(const Local &value, + CVector> *outPropertyDesc); + + class Frontend { + public: + explicit Frontend(ProtocolChannel *channel) : channel_(channel) {} + + void RunIfWaitingForDebugger(); + + private: + bool AllowNotify() const; + + ProtocolChannel *channel_ {nullptr}; + }; + + const EcmaVM *vm_ {nullptr}; + Frontend frontend_; + + RemoteObjectId curObjectId_ {0}; + CUnorderedMap> properties_ {}; - JSBackend *backend_{nullptr}; + friend class DebuggerImpl; }; } // namespace panda::ecmascript::tooling #endif \ No newline at end of file diff --git a/ecmascript/tooling/interface/debugger_api.cpp b/ecmascript/tooling/backend/debugger_api.cpp similarity index 69% rename from ecmascript/tooling/interface/debugger_api.cpp rename to ecmascript/tooling/backend/debugger_api.cpp index 63df165028ef97bc0ffaf71f2143d1718f1b9652..2fe331061f6e3636588bde8a05f0de68396c0313 100644 --- a/ecmascript/tooling/interface/debugger_api.cpp +++ b/ecmascript/tooling/backend/debugger_api.cpp @@ -13,10 +13,12 @@ * limitations under the License. */ -#include "ecmascript/tooling/interface/debugger_api.h" +#include "ecmascript/tooling/backend/debugger_api.h" #include "ecmascript/base/number_helper.h" #include "ecmascript/interpreter/frame_handler.h" +#include "ecmascript/interpreter/slow_runtime_stub.h" +#include "ecmascript/interpreter/fast_runtime_stub-inl.h" #include "ecmascript/jspandafile/js_pandafile_executor.h" #include "ecmascript/jspandafile/program_object.h" #include "ecmascript/js_handle.h" @@ -24,7 +26,7 @@ #include "ecmascript/jspandafile/js_pandafile_manager.h" #include "ecmascript/mem/c_string.h" #include "ecmascript/napi/jsnapi_helper.h" -#include "ecmascript/tooling/interface/js_debugger.h" +#include "ecmascript/tooling/backend/js_debugger.h" namespace panda::ecmascript::tooling { using panda::ecmascript::base::ALLOW_BINARY; @@ -32,19 +34,6 @@ using panda::ecmascript::base::ALLOW_HEX; using panda::ecmascript::base::ALLOW_OCTAL; using panda::ecmascript::base::NumberHelper; -// JSPandaFileExecutor -Local DebuggerApi::Execute(const EcmaVM *ecmaVm, const void *buffer, size_t size, - std::string_view entryPoint) -{ - JSThread *thread = ecmaVm->GetJSThread(); - auto result = JSPandaFileExecutor::ExecuteFromBuffer(thread, buffer, size, entryPoint); - if (!result) { - return JSValueRef::Undefined(ecmaVm); - } - - return JSNApiHelper::ToLocal(JSHandle(thread, result.Value())); -} - // FrameHandler uint32_t DebuggerApi::GetStackDepth(const EcmaVM *ecmaVm) { @@ -93,16 +82,9 @@ JSMethod *DebuggerApi::GetMethod(const EcmaVM *ecmaVm) return FrameHandler(ecmaVm->GetJSThread()).GetMethod(); } -Local DebuggerApi::GetVRegValue(const EcmaVM *ecmaVm, size_t index) -{ - auto value = FrameHandler(ecmaVm->GetJSThread()).GetVRegValue(index); - JSHandle handledValue(ecmaVm->GetJSThread(), value); - return JSNApiHelper::ToLocal(handledValue); -} - -void DebuggerApi::SetVRegValue(const EcmaVM *ecmaVm, size_t index, Local value) +void DebuggerApi::SetVRegValue(FrameHandler *frameHandler, size_t index, Local value) { - return FrameHandler(ecmaVm->GetJSThread()).SetVRegValue(index, JSNApiHelper::ToJSTaggedValue(*value)); + return frameHandler->SetVRegValue(index, JSNApiHelper::ToJSTaggedValue(*value)); } uint32_t DebuggerApi::GetBytecodeOffset(const FrameHandler *frameHandler) @@ -125,6 +107,26 @@ JSTaggedType *DebuggerApi::GetSp(const FrameHandler *frameHandler) return frameHandler->GetSp(); } +int32_t DebuggerApi::GetVregIndex(const FrameHandler *frameHandler, std::string_view name) +{ + JSMethod *method = frameHandler->GetMethod(); + if (method->IsNativeWithCallField()) { + LOG(ERROR, DEBUGGER) << "GetVregIndex: native frame not support"; + return -1; + } + JSPtExtractor *extractor = JSPandaFileManager::GetInstance()->GetJSPtExtractor(method->GetJSPandaFile()); + if (extractor == nullptr) { + LOG(ERROR, DEBUGGER) << "GetVregIndex: extractor is null"; + return -1; + } + auto table = extractor->GetLocalVariableTable(method->GetMethodId()); + auto iter = table.find(name.data()); + if (iter == table.end()) { + return -1; + } + return iter->second; +} + Local DebuggerApi::GetVRegValue(const EcmaVM *ecmaVm, const FrameHandler *frameHandler, size_t index) { @@ -210,34 +212,38 @@ CString DebuggerApi::ParseFunctionName(const JSMethod *method) } // ScopeInfo -Local DebuggerApi::GetProperties(const EcmaVM *ecmaVm, int32_t level, uint32_t slot) +Local DebuggerApi::GetProperties(const EcmaVM *vm, const FrameHandler *frameHandler, + int32_t level, uint32_t slot) { - JSTaggedValue env = GetCurrentEvaluateEnv(ecmaVm); + JSTaggedValue env = frameHandler->GetEnv(); for (int i = 0; i < level; i++) { JSTaggedValue taggedParentEnv = LexicalEnv::Cast(env.GetTaggedObject())->GetParentEnv(); ASSERT(!taggedParentEnv.IsUndefined()); env = taggedParentEnv; } JSTaggedValue value = LexicalEnv::Cast(env.GetTaggedObject())->GetProperties(slot); - JSHandle handledValue(ecmaVm->GetJSThread(), value); + JSHandle handledValue(vm->GetJSThread(), value); return JSNApiHelper::ToLocal(handledValue); } -void DebuggerApi::SetProperties(const EcmaVM *ecmaVm, int32_t level, uint32_t slot, Local value) +void DebuggerApi::SetProperties(const EcmaVM *vm, const FrameHandler *frameHandler, + int32_t level, uint32_t slot, Local value) { - JSTaggedValue env = GetCurrentEvaluateEnv(ecmaVm); + JSTaggedValue env = frameHandler->GetEnv(); for (int i = 0; i < level; i++) { JSTaggedValue taggedParentEnv = LexicalEnv::Cast(env.GetTaggedObject())->GetParentEnv(); ASSERT(!taggedParentEnv.IsUndefined()); env = taggedParentEnv; } JSTaggedValue target = JSNApiHelper::ToJSHandle(value).GetTaggedValue(); - LexicalEnv::Cast(env.GetTaggedObject())->SetProperties(ecmaVm->GetJSThread(), slot, target); + LexicalEnv::Cast(env.GetTaggedObject())->SetProperties(vm->GetJSThread(), slot, target); } -bool DebuggerApi::EvaluateLexicalValue(const EcmaVM *ecmaVm, const CString &name, int32_t &level, uint32_t &slot) +std::pair DebuggerApi::GetLevelSlot(const FrameHandler *frameHandler, std::string_view name) { - JSTaggedValue curEnv = GetCurrentEvaluateEnv(ecmaVm); + int32_t level = 0; + uint32_t slot = 0; + JSTaggedValue curEnv = frameHandler->GetEnv(); for (; curEnv.IsTaggedArray(); curEnv = LexicalEnv::Cast(curEnv.GetTaggedObject())->GetParentEnv(), level++) { LexicalEnv *lexicalEnv = LexicalEnv::Cast(curEnv.GetTaggedObject()); if (lexicalEnv->GetScopeInfo().IsHole()) { @@ -245,43 +251,62 @@ bool DebuggerApi::EvaluateLexicalValue(const EcmaVM *ecmaVm, const CString &name } auto result = JSNativePointer::Cast(lexicalEnv->GetScopeInfo().GetTaggedObject())->GetExternalPointer(); ScopeDebugInfo *scopeDebugInfo = reinterpret_cast(result); - auto iter = scopeDebugInfo->scopeInfo.find(name); + auto iter = scopeDebugInfo->scopeInfo.find(name.data()); if (iter == scopeDebugInfo->scopeInfo.end()) { continue; } slot = iter->second; - return true; + return std::make_pair(level, slot); } - return false; + return std::make_pair(-1, 0); } -Local DebuggerApi::GetLexicalValueInfo(const EcmaVM *ecmaVm, const CString &name) +Local DebuggerApi::GetGlobalValue(const EcmaVM *vm, Local name) { - JSThread *thread = ecmaVm->GetJSThread(); - JSTaggedValue curEnv = thread->GetCurrentLexenv(); - for (; curEnv.IsTaggedArray(); curEnv = LexicalEnv::Cast(curEnv.GetTaggedObject())->GetParentEnv()) { - LexicalEnv *lexicalEnv = LexicalEnv::Cast(curEnv.GetTaggedObject()); - if (lexicalEnv->GetScopeInfo().IsHole()) { - continue; - } - void *pointer = JSNativePointer::Cast(lexicalEnv->GetScopeInfo().GetTaggedObject())->GetExternalPointer(); - ScopeDebugInfo *scopeDebugInfo = static_cast(pointer); - auto iter = scopeDebugInfo->scopeInfo.find(name); - if (iter == scopeDebugInfo->scopeInfo.end()) { - continue; - } - uint32_t slot = iter->second; - JSTaggedValue value = lexicalEnv->GetProperties(slot); - JSHandle handledValue(thread, value); - return JSNApiHelper::ToLocal(handledValue); + JSTaggedValue result; + JSTaggedValue globalObj = vm->GetGlobalEnv()->GetGlobalObject(); + JSThread *thread = vm->GetJSThread(); + + JSTaggedValue key = JSNApiHelper::ToJSTaggedValue(*name); + JSTaggedValue globalRec = SlowRuntimeStub::LdGlobalRecord(thread, key); + if (!globalRec.IsUndefined()) { + ASSERT(globalRec.IsPropertyBox()); + result = PropertyBox::Cast(globalRec.GetTaggedObject())->GetValue(); + return JSNApiHelper::ToLocal(JSHandle(thread, result)); } - JSHandle handledValue(thread, JSTaggedValue::Hole()); - return JSNApiHelper::ToLocal(handledValue); + + JSTaggedValue globalVar = FastRuntimeStub::GetGlobalOwnProperty(thread, globalObj, key); + if (!globalVar.IsHole()) { + return JSNApiHelper::ToLocal(JSHandle(thread, globalVar)); + } else { + result = SlowRuntimeStub::TryLdGlobalByName(thread, globalObj, key); + return JSNApiHelper::ToLocal(JSHandle(thread, result)); + } + + return JSValueRef::Exception(vm); } -void DebuggerApi::InitJSDebugger(JSDebugger *debugger) +bool DebuggerApi::SetGlobalValue(const EcmaVM *vm, Local name, Local value) { - debugger->Init(); + JSTaggedValue result; + JSTaggedValue globalObj = vm->GetGlobalEnv()->GetGlobalObject(); + JSThread *thread = vm->GetJSThread(); + + JSTaggedValue key = JSNApiHelper::ToJSTaggedValue(*name); + JSTaggedValue newVal = JSNApiHelper::ToJSTaggedValue(*value); + JSTaggedValue globalRec = SlowRuntimeStub::LdGlobalRecord(thread, key); + if (!globalRec.IsUndefined()) { + result = SlowRuntimeStub::TryUpdateGlobalRecord(thread, key, newVal); + return !result.IsException(); + } + + JSTaggedValue globalVar = FastRuntimeStub::GetGlobalOwnProperty(thread, globalObj, key); + if (!globalVar.IsHole()) { + result = SlowRuntimeStub::StGlobalVar(thread, key, newVal); + return !result.IsException(); + } + + return false; } void DebuggerApi::HandleUncaughtException(const EcmaVM *ecmaVm, CString &message) @@ -304,15 +329,6 @@ void DebuggerApi::HandleUncaughtException(const EcmaVM *ecmaVm, CString &message thread->ClearException(); } -JSTaggedValue DebuggerApi::GetCurrentEvaluateEnv(const EcmaVM *ecmaVm) -{ - auto &frameHandler = ecmaVm->GetJsDebuggerManager()->GetEvalFrameHandler(); - if (frameHandler != nullptr) { - return frameHandler->GetEnv(); - } - return ecmaVm->GetJSThread()->GetCurrentLexenv(); -} - Local DebuggerApi::GenerateFuncFromBuffer(const EcmaVM *ecmaVm, const void *buffer, size_t size, std::string_view entryPoint) { diff --git a/ecmascript/tooling/interface/debugger_api.h b/ecmascript/tooling/backend/debugger_api.h similarity index 75% rename from ecmascript/tooling/interface/debugger_api.h rename to ecmascript/tooling/backend/debugger_api.h index 8ac03646b85668a8fb36436e5c3b7001932e42c7..774e01987cbd67674de7c85978d88c941972b86c 100644 --- a/ecmascript/tooling/interface/debugger_api.h +++ b/ecmascript/tooling/backend/debugger_api.h @@ -13,8 +13,8 @@ * limitations under the License. */ -#ifndef ECMASCRIPT_TOOLING_INTERFACE_DEBUGGER_API_H -#define ECMASCRIPT_TOOLING_INTERFACE_DEBUGGER_API_H +#ifndef ECMASCRIPT_TOOLING_BACKEND_DEBUGGER_API_H +#define ECMASCRIPT_TOOLING_BACKEND_DEBUGGER_API_H #include @@ -23,7 +23,7 @@ #include "ecmascript/mem/c_string.h" #include "ecmascript/napi/include/jsnapi.h" #include "ecmascript/lexical_env.h" -#include "ecmascript/tooling/interface/js_debug_interface.h" +#include "ecmascript/tooling/backend/js_debugger_interface.h" namespace panda { namespace ecmascript { @@ -46,24 +46,28 @@ enum StackState { class PUBLIC_API DebuggerApi { public: - // JSPandaFileExecutor - static Local Execute(const EcmaVM *ecmaVm, const void *buffer, size_t size, - std::string_view entryPoint); - // FrameHandler static uint32_t GetStackDepth(const EcmaVM *ecmaVm); static std::shared_ptr NewFrameHandler(const EcmaVM *ecmaVm); static bool StackWalker(const EcmaVM *ecmaVm, std::function func); static uint32_t GetBytecodeOffset(const EcmaVM *ecmaVm); static JSMethod *GetMethod(const EcmaVM *ecmaVm); - static Local GetVRegValue(const EcmaVM *ecmaVm, size_t index); - static void SetVRegValue(const EcmaVM *ecmaVm, size_t index, Local value); static uint32_t GetBytecodeOffset(const FrameHandler *frameHandler); static JSMethod *GetMethod(const FrameHandler *frameHandler); static JSTaggedValue GetEnv(const FrameHandler *frameHandler); static JSTaggedType *GetSp(const FrameHandler *frameHandler); + static int32_t GetVregIndex(const FrameHandler *frameHandler, std::string_view name); static Local GetVRegValue(const EcmaVM *ecmaVm, - const FrameHandler *frameHandler, size_t index); + const FrameHandler *frameHandler, size_t index); + static void SetVRegValue(FrameHandler *frameHandler, size_t index, Local value); + + static Local GetProperties(const EcmaVM *ecmaVm, const FrameHandler *frameHandler, + int32_t level, uint32_t slot); + static void SetProperties(const EcmaVM *vm, const FrameHandler *frameHandler, + int32_t level, uint32_t slot, Local value); + static std::pair GetLevelSlot(const FrameHandler *frameHandler, std::string_view name); + static Local GetGlobalValue(const EcmaVM *vm, Local name); + static bool SetGlobalValue(const EcmaVM *vm, Local name, Local value); // String static int32_t CStringToInt(const CString &str); @@ -85,7 +89,6 @@ public: static bool SetBreakpoint(JSDebugger *debugger, const JSPtLocation &location, const Local &condFuncRef); static bool RemoveBreakpoint(JSDebugger *debugger, const JSPtLocation &location); - static void InitJSDebugger(JSDebugger *debugger); static void HandleUncaughtException(const EcmaVM *ecmaVm, CString &message); static Local EvaluateViaFuncCall(EcmaVM *ecmaVm, const Local &funcRef, std::shared_ptr &frameHandler); @@ -94,16 +97,7 @@ public: // JSMethod static CString ParseFunctionName(const JSMethod *method); - - // ScopeInfo - static Local GetProperties(const EcmaVM *ecmaVm, int32_t level, uint32_t slot); - static void SetProperties(const EcmaVM *ecmaVm, int32_t level, uint32_t slot, Local value); - static bool EvaluateLexicalValue(const EcmaVM *ecmaVm, const CString &name, int32_t &level, uint32_t &slot); - static Local GetLexicalValueInfo(const EcmaVM *ecmaVm, const CString &name); - -private: - static JSTaggedValue GetCurrentEvaluateEnv(const EcmaVM *ecmaVm); }; } // namespace panda::ecmascript::tooling -#endif // ECMASCRIPT_TOOLING_DEBUGGER_API_H +#endif // ECMASCRIPT_TOOLING_BACKEND_DEBUGGER_API_H diff --git a/ecmascript/tooling/backend/debugger_executor.cpp b/ecmascript/tooling/backend/debugger_executor.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e6c4459c16e61267306675c7fb0c0f97235ecd55 --- /dev/null +++ b/ecmascript/tooling/backend/debugger_executor.cpp @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2021-2022 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/tooling/backend/debugger_executor.h" + +#include "ecmascript/tooling/backend/debugger_api.h" +#include "ecmascript/tooling/interface/js_debugger_manager.h" +#include "libpandabase/utils/logger.h" + +namespace panda::ecmascript::tooling { +void DebuggerExecutor::Initialize(const EcmaVM *vm) +{ + Local globalObj = JSNApi::GetGlobalObject(vm); + globalObj->Set(vm, StringRef::NewFromUtf8(vm, "debuggerSetValue"), FunctionRef::New( + const_cast(vm), DebuggerExecutor::DebuggerSetValue)); + globalObj->Set(vm, StringRef::NewFromUtf8(vm, "debuggerGetValue"), FunctionRef::New( + const_cast(vm), DebuggerExecutor::DebuggerGetValue)); +} + +Local DebuggerExecutor::DebuggerGetValue(JsiRuntimeCallInfo *runtimeCallInfo) +{ + EcmaVM *vm = runtimeCallInfo->GetVM(); + size_t argc = runtimeCallInfo->GetArgsNumber(); + if (argc != NUM_ARGS) { + return JSValueRef::Undefined(vm); + } + Local name = runtimeCallInfo->GetCallArgRef(0); + if (!name->IsString()) { + return JSValueRef::Undefined(vm); + } + Local isThrow = runtimeCallInfo->GetCallArgRef(1); + + auto &frameHandler = vm->GetJsDebuggerManager()->GetEvalFrameHandler(); + ASSERT(frameHandler); + + Local value = GetValue(vm, frameHandler.get(), Local(name)); + if (!value.IsEmpty() && !value->IsException()) { + return value; + } + + if (!isThrow->ToBoolean(vm)->Value()) { + DebuggerApi::ClearException(vm); + return JSValueRef::Undefined(vm); + } + + std::string varName = Local(name)->ToString(); + ThrowException(vm, varName + " is not defined"); + return JSValueRef::Exception(vm); +} + +Local DebuggerExecutor::DebuggerSetValue(JsiRuntimeCallInfo *runtimeCallInfo) +{ + EcmaVM *vm = runtimeCallInfo->GetVM(); + size_t argc = runtimeCallInfo->GetArgsNumber(); + if (argc != NUM_ARGS) { + return JSValueRef::Undefined(vm); + } + Local name = runtimeCallInfo->GetCallArgRef(0); + if (!name->IsString()) { + return JSValueRef::Undefined(vm); + } + Local value = runtimeCallInfo->GetCallArgRef(1); + + auto &frameHandler = vm->GetJsDebuggerManager()->GetEvalFrameHandler(); + ASSERT(frameHandler); + + if (SetValue(vm, frameHandler.get(), Local(name), value)) { + return value; + } + + std::string varName = StringRef::Cast(*name)->ToString(); + ThrowException(vm, varName + " is not defined"); + return JSValueRef::Exception(vm); +} + +Local DebuggerExecutor::GetValue(const EcmaVM *vm, const FrameHandler *frameHandler, Local name) +{ + Local value; + value = GetLocalValue(vm, frameHandler, name); + if (!value.IsEmpty()) { + return value; + } + value = GetLexicalValue(vm, frameHandler, name); + if (!value.IsEmpty()) { + return value; + } + value = GetGlobalValue(vm, name); + if (!value.IsEmpty() && !value->IsException()) { + return value; + } + + return Local(); +} + +bool DebuggerExecutor::SetValue(const EcmaVM *vm, FrameHandler *frameHandler, + Local name, Local value) +{ + if (SetLocalValue(vm, frameHandler, name, value)) { + return true; + } + if (SetLexicalValue(vm, frameHandler, name, value)) { + return true; + } + if (SetGlobalValue(vm, name, value)) { + return true; + } + + return false; +} + +void DebuggerExecutor::ThrowException(const EcmaVM *vm, const std::string &error) +{ + Local msg = StringRef::NewFromUtf8(vm, error.c_str()); + Local exception = Exception::ReferenceError(vm, msg); + JSNApi::ThrowException(vm, exception); +} + +Local DebuggerExecutor::GetLocalValue(const EcmaVM *vm, const FrameHandler *frameHandler, + Local name) +{ + Local result; + + int32_t index = DebuggerApi::GetVregIndex(frameHandler, name->ToString()); + if (index == -1) { + return result; + } + + result = DebuggerApi::GetVRegValue(vm, frameHandler, index); + return result; +} + +bool DebuggerExecutor::SetLocalValue(const EcmaVM *vm, FrameHandler *frameHandler, + Local name, Local value) +{ + std::string varName = name->ToString(); + int32_t index = DebuggerApi::GetVregIndex(frameHandler, varName); + if (index == -1) { + return false; + } + + DebuggerApi::SetVRegValue(frameHandler, index, value); + vm->GetJsDebuggerManager()->NotifyLocalScopeUpdated(varName, value); + return true; +} + +Local DebuggerExecutor::GetLexicalValue(const EcmaVM *vm, const FrameHandler *frameHandler, + Local name) +{ + Local result; + + auto [level, slot] = DebuggerApi::GetLevelSlot(frameHandler, name->ToString()); + if (level == -1) { + return result; + } + + result = DebuggerApi::GetProperties(vm, frameHandler, level, slot); + return result; +} + +bool DebuggerExecutor::SetLexicalValue(const EcmaVM *vm, const FrameHandler *frameHandler, + Local name, Local value) +{ + std::string varName = name->ToString(); + auto [level, slot] = DebuggerApi::GetLevelSlot(frameHandler, varName); + if (level == -1) { + return false; + } + + DebuggerApi::SetProperties(vm, frameHandler, level, slot, value); + vm->GetJsDebuggerManager()->NotifyLocalScopeUpdated(varName, value); + return true; +} + +Local DebuggerExecutor::GetGlobalValue(const EcmaVM *vm, Local name) +{ + return DebuggerApi::GetGlobalValue(vm, name); +} + +bool DebuggerExecutor::SetGlobalValue(const EcmaVM *vm, Local name, Local value) +{ + return DebuggerApi::SetGlobalValue(vm, name, value); +} +} // namespace panda::ecmascript::tooling diff --git a/ecmascript/tooling/backend/debugger_executor.h b/ecmascript/tooling/backend/debugger_executor.h new file mode 100644 index 0000000000000000000000000000000000000000..87eea5c11b98cfc18be62dcc53ed077ecb058172 --- /dev/null +++ b/ecmascript/tooling/backend/debugger_executor.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2021-2022 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. + */ + +#ifndef ECMASCRIPT_TOOLING_BACKEND_DEBUGGER_EXECUTOR_H +#define ECMASCRIPT_TOOLING_BACKEND_DEBUGGER_EXECUTOR_H + +#include "ecmascript/napi/include/jsnapi.h" + +namespace panda::ecmascript { +class FrameHandler; +namespace tooling { +class DebuggerExecutor { +public: + DebuggerExecutor() = default; + ~DebuggerExecutor() = default; + + static void Initialize(const EcmaVM *vm); + + static Local GetValue(const EcmaVM *vm, const FrameHandler *frameHandler, Local name); + static bool SetValue(const EcmaVM *vm, FrameHandler *frameHandler, + Local name, Local value); + +private: + NO_COPY_SEMANTIC(DebuggerExecutor); + NO_MOVE_SEMANTIC(DebuggerExecutor); + + static Local DebuggerGetValue(JsiRuntimeCallInfo *runtimeCallInfo); + static Local DebuggerSetValue(JsiRuntimeCallInfo *runtimeCallInfo); + + static void ThrowException(const EcmaVM *vm, const std::string &error); + + static Local GetLocalValue(const EcmaVM *vm, const FrameHandler *frameHandler, Local name); + static Local GetLexicalValue(const EcmaVM *vm, const FrameHandler *frameHandler, + Local name); + static Local GetGlobalValue(const EcmaVM *vm, Local name); + + static bool SetLocalValue(const EcmaVM *vm, FrameHandler *frameHandler, + Local name, Local value); + static bool SetLexicalValue(const EcmaVM *vm, const FrameHandler *frameHandler, + Local name, Local value); + static bool SetGlobalValue(const EcmaVM *vm, Local name, Local value); + + constexpr static int32_t NUM_ARGS = 2; +}; +} // namespace tooling +} // namespace panda::ecmascript + +#endif // ECMASCRIPT_TOOLING_BACKEND_DEBUGGER_EXECUTOR_H diff --git a/ecmascript/tooling/interface/js_debugger.cpp b/ecmascript/tooling/backend/js_debugger.cpp similarity index 42% rename from ecmascript/tooling/interface/js_debugger.cpp rename to ecmascript/tooling/backend/js_debugger.cpp index 4d89c19cc46d0c67d5f60d95e084aa0eb1fb9f29..74a3da703c4671e5016b6a74e9b6601069cc10d3 100644 --- a/ecmascript/tooling/interface/js_debugger.cpp +++ b/ecmascript/tooling/backend/js_debugger.cpp @@ -13,17 +13,14 @@ * limitations under the License. */ -#include "ecmascript/tooling/interface/js_debugger.h" +#include "ecmascript/tooling/backend/js_debugger.h" #include "ecmascript/base/builtins_base.h" #include "ecmascript/ecma_macros.h" #include "ecmascript/global_env.h" #include "ecmascript/interpreter/fast_runtime_stub-inl.h" #include "ecmascript/interpreter/frame_handler.h" -#include "ecmascript/interpreter/slow_runtime_stub.h" #include "ecmascript/jspandafile/js_pandafile_manager.h" -#include "ecmascript/js_thread.h" -#include "ecmascript/napi/jsnapi_helper.h" namespace panda::ecmascript::tooling { using panda::ecmascript::base::BuiltinsBase; @@ -180,202 +177,4 @@ JSMethod *JSDebugger::FindMethod(const JSPtLocation &location) const }); return method; } - -JSTaggedValue JSDebugger::DebuggerSetValue(EcmaRuntimeCallInfo *argv) -{ - auto localEvalFunc = [](const EcmaVM *ecmaVm, FrameHandler *frameHandler, int32_t regIndex, - const CString &varName, Local value) { - JSTaggedValue newVal = JSNApiHelper::ToJSTaggedValue(*value); - frameHandler->SetVRegValue(regIndex, newVal); - ecmaVm->GetJsDebuggerManager()->NotifyLocalScopeUpdated(varName, value); - return newVal; - }; - - auto lexEvalFunc = [](const EcmaVM *ecmaVm, int32_t level, uint32_t slot, - const CString &varName, Local value) { - DebuggerApi::SetProperties(ecmaVm, level, slot, value); - if (level == 0) { - // local closure variable - ecmaVm->GetJsDebuggerManager()->NotifyLocalScopeUpdated(varName, value); - } - return JSNApiHelper::ToJSTaggedValue(*value); - }; - - auto globalEvalFunc = [](const EcmaVM *ecmaVm, JSTaggedValue key, - const CString &varName, JSTaggedValue value) { - JSTaggedValue result = SetGlobalValue(ecmaVm, key, value); - if (!result.IsException()) { - return value; - } - - JSThread *thread = ecmaVm->GetJSThread(); - CString msg = varName + " is not defined"; - THROW_REFERENCE_ERROR_AND_RETURN(thread, msg.data(), JSTaggedValue::Exception()); - }; - - return Evaluate(argv, localEvalFunc, lexEvalFunc, globalEvalFunc); -} - -JSTaggedValue JSDebugger::DebuggerGetValue(EcmaRuntimeCallInfo *argv) -{ - auto localEvalFunc = [](const EcmaVM *, FrameHandler *frameHandler, int32_t regIndex, - const CString &, Local) { - return frameHandler->GetVRegValue(regIndex); - }; - - auto lexEvalFunc = [](const EcmaVM *ecmaVm, int32_t level, uint32_t slot, - const CString &, Local) { - Local valRef = DebuggerApi::GetProperties(ecmaVm, level, slot); - return JSNApiHelper::ToJSTaggedValue(*valRef); - }; - - auto globalEvalFunc = [](const EcmaVM *ecmaVm, JSTaggedValue key, - const CString &varName, JSTaggedValue isThrow) { - JSTaggedValue globalVal = GetGlobalValue(ecmaVm, key); - if (!globalVal.IsException()) { - return globalVal; - } - - JSThread *thread = ecmaVm->GetJSThread(); - if (isThrow.ToBoolean()) { - CString msg = varName + " is not defined"; - THROW_REFERENCE_ERROR_AND_RETURN(thread, msg.data(), JSTaggedValue::Exception()); - } - // in case of `typeof`, return undefined instead of exception - thread->ClearException(); - return JSTaggedValue::Undefined(); - }; - - return Evaluate(argv, localEvalFunc, lexEvalFunc, globalEvalFunc); -} - -JSTaggedValue JSDebugger::Evaluate(EcmaRuntimeCallInfo *argv, LocalEvalFunc localEvalFunc, - LexEvalFunc lexEvalFunc, GlobalEvalFunc globalEvalFunc) -{ - LOG(INFO, DEBUGGER) << "Evaluate: called"; - ASSERT(argv); - JSThread *thread = argv->GetThread(); - const EcmaVM *ecmaVm = thread->GetEcmaVM(); - [[maybe_unused]] EcmaHandleScope handleScope(thread); - - // The arg2 is the new value for setter and throw flag for getter - JSHandle arg1 = BuiltinsBase::GetCallArg(argv, 0); - JSHandle arg2 = BuiltinsBase::GetCallArg(argv, 1); - CString varName = ConvertToString(arg1.GetTaggedValue()); - Local valRef = JSNApiHelper::ToLocal(arg2); - LOG(INFO, DEBUGGER) << "Evaluate: varName = " << varName; - - auto &frameHandler = ecmaVm->GetJsDebuggerManager()->GetEvalFrameHandler(); - ASSERT(frameHandler); - JSMethod *method = frameHandler->GetMethod(); - int32_t regIndex = -1; - bool found = EvaluateLocalValue(method, thread, varName, regIndex); - if (found) { - LOG(INFO, DEBUGGER) << "Evaluate: found regIndex = " << regIndex; - return localEvalFunc(ecmaVm, frameHandler.get(), regIndex, varName, valRef); - } - - int32_t level = 0; - uint32_t slot = 0; - found = DebuggerApi::EvaluateLexicalValue(ecmaVm, varName, level, slot); - if (found) { - LOG(INFO, DEBUGGER) << "Evaluate: found level = " << level; - return lexEvalFunc(ecmaVm, level, slot, varName, valRef); - } - - return globalEvalFunc(ecmaVm, arg1.GetTaggedValue(), varName, arg2.GetTaggedValue()); -} - -JSTaggedValue JSDebugger::GetGlobalValue(const EcmaVM *ecmaVm, JSTaggedValue key) -{ - JSTaggedValue globalObj = ecmaVm->GetGlobalEnv()->GetGlobalObject(); - JSThread *thread = ecmaVm->GetJSThread(); - - JSTaggedValue globalRec = SlowRuntimeStub::LdGlobalRecord(thread, key); - if (!globalRec.IsUndefined()) { - ASSERT(globalRec.IsPropertyBox()); - return PropertyBox::Cast(globalRec.GetTaggedObject())->GetValue(); - } - - JSTaggedValue globalVar = FastRuntimeStub::GetGlobalOwnProperty(thread, globalObj, key); - if (!globalVar.IsHole()) { - return globalVar; - } else { - return SlowRuntimeStub::TryLdGlobalByName(thread, globalObj, key); - } - - return JSTaggedValue::Exception(); -} - -JSTaggedValue JSDebugger::SetGlobalValue(const EcmaVM *ecmaVm, JSTaggedValue key, JSTaggedValue newVal) -{ - JSTaggedValue globalObj = ecmaVm->GetGlobalEnv()->GetGlobalObject(); - JSThread *thread = ecmaVm->GetJSThread(); - - JSTaggedValue globalRec = SlowRuntimeStub::LdGlobalRecord(thread, key); - if (!globalRec.IsUndefined()) { - return SlowRuntimeStub::TryUpdateGlobalRecord(thread, key, newVal); - } - - JSTaggedValue globalVar = FastRuntimeStub::GetGlobalOwnProperty(thread, globalObj, key); - if (!globalVar.IsHole()) { - return SlowRuntimeStub::StGlobalVar(thread, key, newVal); - } - - return JSTaggedValue::Exception(); -} - -bool JSDebugger::EvaluateLocalValue(JSMethod *method, JSThread *thread, const CString &varName, int32_t ®Index) -{ - if (method->IsNativeWithCallField()) { - LOG(ERROR, DEBUGGER) << "EvaluateLocalValue: native frame not support"; - THROW_TYPE_ERROR_AND_RETURN(thread, "native frame not support", false); - } - JSPtExtractor *extractor = JSPandaFileManager::GetInstance()->GetJSPtExtractor(method->GetJSPandaFile()); - if (extractor == nullptr) { - LOG(ERROR, DEBUGGER) << "EvaluateLocalValue: extractor is null"; - THROW_TYPE_ERROR_AND_RETURN(thread, "extractor is null", false); - } - - auto varTbl = extractor->GetLocalVariableTable(method->GetMethodId()); - auto iter = varTbl.find(varName); - if (iter == varTbl.end()) { - return false; - } - regIndex = iter->second; - return true; -} - -void JSDebugger::Init() -{ - JSThread *thread = ecmaVm_->GetJSThread(); - [[maybe_unused]] ecmascript::EcmaHandleScope scope(thread); - - ObjectFactory *factory = ecmaVm_->GetFactory(); - constexpr int32_t NUM_ARGS = 2; - JSHandle getter(factory->NewFromUtf8("debuggerGetValue")); - SetGlobalFunction(getter, JSDebugger::DebuggerGetValue, NUM_ARGS); - JSHandle setter(factory->NewFromUtf8("debuggerSetValue")); - SetGlobalFunction(setter, JSDebugger::DebuggerSetValue, NUM_ARGS); -} - -void JSDebugger::SetGlobalFunction(const JSHandle &funcName, - EcmaEntrypoint nativeFunc, int32_t numArgs) const -{ - ObjectFactory *factory = ecmaVm_->GetFactory(); - JSThread *thread = ecmaVm_->GetJSThread(); - [[maybe_unused]] ecmascript::EcmaHandleScope scope(thread); - JSHandle env = ecmaVm_->GetGlobalEnv(); - JSHandle globalObject(thread, env->GetGlobalObject()); - - JSHandle jsFunc = factory->NewJSFunction(env, reinterpret_cast(nativeFunc)); - JSFunction::SetFunctionLength(thread, jsFunc, JSTaggedValue(numArgs)); - JSHandle baseFunc(jsFunc); - JSHandle undefined = thread->GlobalConstants()->GetHandledUndefined(); - JSFunction::SetFunctionName(thread, baseFunc, funcName, undefined); - - JSHandle funcHandle(jsFunc); - PropertyDescriptor desc(thread, JSHandle(funcHandle), true, false, true); - JSObject::DefineOwnProperty(thread, globalObject, funcName, desc); -} } // namespace panda::tooling::ecmascript diff --git a/ecmascript/tooling/interface/js_debugger.h b/ecmascript/tooling/backend/js_debugger.h similarity index 75% rename from ecmascript/tooling/interface/js_debugger.h rename to ecmascript/tooling/backend/js_debugger.h index 8628adeeffb598e21e6cfcc684d4c1c768326f59..dbda46cfd2bbc78ff02817656b01e97dbb45f71d 100644 --- a/ecmascript/tooling/interface/js_debugger.h +++ b/ecmascript/tooling/backend/js_debugger.h @@ -13,12 +13,12 @@ * limitations under the License. */ -#ifndef ECMASCRIPT_TOOLING_INTERFACE_JS_DEBUGGER_H -#define ECMASCRIPT_TOOLING_INTERFACE_JS_DEBUGGER_H +#ifndef ECMASCRIPT_TOOLING_BACKEND_JS_DEBUGGER_H +#define ECMASCRIPT_TOOLING_BACKEND_JS_DEBUGGER_H #include "ecmascript/ecma_vm.h" #include "ecmascript/js_method.h" -#include "ecmascript/tooling/interface/debugger_api.h" +#include "ecmascript/tooling/backend/debugger_api.h" #include "ecmascript/tooling/interface/notification_manager.h" #include "ecmascript/tooling/interface/js_debugger_manager.h" @@ -92,8 +92,6 @@ public: hooks_ = nullptr; } - void Init(); - bool SetBreakpoint(const JSPtLocation &location, const Local &condFuncRef) override; bool RemoveBreakpoint(const JSPtLocation &location) override; void BytecodePcChanged(JSThread *thread, JSMethod *method, uint32_t bcOffset) override; @@ -127,28 +125,12 @@ public: } private: - using LocalEvalFunc = - std::function)>; - using LexEvalFunc = - std::function)>; - using GlobalEvalFunc = - std::function; - JSMethod *FindMethod(const JSPtLocation &location) const; std::optional FindBreakpoint(const JSMethod *method, uint32_t bcOffset) const; bool RemoveBreakpoint(const JSMethod *method, uint32_t bcOffset); void HandleExceptionThrowEvent(const JSThread *thread, const JSMethod *method, uint32_t bcOffset); bool HandleStep(const JSMethod *method, uint32_t bcOffset); bool HandleBreakpoint(const JSMethod *method, uint32_t bcOffset); - void SetGlobalFunction(const JSHandle &funcName, EcmaEntrypoint nativeFunc, int32_t numArgs) const; - - static JSTaggedValue Evaluate(EcmaRuntimeCallInfo *argv, LocalEvalFunc localEvalFunc, - LexEvalFunc lexEvalFunc, GlobalEvalFunc globalEvalFunc); - static JSTaggedValue DebuggerSetValue(EcmaRuntimeCallInfo *argv); - static JSTaggedValue DebuggerGetValue(EcmaRuntimeCallInfo *argv); - static JSTaggedValue GetGlobalValue(const EcmaVM *ecmaVm, JSTaggedValue key); - static JSTaggedValue SetGlobalValue(const EcmaVM *ecmaVm, JSTaggedValue key, JSTaggedValue value); - static bool EvaluateLocalValue(JSMethod *method, JSThread *thread, const CString &varName, int32_t ®Index); const EcmaVM *ecmaVm_; PtHooks *hooks_ {nullptr}; @@ -158,4 +140,4 @@ private: }; } // namespace panda::ecmascript::tooling -#endif // ECMASCRIPT_TOOLING_JS_DEBUGGER_H +#endif // ECMASCRIPT_TOOLING_BACKEND_JS_DEBUGGER_H diff --git a/ecmascript/tooling/interface/js_debug_interface.h b/ecmascript/tooling/backend/js_debugger_interface.h similarity index 97% rename from ecmascript/tooling/interface/js_debug_interface.h rename to ecmascript/tooling/backend/js_debugger_interface.h index 1dcdbfdf0e407f6586daf57b8974c11e082470df..1afebdc4cdbd007a23f9394eae3eef0a55734954 100644 --- a/ecmascript/tooling/interface/js_debug_interface.h +++ b/ecmascript/tooling/backend/js_debugger_interface.h @@ -19,10 +19,10 @@ #include #include "ecmascript/napi/include/jsnapi.h" -#include "ecmascript/tooling/interface/js_pt_location.h" +#include "ecmascript/tooling/backend/js_pt_location.h" +#include "libpandafile/file.h" #include "libpandabase/macros.h" #include "libpandabase/utils/expected.h" -#include "libpandafile/file.h" namespace panda::ecmascript::tooling { struct JSPtStepRange { @@ -64,6 +64,11 @@ public: */ virtual void LoadModule(std::string_view pandaFileName) = 0; + /** + * \brief called before executing pending job + */ + virtual void PendingJobEntry() = 0; + /** * \brief called by the ecmavm when virtual machine start initialization */ @@ -74,13 +79,6 @@ public: */ virtual void VmDeath() = 0; - /** - * \brief called before executing pending job - */ - virtual void PendingJobEntry() = 0; - - virtual void Paused(PauseReason reason) = 0; - virtual void Exception(const JSPtLocation &location) = 0; virtual bool SingleStep(const JSPtLocation &location) = 0; diff --git a/ecmascript/tooling/js_pt_extractor.cpp b/ecmascript/tooling/backend/js_pt_extractor.cpp similarity index 97% rename from ecmascript/tooling/js_pt_extractor.cpp rename to ecmascript/tooling/backend/js_pt_extractor.cpp index d78243553f851f690ccfdccd097e4f6b70a8d09c..5a48add86dfdf64faa520b37961f36c3265fe5e6 100644 --- a/ecmascript/tooling/js_pt_extractor.cpp +++ b/ecmascript/tooling/backend/js_pt_extractor.cpp @@ -13,8 +13,9 @@ * limitations under the License. */ -#include "ecmascript/tooling/interface/debugger_api.h" -#include "ecmascript/tooling/js_pt_extractor.h" +#include "ecmascript/tooling/backend/js_pt_extractor.h" + +#include "ecmascript/tooling/backend/debugger_api.h" namespace panda::ecmascript::tooling { uint32_t JSPtExtractor::SingleStepper::GetStackDepth() const diff --git a/ecmascript/tooling/js_pt_extractor.h b/ecmascript/tooling/backend/js_pt_extractor.h similarity index 95% rename from ecmascript/tooling/js_pt_extractor.h rename to ecmascript/tooling/backend/js_pt_extractor.h index 6afe0445117ad3e881ce36fa3ff762a1f4471183..9530f81dd3076338f13c414ce07d0869427fde6f 100644 --- a/ecmascript/tooling/js_pt_extractor.h +++ b/ecmascript/tooling/backend/js_pt_extractor.h @@ -13,13 +13,13 @@ * limitations under the License. */ -#ifndef ECMASCRIPT_TOOLING_JS_PT_EXTRACTOR_H -#define ECMASCRIPT_TOOLING_JS_PT_EXTRACTOR_H +#ifndef ECMASCRIPT_TOOLING_BACKEND_JS_PT_EXTRACTOR_H +#define ECMASCRIPT_TOOLING_BACKEND_JS_PT_EXTRACTOR_H #include "ecmascript/js_method.h" #include "ecmascript/js_thread.h" #include "ecmascript/jspandafile/debug_info_extractor.h" -#include "ecmascript/tooling/interface/js_debug_interface.h" +#include "ecmascript/tooling/backend/js_debugger_interface.h" #include "libpandabase/macros.h" namespace panda::ecmascript::tooling { @@ -122,4 +122,4 @@ private: std::unique_ptr GetStepper(const EcmaVM *ecmaVm, SingleStepper::Type type); }; } // namespace panda::ecmascript::tooling -#endif // ECMASCRIPT_TOOLING_JS_PT_EXTRACTOR_H +#endif // ECMASCRIPT_TOOLING_BACKEND_JS_PT_EXTRACTOR_H diff --git a/ecmascript/tooling/agent/js_pt_hooks.cpp b/ecmascript/tooling/backend/js_pt_hooks.cpp similarity index 64% rename from ecmascript/tooling/agent/js_pt_hooks.cpp rename to ecmascript/tooling/backend/js_pt_hooks.cpp index 8e5a4a4805096d0c01535a081749d400653d34d9..f3af915594be023707c8b46ae9a9ab97acc20b76 100644 --- a/ecmascript/tooling/agent/js_pt_hooks.cpp +++ b/ecmascript/tooling/backend/js_pt_hooks.cpp @@ -13,8 +13,8 @@ * limitations under the License. */ -#include "ecmascript/tooling/agent/js_pt_hooks.h" -#include "ecmascript/tooling/agent/js_backend.h" +#include "ecmascript/tooling/agent/debugger_impl.h" +#include "ecmascript/tooling/backend/js_pt_hooks.h" namespace panda::ecmascript::tooling { void JSPtHooks::Breakpoint(const JSPtLocation &location) @@ -22,41 +22,33 @@ void JSPtHooks::Breakpoint(const JSPtLocation &location) LOG(DEBUG, DEBUGGER) << "JSPtHooks: Breakpoint => " << location.GetMethodId() << ": " << location.GetBytecodeOffset(); - [[maybe_unused]] LocalScope scope(backend_->ecmaVm_); - backend_->NotifyPaused(location, INSTRUMENTATION); -} - -void JSPtHooks::Paused(PauseReason reason) -{ - LOG(DEBUG, DEBUGGER) << "JSPtHooks: Paused"; - - [[maybe_unused]] LocalScope scope(backend_->ecmaVm_); - backend_->NotifyPaused({}, reason); + [[maybe_unused]] LocalScope scope(debugger_->vm_); + debugger_->NotifyPaused(location, INSTRUMENTATION); } void JSPtHooks::Exception([[maybe_unused]] const JSPtLocation &location) { LOG(DEBUG, DEBUGGER) << "JSPtHooks: Exception"; - [[maybe_unused]] LocalScope scope(backend_->ecmaVm_); + [[maybe_unused]] LocalScope scope(debugger_->vm_); - backend_->NotifyPaused({}, EXCEPTION); + debugger_->NotifyPaused({}, EXCEPTION); } bool JSPtHooks::SingleStep(const JSPtLocation &location) { LOG(DEBUG, DEBUGGER) << "JSPtHooks: SingleStep => " << location.GetBytecodeOffset(); - [[maybe_unused]] LocalScope scope(backend_->ecmaVm_); + [[maybe_unused]] LocalScope scope(debugger_->vm_); if (UNLIKELY(firstTime_)) { firstTime_ = false; - backend_->NotifyPaused({}, BREAK_ON_START); + debugger_->NotifyPaused({}, BREAK_ON_START); return false; } // pause or step complete - if (backend_->StepComplete(location)) { - backend_->NotifyPaused({}, OTHER); + if (debugger_->NotifySingleStep(location)) { + debugger_->NotifyPaused({}, OTHER); return true; } return false; @@ -66,10 +58,10 @@ void JSPtHooks::LoadModule(std::string_view pandaFileName) { LOG(INFO, DEBUGGER) << "JSPtHooks: LoadModule: " << pandaFileName; - [[maybe_unused]] LocalScope scope(backend_->ecmaVm_); + [[maybe_unused]] LocalScope scope(debugger_->vm_); static uint32_t scriptId = 0; - if (backend_->NotifyScriptParsed(scriptId++, pandaFileName.data())) { + if (debugger_->NotifyScriptParsed(scriptId++, pandaFileName.data())) { firstTime_ = true; } } @@ -78,8 +70,8 @@ void JSPtHooks::PendingJobEntry() { LOG(DEBUG, DEBUGGER) << "JSPtHooks: PendingJobEntry"; - [[maybe_unused]] LocalScope scope(backend_->ecmaVm_); + [[maybe_unused]] LocalScope scope(debugger_->vm_); - backend_->PendingJobEntry(); + debugger_->NotifyPendingJobEntry(); } } // namespace panda::ecmascript::tooling diff --git a/ecmascript/tooling/agent/js_pt_hooks.h b/ecmascript/tooling/backend/js_pt_hooks.h similarity index 77% rename from ecmascript/tooling/agent/js_pt_hooks.h rename to ecmascript/tooling/backend/js_pt_hooks.h index 524af45f15c74d62a36766782a6c9cad160b1ba6..29cfa9e4ce5cbbb05fd013e6bef40e8a3c748c68 100644 --- a/ecmascript/tooling/agent/js_pt_hooks.h +++ b/ecmascript/tooling/backend/js_pt_hooks.h @@ -13,38 +13,37 @@ * limitations under the License. */ -#ifndef ECMASCRIPT_TOOLING_AGENT_JS_PT_HOOKS_H -#define ECMASCRIPT_TOOLING_AGENT_JS_PT_HOOKS_H +#ifndef ECMASCRIPT_TOOLING_BACKEND_JS_PT_HOOKS_H +#define ECMASCRIPT_TOOLING_BACKEND_JS_PT_HOOKS_H #include "libpandabase/macros.h" -#include "ecmascript/tooling/js_pt_extractor.h" +#include "ecmascript/tooling/backend/js_pt_extractor.h" #include "ecmascript/tooling/base/pt_events.h" #include "ecmascript/tooling/base/pt_script.h" -#include "ecmascript/tooling/interface/js_debug_interface.h" +#include "ecmascript/tooling/backend/js_debugger_interface.h" namespace panda::ecmascript::tooling { -class JSBackend; +class DebuggerImpl; class JSPtHooks : public PtHooks { public: - explicit JSPtHooks(JSBackend *backend) : backend_(backend) {} + explicit JSPtHooks(DebuggerImpl *debugger) : debugger_(debugger) {} ~JSPtHooks() override = default; void Breakpoint(const JSPtLocation &location) override; void LoadModule(std::string_view pandaFileName) override; - void Paused(PauseReason reason) override; void Exception(const JSPtLocation &location) override; bool SingleStep(const JSPtLocation &location) override; + void PendingJobEntry() override; void VmStart() override {} void VmDeath() override {} - void PendingJobEntry() override; private: NO_COPY_SEMANTIC(JSPtHooks); NO_MOVE_SEMANTIC(JSPtHooks); - JSBackend *backend_{nullptr}; + DebuggerImpl *debugger_ {nullptr}; bool firstTime_ {true}; }; } // namespace panda::ecmascript::tooling -#endif \ No newline at end of file +#endif // ECMASCRIPT_TOOLING_BACKEND_JS_PT_HOOKS_H \ No newline at end of file diff --git a/ecmascript/tooling/interface/js_pt_location.h b/ecmascript/tooling/backend/js_pt_location.h similarity index 91% rename from ecmascript/tooling/interface/js_pt_location.h rename to ecmascript/tooling/backend/js_pt_location.h index 0c1fd9b8ee700fbe2ce2bd36d5862123a5ecc42a..b5e6207d902e7aea3932419eb4aa4f0b8c6d7d60 100644 --- a/ecmascript/tooling/interface/js_pt_location.h +++ b/ecmascript/tooling/backend/js_pt_location.h @@ -13,8 +13,8 @@ * limitations under the License. */ -#ifndef ECMASCRIPT_TOOLING_INTERFACE_JS_PT_LOCATION_H -#define ECMASCRIPT_TOOLING_INTERFACE_JS_PT_LOCATION_H +#ifndef ECMASCRIPT_TOOLING_BACKEND_JS_PT_LOCATION_H +#define ECMASCRIPT_TOOLING_BACKEND_JS_PT_LOCATION_H #include @@ -64,4 +64,4 @@ private: }; } // namespace panda::ecmascript::tooling -#endif // ECMASCRIPT_TOOLING_INTERFACE_JS_PT_LOCATION_H +#endif // ECMASCRIPT_TOOLING_BACKEND_JS_PT_LOCATION_H diff --git a/ecmascript/tooling/base/pt_events.cpp b/ecmascript/tooling/base/pt_events.cpp index d5d113749fdd5bb747166ac52e0b32f48f73f0f5..8e6e14470e5f12970396fc024a48f672d91fc250 100644 --- a/ecmascript/tooling/base/pt_events.cpp +++ b/ecmascript/tooling/base/pt_events.cpp @@ -753,7 +753,7 @@ std::unique_ptr AddHeapSnapshotChunk::Create(const EcmaVM } else { error += "should contain 'chunk';"; } - + if (!error.empty()) { LOG(ERROR, DEBUGGER) << "AddHeapSnapshotChunk::Create " << error; return nullptr; diff --git a/ecmascript/tooling/base/pt_script.cpp b/ecmascript/tooling/base/pt_script.cpp index 8e2db910e46868959940438d655d5c6788fde347..ad3bc09855902a9749f6d1b42ea18b47912954a9 100644 --- a/ecmascript/tooling/base/pt_script.cpp +++ b/ecmascript/tooling/base/pt_script.cpp @@ -14,7 +14,7 @@ */ #include "ecmascript/tooling/base/pt_script.h" -#include "ecmascript/tooling/interface/debugger_api.h" +#include "ecmascript/tooling/backend/debugger_api.h" namespace panda::ecmascript::tooling { PtScript::PtScript(ScriptId scriptId, const CString &fileName, const CString &url, const CString &source) diff --git a/ecmascript/tooling/base/pt_types.h b/ecmascript/tooling/base/pt_types.h index ce9e366819c91ad8ac944a92ac49c397dbe5d3be..a5bfbafb2c83f1bd013c26b3d066d5f61c8184ee 100644 --- a/ecmascript/tooling/base/pt_types.h +++ b/ecmascript/tooling/base/pt_types.h @@ -21,7 +21,7 @@ #include "ecmascript/mem/c_containers.h" #include "ecmascript/mem/c_string.h" -#include "ecmascript/tooling/interface/debugger_api.h" +#include "ecmascript/tooling/backend/debugger_api.h" #include "libpandabase/macros.h" namespace panda::ecmascript::tooling { @@ -40,7 +40,7 @@ private: NO_MOVE_SEMANTIC(PtBaseTypes); friend class ProtocolHandler; - friend class JSBackend; + friend class DebuggerImpl; }; // ========== Debugger types begin diff --git a/ecmascript/tooling/debugger_service.cpp b/ecmascript/tooling/debugger_service.cpp index 218b29f1e7249ea31982b7545bab867540ee1c7a..a8173fa9a6419f00ac9573c3b750f9ea37594af9 100644 --- a/ecmascript/tooling/debugger_service.cpp +++ b/ecmascript/tooling/debugger_service.cpp @@ -23,7 +23,8 @@ void InitializeDebugger(const std::function &onRespon { ProtocolHandler *handler = vm->GetJsDebuggerManager()->GetDebuggerHandler(); if (handler != nullptr) { - LOG(FATAL, DEBUGGER) << "JS debugger was initialized"; + LOG(ERROR, DEBUGGER) << "JS debugger was initialized"; + return; } vm->GetJsDebuggerManager()->SetDebuggerHandler(new ProtocolHandler(onResponse, vm)); } diff --git a/ecmascript/tooling/dispatcher.cpp b/ecmascript/tooling/dispatcher.cpp index 9fea8699885508170cda84179cc19058d836c877..36f091be5f2cc94669502ff645aad5a5f66eddde 100644 --- a/ecmascript/tooling/dispatcher.cpp +++ b/ecmascript/tooling/dispatcher.cpp @@ -21,7 +21,7 @@ #include "ecmascript/tooling/agent/runtime_impl.h" #include "ecmascript/tooling/agent/heapprofiler_impl.h" #include "ecmascript/tooling/agent/profiler_impl.h" -#include "ecmascript/tooling/front_end.h" +#include "ecmascript/tooling/protocol_channel.h" namespace panda::ecmascript::tooling { DispatchRequest::DispatchRequest(const EcmaVM *ecmaVm, const CString &message) : ecmaVm_(ecmaVm) @@ -128,22 +128,28 @@ DispatchResponse DispatchResponse::Fail(const CString &message) void DispatcherBase::SendResponse(const DispatchRequest &request, const DispatchResponse &response, std::unique_ptr result) { - if (frontend_ != nullptr) { - frontend_->SendResponse(request, response, std::move(result)); + if (channel_ != nullptr) { + channel_->SendResponse(request, response, std::move(result)); } } -Dispatcher::Dispatcher(FrontEnd *front) +Dispatcher::Dispatcher(const EcmaVM *vm, ProtocolChannel *channel) { - std::unique_ptr backend = std::make_unique(front); - dispatchers_["Runtime"] = - std::make_unique(front, std::make_unique(backend.get())); - dispatchers_["HeapProfiler"] = - std::make_unique(front, std::make_unique(front)); + // profiler + auto profiler = std::make_unique(vm, channel); + auto heapProfiler = std::make_unique(vm, channel); dispatchers_["Profiler"] = - std::make_unique(front, std::make_unique(backend.get())); + std::make_unique(channel, std::move(profiler)); + dispatchers_["HeapProfiler"] = + std::make_unique(channel, std::move(heapProfiler)); + + // debugger + auto runtime = std::make_unique(vm, channel); + auto debugger = std::make_unique(vm, channel, runtime.get()); + dispatchers_["Runtime"] = + std::make_unique(channel, std::move(runtime)); dispatchers_["Debugger"] = - std::make_unique(front, std::make_unique(std::move(backend))); + std::make_unique(channel, std::move(debugger)); } void Dispatcher::Dispatch(const DispatchRequest &request) @@ -152,7 +158,7 @@ void Dispatcher::Dispatch(const DispatchRequest &request) LOG(ERROR, DEBUGGER) << "Unknown request"; return; } - CString domain = request.GetDomain(); + const CString &domain = request.GetDomain(); auto dispatcher = dispatchers_.find(domain); if (dispatcher != dispatchers_.end()) { dispatcher->second->Dispatch(request); diff --git a/ecmascript/tooling/dispatcher.h b/ecmascript/tooling/dispatcher.h index 86e2f50989b4ec77ab6c6cc631f685345c5d9440..0f28dfd29e4a2d656e05eb9b8e538cf7812d9947 100644 --- a/ecmascript/tooling/dispatcher.h +++ b/ecmascript/tooling/dispatcher.h @@ -22,11 +22,11 @@ #include "ecmascript/mem/c_containers.h" #include "ecmascript/mem/c_string.h" #include "ecmascript/napi/include/jsnapi.h" -#include "ecmascript/tooling/interface/js_debug_interface.h" +#include "ecmascript/tooling/backend/js_debugger_interface.h" #include "libpandabase/macros.h" namespace panda::ecmascript::tooling { -class FrontEnd; +class ProtocolChannel; class PtBaseReturns; class PtBaseEvents; @@ -120,10 +120,10 @@ private: class DispatcherBase { public: - explicit DispatcherBase(FrontEnd *frontend) : frontend_(frontend) {} + explicit DispatcherBase(ProtocolChannel *channel) : channel_(channel) {} virtual ~DispatcherBase() { - frontend_ = nullptr; + channel_ = nullptr; }; virtual void Dispatch(const DispatchRequest &request) = 0; @@ -132,7 +132,7 @@ protected: std::unique_ptr result); private: - FrontEnd *frontend_ {nullptr}; + ProtocolChannel *channel_ {nullptr}; NO_COPY_SEMANTIC(DispatcherBase); NO_MOVE_SEMANTIC(DispatcherBase); @@ -140,7 +140,7 @@ private: class Dispatcher { public: - explicit Dispatcher(FrontEnd *front); + explicit Dispatcher(const EcmaVM *vm, ProtocolChannel *channel); ~Dispatcher() = default; void Dispatch(const DispatchRequest &request); diff --git a/ecmascript/tooling/interface/file_stream.cpp b/ecmascript/tooling/interface/file_stream.cpp index f7ca83291f3accb5ec6522d14e7f728828b237b7..6cd5a7f424724aebc0df28307b62f347cc17ac4c 100644 --- a/ecmascript/tooling/interface/file_stream.cpp +++ b/ecmascript/tooling/interface/file_stream.cpp @@ -19,6 +19,7 @@ #include #include "ecmascript/ecma_macros.h" +#include "ecmascript/mem/c_string.h" #include "libpandabase/utils/logger.h" namespace panda::ecmascript { @@ -69,7 +70,7 @@ std::pair FileStream::FilePathValid(const std::string &fileNa } // Writes the chunk of data into the stream -bool FileStream::WriteChunk(char* data, int size) +bool FileStream::WriteChunk(char *data, int size) { if (fileStream_.fail()) { return false; @@ -99,13 +100,13 @@ bool FileDescriptorStream::Good() } // Writes the chunk of data into the stream -bool FileDescriptorStream::WriteChunk(char* data, int size) +bool FileDescriptorStream::WriteChunk(char *data, int size) { if (fd_ < 0) { return false; } - std::string str; + CString str; str.resize(size); for (int i = 0; i < size; ++i) { str[i] = data[i]; diff --git a/ecmascript/tooling/interface/file_stream.h b/ecmascript/tooling/interface/file_stream.h index 0962b24866cac5f2fd8bb822f86856e7855447f0..6a523be3861a285ce05b3975e9e3923dd817fe1a 100644 --- a/ecmascript/tooling/interface/file_stream.h +++ b/ecmascript/tooling/interface/file_stream.h @@ -49,7 +49,7 @@ private: class FileDescriptorStream : public Stream { public: explicit FileDescriptorStream(int32_t fd): fd_(fd) {} - ~FileDescriptorStream() override = default; + ~FileDescriptorStream() override = default; void EndOfStream() override; @@ -61,7 +61,7 @@ public: } // Writes the chunk of data into the stream - bool WriteChunk(char* data, int size) override; + bool WriteChunk(char *data, int size) override; bool Good() override; private: diff --git a/ecmascript/tooling/interface/js_debugger_manager.h b/ecmascript/tooling/interface/js_debugger_manager.h index a3283d86164a9a692580066b0f4761aad6ca875c..50f7baceba461c998ab20617ff9f4f2258c4837c 100644 --- a/ecmascript/tooling/interface/js_debugger_manager.h +++ b/ecmascript/tooling/interface/js_debugger_manager.h @@ -28,7 +28,7 @@ class JsDebuggerManager { public: using LibraryHandle = os::library_loader::LibraryHandle; using ObjectUpdaterFunc = - std::function &)>; + std::function &)>; JsDebuggerManager() = default; ~JsDebuggerManager() @@ -94,7 +94,7 @@ public: updaterFunc_ = updaterFunc; } - void NotifyLocalScopeUpdated(const CString &varName, const Local &value) + void NotifyLocalScopeUpdated(std::string_view varName, const Local &value) { if (updaterFunc_ != nullptr) { (*updaterFunc_)(frameHandler_.get(), varName, value); diff --git a/ecmascript/tooling/interface/stream.h b/ecmascript/tooling/interface/stream.h index 00e313a7f4533849dbd169f7304ed42a6ce713dd..5f5150b2a96c73a29ce276a9e062f15123657818 100644 --- a/ecmascript/tooling/interface/stream.h +++ b/ecmascript/tooling/interface/stream.h @@ -27,7 +27,7 @@ public: virtual int GetSize() = 0; // Writes the chunk of data into the stream - virtual bool WriteChunk(char* data, int size) = 0; + virtual bool WriteChunk(char *data, int size) = 0; virtual bool Good() = 0; }; } // namespace panda::ecmascript diff --git a/ecmascript/tooling/front_end.h b/ecmascript/tooling/protocol_channel.h similarity index 72% rename from ecmascript/tooling/front_end.h rename to ecmascript/tooling/protocol_channel.h index b41870a4ddde04691fcbd8dbd8e50c4e569e311e..09711aecfd2eb79e27a60f7d8d154031ed6db7de 100644 --- a/ecmascript/tooling/front_end.h +++ b/ecmascript/tooling/protocol_channel.h @@ -13,31 +13,31 @@ * limitations under the License. */ -#ifndef ECMASCRIPT_TOOLING_FRONT_END_H -#define ECMASCRIPT_TOOLING_FRONT_END_H +#ifndef ECMASCRIPT_TOOLING_PROTOCOL_CHANNEL_H +#define ECMASCRIPT_TOOLING_PROTOCOL_CHANNEL_H -#include "libpandabase/macros.h" #include "ecmascript/tooling/base/pt_events.h" #include "ecmascript/tooling/base/pt_returns.h" #include "ecmascript/tooling/dispatcher.h" +#include "libpandabase/macros.h" + namespace panda::ecmascript::tooling { -class FrontEnd { +class ProtocolChannel { public: - FrontEnd() = default; - virtual ~FrontEnd() = default; + ProtocolChannel() = default; + virtual ~ProtocolChannel() = default; virtual void WaitForDebugger() = 0; virtual void RunIfWaitingForDebugger() = 0; virtual void SendResponse(const DispatchRequest &request, const DispatchResponse &response, std::unique_ptr result) = 0; - virtual void SendNotification(const EcmaVM *ecmaVm, std::unique_ptr events) = 0; - virtual void SendProfilerNotify(const EcmaVM *ecmaVm, std::unique_ptr events) = 0; + virtual void SendNotification(std::unique_ptr events) = 0; private: - NO_COPY_SEMANTIC(FrontEnd); - NO_MOVE_SEMANTIC(FrontEnd); + NO_COPY_SEMANTIC(ProtocolChannel); + NO_MOVE_SEMANTIC(ProtocolChannel); }; } // namespace panda::ecmascript::tooling -#endif \ No newline at end of file +#endif // ECMASCRIPT_TOOLING_PROTOCOL_CHANNEL_H \ No newline at end of file diff --git a/ecmascript/tooling/protocol_handler.cpp b/ecmascript/tooling/protocol_handler.cpp index 3d7c560d70f04e2e68756a7a2f4a5088c57133da..0c1245b6d3e9271c630ecbb0c7b1fc690eac8016 100644 --- a/ecmascript/tooling/protocol_handler.cpp +++ b/ecmascript/tooling/protocol_handler.cpp @@ -23,7 +23,7 @@ namespace panda::ecmascript::tooling { ProtocolHandler::ProtocolHandler(std::function callback, const EcmaVM *vm) : callback_(std::move(callback)), vm_(vm) { - dispatcher_ = std::make_unique(this); + dispatcher_ = std::make_unique(vm_, this); } void ProtocolHandler::WaitForDebugger() @@ -46,14 +46,7 @@ void ProtocolHandler::ProcessCommand(const CString &msg) [[maybe_unused]] LocalScope scope(vm_); Local exception = DebuggerApi::GetAndClearException(vm_); dispatcher_->Dispatch(DispatchRequest(vm_, msg)); - DebuggerApi::ClearException(vm_); - if (!exception->IsHole()) { - DebuggerApi::SetException(vm_, exception); - } - CString startDebugging("Runtime.runIfWaitingForDebugger"); - if (msg.find(startDebugging, 0) != CString::npos) { - waitingForDebugger_ = false; - } + DebuggerApi::SetException(vm_, exception); } void ProtocolHandler::SendResponse(const DispatchRequest &request, const DispatchResponse &response, @@ -61,42 +54,33 @@ void ProtocolHandler::SendResponse(const DispatchRequest &request, const Dispatc { LOG(INFO, DEBUGGER) << "ProtocolHandler::SendResponse: " << (response.IsOk() ? "success" : "failed: " + response.GetMessage()); - const EcmaVM *ecmaVm = request.GetEcmaVM(); - Local reply = PtBaseTypes::NewObject(ecmaVm); - reply->Set(ecmaVm, StringRef::NewFromUtf8(ecmaVm, "id"), IntegerRef::New(ecmaVm, request.GetCallId())); + Local reply = PtBaseTypes::NewObject(vm_); + reply->Set(vm_, StringRef::NewFromUtf8(vm_, "id"), IntegerRef::New(vm_, request.GetCallId())); Local resultObj; if (response.IsOk() && result != nullptr) { - resultObj = result->ToObject(ecmaVm); + resultObj = result->ToObject(vm_); } else { - resultObj = CreateErrorReply(ecmaVm, response); - } - reply->Set(ecmaVm, StringRef::NewFromUtf8(ecmaVm, "result"), Local(resultObj)); - SendReply(ecmaVm, reply); -} - -void ProtocolHandler::SendNotification(const EcmaVM *ecmaVm, std::unique_ptr events) -{ - if (!ecmaVm->GetJsDebuggerManager()->IsDebugMode()) { - return; + resultObj = CreateErrorReply(response); } - SendProfilerNotify(ecmaVm, std::move(events)); + reply->Set(vm_, StringRef::NewFromUtf8(vm_, "result"), Local(resultObj)); + SendReply(reply); } -void ProtocolHandler::SendProfilerNotify(const EcmaVM *ecmaVm, std::unique_ptr events) +void ProtocolHandler::SendNotification(std::unique_ptr events) { if (events == nullptr) { return; } LOG(DEBUG, DEBUGGER) << "ProtocolHandler::SendNotification: " << events->GetName(); - SendReply(ecmaVm, events->ToObject(ecmaVm)); + SendReply(events->ToObject(vm_)); } -void ProtocolHandler::SendReply(const EcmaVM *ecmaVm, Local reply) +void ProtocolHandler::SendReply(Local reply) { - Local str = JSON::Stringify(ecmaVm, reply); + Local str = JSON::Stringify(vm_, reply); if (str->IsException()) { - DebuggerApi::ClearException(ecmaVm); + DebuggerApi::ClearException(vm_); LOG(ERROR, DEBUGGER) << "json stringifier throw exception"; return; } @@ -108,17 +92,17 @@ void ProtocolHandler::SendReply(const EcmaVM *ecmaVm, Local reply) callback_(StringRef::Cast(*str)->ToString()); } -Local ProtocolHandler::CreateErrorReply(const EcmaVM *ecmaVm, const DispatchResponse &response) +Local ProtocolHandler::CreateErrorReply(const DispatchResponse &response) { - Local result = PtBaseTypes::NewObject(ecmaVm); + Local result = PtBaseTypes::NewObject(vm_); if (!response.IsOk()) { - result->Set(ecmaVm, - Local(StringRef::NewFromUtf8(ecmaVm, "code")), - IntegerRef::New(ecmaVm, static_cast(response.GetError()))); - result->Set(ecmaVm, - Local(StringRef::NewFromUtf8(ecmaVm, "message")), - Local(StringRef::NewFromUtf8(ecmaVm, response.GetMessage().c_str()))); + result->Set(vm_, + Local(StringRef::NewFromUtf8(vm_, "code")), + IntegerRef::New(vm_, static_cast(response.GetError()))); + result->Set(vm_, + Local(StringRef::NewFromUtf8(vm_, "message")), + Local(StringRef::NewFromUtf8(vm_, response.GetMessage().c_str()))); } return result; diff --git a/ecmascript/tooling/protocol_handler.h b/ecmascript/tooling/protocol_handler.h index 5be754cad130734cae57340a41d01d80316dd122..4667805fc0af703c247f1c4addd4a9718c14f396 100644 --- a/ecmascript/tooling/protocol_handler.h +++ b/ecmascript/tooling/protocol_handler.h @@ -20,12 +20,12 @@ #include #include -#include "ecmascript/tooling/front_end.h" +#include "ecmascript/tooling/protocol_channel.h" namespace panda::ecmascript::tooling { -class ProtocolHandler final : public FrontEnd { +class ProtocolHandler final : public ProtocolChannel { public: - explicit ProtocolHandler(std::function callback, const EcmaVM *vm); + ProtocolHandler(std::function callback, const EcmaVM *vm); ~ProtocolHandler() override = default; void WaitForDebugger() override; @@ -33,18 +33,13 @@ public: void ProcessCommand(const CString &msg); void SendResponse(const DispatchRequest &request, const DispatchResponse &response, std::unique_ptr result) override; - void SendNotification(const EcmaVM *ecmaVm, std::unique_ptr events) override; - void SendProfilerNotify(const EcmaVM *ecmaVm, std::unique_ptr events) override; - const EcmaVM *GetEcmaVM() const - { - return vm_; - } + void SendNotification(std::unique_ptr events) override; private: NO_MOVE_SEMANTIC(ProtocolHandler); NO_COPY_SEMANTIC(ProtocolHandler); - Local CreateErrorReply(const EcmaVM *ecmaVm, const DispatchResponse &response); - void SendReply(const EcmaVM *ecmaVm, Local reply); + Local CreateErrorReply(const DispatchResponse &response); + void SendReply(Local reply); std::function callback_; std::unique_ptr dispatcher_ {}; diff --git a/ecmascript/tooling/test/debugger_commands_test.cpp b/ecmascript/tooling/test/debugger_commands_test.cpp index d6d9eb1f02ef13531f7d7792a9cc8bbe694d7053..43e04f306c4d13aa5b204a9b14a4bf8f81b2ce1e 100644 --- a/ecmascript/tooling/test/debugger_commands_test.cpp +++ b/ecmascript/tooling/test/debugger_commands_test.cpp @@ -21,7 +21,7 @@ #include "ecmascript/tooling/base/pt_returns.h" #include "ecmascript/tooling/debugger_service.h" #include "ecmascript/tooling/dispatcher.h" -#include "ecmascript/tooling/interface/js_debugger.h" +#include "ecmascript/tooling/backend/js_debugger.h" using namespace panda::ecmascript; using namespace panda::ecmascript::tooling; diff --git a/ecmascript/tooling/test/debugger_script_test.cpp b/ecmascript/tooling/test/debugger_script_test.cpp index ff76b9499ab6c65295803cd5ed9fa1e5627e0552..afad8207dd732920e90c96dc79f206000bfbe47f 100644 --- a/ecmascript/tooling/test/debugger_script_test.cpp +++ b/ecmascript/tooling/test/debugger_script_test.cpp @@ -21,7 +21,7 @@ #include "ecmascript/tooling/base/pt_returns.h" #include "ecmascript/tooling/debugger_service.h" #include "ecmascript/tooling/dispatcher.h" -#include "ecmascript/tooling/interface/js_debugger.h" +#include "ecmascript/tooling/backend/js_debugger.h" #include "ecmascript/tooling/base/pt_script.h" using namespace panda::ecmascript; diff --git a/ecmascript/tooling/test/js_pt_hooks_test.cpp b/ecmascript/tooling/test/js_pt_hooks_test.cpp index c5f3c8ef720e2fa8357a824513a6d355f269f5e6..80101a9257c9b72c9043e0c311755c3127ab93b3 100644 --- a/ecmascript/tooling/test/js_pt_hooks_test.cpp +++ b/ecmascript/tooling/test/js_pt_hooks_test.cpp @@ -17,13 +17,13 @@ #include "ecmascript/js_tagged_value-inl.h" #include "ecmascript/object_factory.h" #include "ecmascript/tests/test_helper.h" -#include "ecmascript/tooling/agent/js_backend.h" -#include "ecmascript/tooling/dispatcher.h" -#include "ecmascript/tooling/agent/js_pt_hooks.h" -#include "ecmascript/tooling/interface/js_debugger.h" +#include "ecmascript/tooling/agent/debugger_impl.h" +#include "ecmascript/tooling/backend/js_pt_hooks.h" +#include "ecmascript/tooling/backend/js_debugger.h" #include "ecmascript/tooling/base/pt_types.h" #include "ecmascript/tooling/base/pt_events.h" #include "ecmascript/tooling/dispatcher.h" + using namespace panda::ecmascript; using namespace panda::ecmascript::tooling; @@ -59,8 +59,8 @@ protected: HWTEST_F_L0(JSPtHooksTest, BreakpointTest) { - auto backend = std::make_unique(ecmaVm); - std::unique_ptrjspthooks = std::make_unique(backend.get()); + auto debugger = std::make_unique(ecmaVm, nullptr, nullptr); + std::unique_ptr jspthooks = std::make_unique(debugger.get()); const char *pandaFile = " "; EntityId methodId(0); uint32_t bytecodeOffset = 0; @@ -71,25 +71,16 @@ HWTEST_F_L0(JSPtHooksTest, BreakpointTest) HWTEST_F_L0(JSPtHooksTest, LoadModuleTest) { - auto backend = std::make_unique(ecmaVm); - std::unique_ptrjspthooks = std::make_unique(backend.get()); + auto debugger = std::make_unique(ecmaVm, nullptr, nullptr); + std::unique_ptr jspthooks = std::make_unique(debugger.get()); jspthooks->LoadModule("pandafile/test.abc"); ASSERT_NE(jspthooks, nullptr); } -HWTEST_F_L0(JSPtHooksTest, PausedTest) -{ - auto backend = std::make_unique(ecmaVm); - std::unique_ptrjspthooks = std::make_unique(backend.get()); - PauseReason reason1 = PauseReason::EXCEPTION; - jspthooks->Paused(reason1); - ASSERT_NE(jspthooks, nullptr); -} - HWTEST_F_L0(JSPtHooksTest, ExceptionTest) { - auto backend = std::make_unique(ecmaVm); - std::unique_ptrjspthooks = std::make_unique(backend.get()); + auto debugger = std::make_unique(ecmaVm, nullptr, nullptr); + std::unique_ptr jspthooks = std::make_unique(debugger.get()); const char *pandaFile = " "; EntityId methodId(0); uint32_t bytecodeOffset = 0; @@ -100,8 +91,8 @@ HWTEST_F_L0(JSPtHooksTest, ExceptionTest) HWTEST_F_L0(JSPtHooksTest, SingleStepTest) { - auto backend = std::make_unique(ecmaVm); - std::unique_ptrjspthooks = std::make_unique(backend.get()); + auto debugger = std::make_unique(ecmaVm, nullptr, nullptr); + std::unique_ptr jspthooks = std::make_unique(debugger.get()); const char *pandaFile = " "; EntityId methodId(0); uint32_t bytecodeOffset = 0; @@ -111,16 +102,16 @@ HWTEST_F_L0(JSPtHooksTest, SingleStepTest) HWTEST_F_L0(JSPtHooksTest, VmStartTest) { - auto backend = std::make_unique(ecmaVm); - std::unique_ptrjspthooks = std::make_unique(backend.get()); + auto debugger = std::make_unique(ecmaVm, nullptr, nullptr); + std::unique_ptr jspthooks = std::make_unique(debugger.get()); jspthooks->VmStart(); ASSERT_NE(jspthooks, nullptr); } HWTEST_F_L0(JSPtHooksTest, VmDeathTest) { - auto backend = std::make_unique(ecmaVm); - std::unique_ptrjspthooks = std::make_unique(backend.get()); + auto debugger = std::make_unique(ecmaVm, nullptr, nullptr); + std::unique_ptr jspthooks = std::make_unique(debugger.get()); jspthooks->VmDeath(); ASSERT_NE(jspthooks, nullptr); } diff --git a/ecmascript/tooling/test/utils/test_events.h b/ecmascript/tooling/test/utils/test_events.h index e42850de3ab1244d235490ffce18b75206610030..17236c9dc730d8508928a299ede8fc14c0ea3255 100644 --- a/ecmascript/tooling/test/utils/test_events.h +++ b/ecmascript/tooling/test/utils/test_events.h @@ -17,17 +17,15 @@ #define ECMASCRIPT_TOOLING_TEST_UTILS_TEST_EVENTS_H #include -#include "ecmascript/tooling/agent/js_backend.h" -#include "ecmascript/tooling/agent/js_pt_hooks.h" + +#include "ecmascript/tooling/backend/js_pt_hooks.h" namespace panda::ecmascript::tooling::test { using BreakpointCallback = std::function; using LoadModuleCallback = std::function; -using PausedCallback = std::function; using ExceptionCallback = std::function; using SingleStepCallback = std::function; using VmStartCallback = std::function; -using VmInitializationCallback = std::function; using VmDeathCallback = std::function; using Scenario = std::function; @@ -49,16 +47,15 @@ std::ostream &operator<<(std::ostream &out, DebugEvent value); struct TestEvents { BreakpointCallback breakpoint; LoadModuleCallback loadModule; - PausedCallback paused; ExceptionCallback exception; SingleStepCallback singleStep; VmStartCallback vmStart; - VmInitializationCallback vmInit; VmDeathCallback vmDeath; Scenario scenario; + const EcmaVM *vm_ {nullptr}; JSDebugger *debugInterface_ {nullptr}; - JSBackend *backend_ {nullptr}; + DebuggerImpl *debugger_ {nullptr}; TestEvents(); virtual ~TestEvents() = default; diff --git a/ecmascript/tooling/test/utils/test_extractor.h b/ecmascript/tooling/test/utils/test_extractor.h index 3b43cb3e7acac2c3cfd63c4d575800310c9c3029..4cf68e4f2e3e3b591dbe72c3e9a0d761f85a8b9b 100644 --- a/ecmascript/tooling/test/utils/test_extractor.h +++ b/ecmascript/tooling/test/utils/test_extractor.h @@ -16,7 +16,7 @@ #ifndef ECMASCRIPT_TOOLING_TEST_UTILS_TEST_EXTRACTOR_H #define ECMASCRIPT_TOOLING_TEST_UTILS_TEST_EXTRACTOR_H -#include "ecmascript/tooling/js_pt_extractor.h" +#include "ecmascript/tooling/backend/js_pt_extractor.h" namespace panda::ecmascript::tooling::test { using EntityId = panda_file::File::EntityId; diff --git a/ecmascript/tooling/test/utils/test_hooks.h b/ecmascript/tooling/test/utils/test_hooks.h index 6a70fe1e23fd82af66f9dde95ed90e174f86c84d..3ab19159e29f1f27fc7b3ddb1c10e0c34ae28023 100644 --- a/ecmascript/tooling/test/utils/test_hooks.h +++ b/ecmascript/tooling/test/utils/test_hooks.h @@ -16,21 +16,23 @@ #ifndef ECMASCRIPT_TOOLING_TEST_UTILS_TEST_HOOKS_H #define ECMASCRIPT_TOOLING_TEST_UTILS_TEST_HOOKS_H -#include "ecmascript/tooling/agent/js_pt_hooks.h" -#include "ecmascript/tooling/agent/js_backend.h" +#include "ecmascript/tooling/agent/debugger_impl.h" +#include "ecmascript/tooling/backend/js_pt_hooks.h" #include "ecmascript/tooling/test/utils/test_util.h" namespace panda::ecmascript::tooling::test { class TestHooks : public PtHooks { public: - TestHooks(const CString &testName, const EcmaVM *vm) + TestHooks(const CString &testName, const EcmaVM *vm) : vm_(vm) { - backend_ = std::make_unique(vm); + runtime_ = std::make_unique(vm, nullptr); + debugger_ = std::make_unique(vm, nullptr, runtime_.get()); testName_ = testName; test_ = TestUtil::GetTest(testName); - test_->backend_ = backend_.get(); - test_->debugInterface_ = backend_->GetDebugger(); - debugInterface_ = backend_->GetDebugger(); + test_->vm_ = vm; + test_->debugger_ = debugger_.get(); + test_->debugInterface_ = debugger_->GetDebugger(); + debugInterface_ = debugger_->GetDebugger(); TestUtil::Reset(); debugInterface_->RegisterHooks(this); } @@ -56,22 +58,15 @@ public: } } - void Paused(PauseReason reason) override - { - if (test_->paused) { - test_->paused(reason); - } - }; - void Exception(const JSPtLocation &location) override { if (test_->exception) { - Local exception = DebuggerApi::GetAndClearException(backend_->GetEcmaVm()); + Local exception = DebuggerApi::GetAndClearException(vm_); test_->exception(location); if (!exception->IsHole()) { - DebuggerApi::SetException(backend_->GetEcmaVm(), exception); + DebuggerApi::SetException(vm_, exception); } } } @@ -114,7 +109,9 @@ public: ~TestHooks() = default; private: - std::unique_ptr backend_ {nullptr}; + const EcmaVM *vm_ {nullptr}; + std::unique_ptr runtime_ {nullptr}; + std::unique_ptr debugger_ {nullptr}; JSDebugger *debugInterface_; CString testName_; TestEvents *test_; diff --git a/ecmascript/tooling/test/utils/test_util.h b/ecmascript/tooling/test/utils/test_util.h index 8062a0d56509dcbe63a4b97382579432760fff0d..4186834b40664532b9d7f5a4aa28a2234553bb9e 100644 --- a/ecmascript/tooling/test/utils/test_util.h +++ b/ecmascript/tooling/test/utils/test_util.h @@ -17,7 +17,8 @@ #define ECMASCRIPT_TOOLING_TEST_UTILS_TEST_UTIL_H #include "ecmascript/jspandafile/js_pandafile_manager.h" -#include "ecmascript/tooling/interface/js_debugger.h" +#include "ecmascript/tooling/agent/debugger_impl.h" +#include "ecmascript/tooling/backend/js_debugger.h" #include "ecmascript/tooling/test/utils/test_events.h" #include "ecmascript/tooling/test/utils/test_extractor.h" #include "os/mutex.h" diff --git a/ecmascript/tooling/test/utils/testcases/js_breakpoint_test.h b/ecmascript/tooling/test/utils/testcases/js_breakpoint_test.h index f24bfea13f23f7bd0dfbe91671c09be8cc6b9f5b..de9abb00e36fd84f15b93f06ea9b5cf47584202e 100644 --- a/ecmascript/tooling/test/utils/testcases/js_breakpoint_test.h +++ b/ecmascript/tooling/test/utils/testcases/js_breakpoint_test.h @@ -42,9 +42,9 @@ public: if (moduleName != pandaFile_) { return true; } - ASSERT_TRUE(backend_->NotifyScriptParsed(0, pandaFile_)); + ASSERT_TRUE(debugger_->NotifyScriptParsed(0, pandaFile_)); flag_ = false; - auto condFuncRef = FunctionRef::Undefined(backend_->GetEcmaVm()); + auto condFuncRef = FunctionRef::Undefined(vm_); auto ret = debugInterface_->SetBreakpoint(location_, condFuncRef); ASSERT_TRUE(ret); } diff --git a/ecmascript/tooling/test/utils/testcases/js_exception_test.h b/ecmascript/tooling/test/utils/testcases/js_exception_test.h index a1ea8668787298ff1965a9170a87b869113c5ad4..004eb6e14ee56bd779fc621ac8df8c70dc795b73 100644 --- a/ecmascript/tooling/test/utils/testcases/js_exception_test.h +++ b/ecmascript/tooling/test/utils/testcases/js_exception_test.h @@ -34,7 +34,7 @@ public: ASSERT_LOCATION_EQ(location, location_); ++breakpointCounter_; CVector> callFrames; - ASSERT_TRUE(backend_->GenerateCallFrames(&callFrames)); + ASSERT_TRUE(debugger_->GenerateCallFrames(&callFrames)); ASSERT_TRUE(callFrames.size() > 0); auto jsLocation = callFrames[0]->GetLocation(); ASSERT_TRUE(jsLocation != nullptr); @@ -50,7 +50,7 @@ public: ASSERT_EQ(sourceLocation.column, 27); ++exceptionCounter_; CVector> callFrames; - ASSERT_TRUE(backend_->GenerateCallFrames(&callFrames)); + ASSERT_TRUE(debugger_->GenerateCallFrames(&callFrames)); ASSERT_TRUE(callFrames.size() > 0); auto jsLocation = callFrames[0]->GetLocation(); ASSERT_TRUE(jsLocation != nullptr); @@ -65,9 +65,9 @@ public: if (moduleName != pandaFile_) { return true; } - ASSERT_TRUE(backend_->NotifyScriptParsed(0, pandaFile_)); + ASSERT_TRUE(debugger_->NotifyScriptParsed(0, pandaFile_)); flag_ = false; - auto condFuncRef = FunctionRef::Undefined(backend_->GetEcmaVm()); + auto condFuncRef = FunctionRef::Undefined(vm_); auto ret = debugInterface_->SetBreakpoint(location_, condFuncRef); ASSERT_TRUE(ret); } diff --git a/ecmascript/tooling/test/utils/testcases/js_single_step_test.h b/ecmascript/tooling/test/utils/testcases/js_single_step_test.h index 2ff3c4d9bd5a238307c1d9ae228e5c8715c6a92b..988859539935490367cf8ebe5ae47e590a1ddf34 100644 --- a/ecmascript/tooling/test/utils/testcases/js_single_step_test.h +++ b/ecmascript/tooling/test/utils/testcases/js_single_step_test.h @@ -41,7 +41,7 @@ public: return true; } flag_ = false; - auto condFuncRef = FunctionRef::Undefined(backend_->GetEcmaVm()); + auto condFuncRef = FunctionRef::Undefined(vm_); auto ret = debugInterface_->SetBreakpoint(locationEnd_, condFuncRef); ASSERT_TRUE(ret); }