/* * Copyright (c) 2021 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 "js_backend.h" #include #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::tooling::ecmascript { using ObjectType = RemoteObject::TypeName; using ObjectSubType = RemoteObject::SubTypeName; using ObjectClassName = RemoteObject::ClassName; const std::string DATA_APP_PATH = "/data/app/"; JSBackend::JSBackend(FrontEnd *frontend) : frontend_(frontend) { ecmaVm_ = static_cast(frontend)->GetEcmaVM(); hooks_ = std::make_unique(this); debugger_ = DebuggerApi::CreateJSDebugger(ecmaVm_); DebuggerApi::RegisterHooks(debugger_, hooks_.get()); } JSBackend::JSBackend(const EcmaVM *vm) : ecmaVm_(vm) { // For testcases debugger_ = DebuggerApi::CreateJSDebugger(ecmaVm_); } JSBackend::~JSBackend() { DebuggerApi::DestroyJSDebugger(debugger_); } void JSBackend::ProcessCommand() { frontend_->ProcessCommand(ecmaVm_); } void JSBackend::WaitForDebugger() { frontend_->WaitForDebugger(ecmaVm_); } void JSBackend::NotifyPaused(std::optional location, PauseReason reason) { if (!pauseOnException_ && reason == EXCEPTION) { return; } CVector hitBreakpoints; if (location.has_value()) { BreakpointDetails detail; PtJSExtractor *extractor = nullptr; auto scriptFunc = [this, &extractor, &detail](PtScript *script) -> bool { detail.url_ = script->GetUrl(); extractor = GetExtractor(detail.url_); return true; }; auto offsetFunc = [&detail](int32_t line) -> bool { detail.line_ = line; return true; }; if (!MatchScripts(scriptFunc, location->GetPandaFile(), ScriptMatchType::FILE_NAME) || extractor == nullptr || !extractor->MatchWithOffset(offsetFunc, location->GetMethodId(), location->GetBytecodeOffset())) { LOG(ERROR, DEBUGGER) << "NotifyPaused: unknown " << location->GetPandaFile(); return; } detail.column_ = 0; hitBreakpoints.emplace_back(BreakpointDetails::ToString(detail)); } // 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)); frontend_->SendNotification(ecmaVm_, std::move(paused)); // Waiting for Debugger frontend_->WaitForDebugger(ecmaVm_); } void JSBackend::NotifyResume() { frontend_->RunIfWaitingForDebugger(); // Notify resumed event frontend_->SendNotification(ecmaVm_, std::make_unique()); } bool JSBackend::NotifyScriptParsed(int32_t scriptId, const CString &fileName) { 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 panda_file::File *pfs = DebuggerApi::FindPandaFile(ecmaVm_, fileName); if (pfs == nullptr) { LOG(WARNING, DEBUGGER) << "NotifyScriptParsed: unknown file: " << fileName; return false; } auto classes = pfs->GetClasses(); ASSERT(classes.Size() > 0); size_t index = 0; for (; index < classes.Size(); index++) { if (!(*pfs).IsExternal(panda_file::File::EntityId(classes[index]))) { break; } } panda_file::ClassDataAccessor cda(*pfs, panda_file::File::EntityId(classes[index])); auto lang = cda.GetSourceLang(); if (lang.value_or(panda_file::SourceLang::PANDA_ASSEMBLY) != panda_file::SourceLang::ECMASCRIPT) { LOG(ERROR, DEBUGGER) << "NotifyScriptParsed: Unsupport file: " << fileName; return false; } CString url; CString source; PtJSExtractor *extractor = GenerateExtractor(pfs); if (extractor == nullptr) { LOG(ERROR, DEBUGGER) << "NotifyScriptParsed: Unsupport file: " << fileName; return false; } const uint32_t MIN_SOURCE_CODE_LENGTH = 5; // maybe return 'ANDA' when source code is empty for (const auto &method : extractor->GetMethodIdList()) { source = CString(extractor->GetSourceCode(method)); // only main function has source code if (source.size() >= MIN_SOURCE_CODE_LENGTH) { url = CString(extractor->GetSourceFile(method)); break; } } if (url.empty()) { LOG(ERROR, DEBUGGER) << "NotifyScriptParsed: invalid file: " << fileName; return false; } // Notify script parsed event std::unique_ptr script = std::make_unique(scriptId, fileName, url, source); std::unique_ptr scriptParsed = std::make_unique(); scriptParsed->SetScriptId(script->GetScriptId()) .SetUrl(script->GetUrl()) .SetStartLine(0) .SetStartColumn(0) .SetEndLine(script->GetEndLine()) .SetEndColumn(0) .SetExecutionContextId(0) .SetHash(script->GetHash()); if (frontend_ != nullptr) { frontend_->SendNotification(ecmaVm_, std::move(scriptParsed)); } // Store parsed script in map scripts_[script->GetScriptId()] = std::move(script); return true; } bool JSBackend::StepComplete(const PtLocation &location) { PtJSExtractor *extractor = nullptr; auto scriptFunc = [this, &extractor](PtScript *script) -> bool { extractor = GetExtractor(script->GetUrl()); return true; }; auto offsetFunc = [](int32_t line) -> bool { return line == SPECIAL_LINE_MARK; }; if (MatchScripts(scriptFunc, location.GetPandaFile(), ScriptMatchType::FILE_NAME) && extractor != nullptr && extractor->MatchWithOffset(offsetFunc, location.GetMethodId(), location.GetBytecodeOffset())) { LOG(INFO, DEBUGGER) << "StepComplete: skip -1"; return false; } if (pauseOnNextByteCode_ || (singleStepper_ != nullptr && singleStepper_->StepComplete(location.GetBytecodeOffset()))) { LOG(INFO, DEBUGGER) << "StepComplete: pause on current byte_code"; pauseOnNextByteCode_ = false; return true; } return false; } std::optional JSBackend::GetPossibleBreakpoints(Location *start, [[maybe_unused]] Location *end, CVector> *locations) { PtJSExtractor *extractor = nullptr; auto scriptFunc = [this, &extractor](PtScript *script) -> bool { extractor = GetExtractor(script->GetUrl()); return true; }; if (!MatchScripts(scriptFunc, start->GetScriptId(), ScriptMatchType::SCRIPT_ID) || extractor == nullptr) { return Error(Error::Type::INVALID_BREAKPOINT, "extractor not found"); } int32_t line = start->GetLine(); auto lineFunc = []([[maybe_unused]] File::EntityId id, [[maybe_unused]] uint32_t offset) -> bool { return true; }; if (extractor->MatchWithLine(lineFunc, line)) { std::unique_ptr location = std::make_unique(); location->SetScriptId(start->GetScriptId()).SetLine(line).SetColumn(0); locations->emplace_back(std::move(location)); } return {}; } std::optional JSBackend::SetBreakpointByUrl(const CString &url, int32_t lineNumber, [[maybe_unused]] int32_t columnNumber, CString *out_id, CVector> *outLocations) { PtJSExtractor *extractor = GetExtractor(url); if (extractor == nullptr) { LOG(ERROR, DEBUGGER) << "SetBreakpointByUrl: extractor is null"; return Error(Error::Type::METHOD_NOT_FOUND, "Extractor not found"); } CString 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 Error(Error::Type::INVALID_BREAKPOINT, "Url not found"); } std::optional ret = std::nullopt; auto lineFunc = [this, fileName, &ret](File::EntityId id, uint32_t offset) -> bool { PtLocation location {fileName.c_str(), id, offset}; ret = DebuggerApi::SetBreakpoint(debugger_, location); return true; }; if (!extractor->MatchWithLine(lineFunc, lineNumber)) { LOG(ERROR, DEBUGGER) << "failed to set breakpoint line number: " << lineNumber; return Error(Error::Type::INVALID_BREAKPOINT, "Breakpoint not found"); } if (!ret.has_value()) { BreakpointDetails metaData{lineNumber, 0, url}; *out_id = 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 ret; } std::optional JSBackend::RemoveBreakpoint(const BreakpointDetails &metaData) { PtJSExtractor *extractor = GetExtractor(metaData.url_); if (extractor == nullptr) { LOG(ERROR, DEBUGGER) << "RemoveBreakpoint: extractor is null"; return Error(Error::Type::METHOD_NOT_FOUND, "Extractor not found"); } 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 Error(Error::Type::INVALID_BREAKPOINT, "Url not found"); } std::optional ret = std::nullopt; auto lineFunc = [this, fileName, &ret](File::EntityId id, uint32_t offset) -> bool { PtLocation location {fileName.c_str(), id, offset}; ret = DebuggerApi::RemoveBreakpoint(debugger_, location); return true; }; if (!extractor->MatchWithLine(lineFunc, metaData.line_)) { LOG(ERROR, DEBUGGER) << "failed to set breakpoint line number: " << metaData.line_; return Error(Error::Type::INVALID_BREAKPOINT, "Breakpoint not found"); } LOG(INFO, DEBUGGER) << "remove breakpoint line number:" << metaData.line_; return ret; } std::optional JSBackend::Pause() { pauseOnNextByteCode_ = true; return {}; } std::optional JSBackend::Resume() { singleStepper_.reset(); NotifyResume(); return {}; } std::optional JSBackend::StepInto() { JSMethod *method = DebuggerApi::GetMethod(ecmaVm_); PtJSExtractor *extractor = GetExtractor(method->GetPandaFile()); if (extractor == nullptr) { LOG(ERROR, DEBUGGER) << "StepInto: extractor is null"; return Error(Error::Type::METHOD_NOT_FOUND, "Extractor not found"); } singleStepper_ = extractor->GetStepIntoStepper(ecmaVm_); NotifyResume(); return {}; } std::optional JSBackend::StepOver() { JSMethod *method = DebuggerApi::GetMethod(ecmaVm_); PtJSExtractor *extractor = GetExtractor(method->GetPandaFile()); if (extractor == nullptr) { LOG(ERROR, DEBUGGER) << "StepOver: extractor is null"; return Error(Error::Type::METHOD_NOT_FOUND, "Extractor not found"); } singleStepper_ = extractor->GetStepOverStepper(ecmaVm_); NotifyResume(); return {}; } std::optional JSBackend::StepOut() { JSMethod *method = DebuggerApi::GetMethod(ecmaVm_); PtJSExtractor *extractor = GetExtractor(method->GetPandaFile()); if (extractor == nullptr) { LOG(ERROR, DEBUGGER) << "StepOut: extractor is null"; return Error(Error::Type::METHOD_NOT_FOUND, "Extractor not found"); } singleStepper_ = extractor->GetStepOutStepper(ecmaVm_); NotifyResume(); return {}; } std::optional JSBackend::EvaluateValue(const CString &callFrameId, const CString &expression, std::unique_ptr *result) { JSMethod *method = DebuggerApi::GetMethod(ecmaVm_); if (method->IsNative()) { LOG(ERROR, DEBUGGER) << "EvaluateValue: Native Frame not support"; *result = RemoteObject::FromTagged(ecmaVm_, Exception::EvalError(ecmaVm_, StringRef::NewFromUtf8(ecmaVm_, "Runtime internal error"))); return Error(Error::Type::METHOD_NOT_FOUND, "Native Frame not support"); } DebugInfoExtractor *extractor = GetExtractor(method->GetPandaFile()); if (extractor == nullptr) { LOG(ERROR, DEBUGGER) << "EvaluateValue: extractor is null"; *result = RemoteObject::FromTagged(ecmaVm_, Exception::EvalError(ecmaVm_, StringRef::NewFromUtf8(ecmaVm_, "Runtime internal error"))); return Error(Error::Type::METHOD_NOT_FOUND, "Extractor not found"); } 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())); } int32_t regIndex = -1; auto varInfos = extractor->GetLocalVariableTable(method->GetFileId()); for (const auto &varInfo : varInfos) { if (varInfo.name == std::string(varName)) { regIndex = varInfo.reg_number; } } if (regIndex == -1) { *result = RemoteObject::FromTagged(ecmaVm_, Exception::EvalError(ecmaVm_, StringRef::NewFromUtf8(ecmaVm_, "Unknow input params"))); return Error(Error::Type::METHOD_NOT_FOUND, "Unsupport expression"); } if (varValue.empty()) { return GetValue(regIndex, result); } if (callFrameId != "0") { *result = RemoteObject::FromTagged(ecmaVm_, Exception::EvalError(ecmaVm_, StringRef::NewFromUtf8(ecmaVm_, "Only allow set value in current frame"))); return Error(Error::Type::METHOD_NOT_FOUND, "Unsupport parent frame set value"); } return SetValue(regIndex, result, varValue); } 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; } PtJSExtractor *JSBackend::GenerateExtractor(const panda_file::File *file) { if (file->GetFilename().substr(0, DATA_APP_PATH.length()) != DATA_APP_PATH) { return nullptr; } auto extractor = std::make_unique(file); PtJSExtractor *res = extractor.get(); extractors_[file] = std::move(extractor); return res; } PtJSExtractor *JSBackend::GetExtractor(const panda_file::File *file) { if (extractors_.find(file) == extractors_.end()) { return nullptr; } return extractors_[file].get(); } PtJSExtractor *JSBackend::GetExtractor(const CString &url) { for (const auto &iter : extractors_) { auto methods = iter.second->GetMethodIdList(); for (const auto &method : methods) { auto sourceFile = iter.second->GetSourceFile(method); if (sourceFile == url) { return iter.second.get(); } } } return nullptr; } bool JSBackend::GenerateCallFrames(CVector> *callFrames) { int32_t callFrameId = 0; auto walkerFunc = [this, &callFrameId, &callFrames](const InterpretedFrameHandler *frameHandler) -> StackState { JSMethod *method = DebuggerApi::GetMethod(frameHandler); if (method->IsNative()) { 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 { callFrames->emplace_back(std::move(callFrame)); callFrameId++; } return StackState::CONTINUE; }; return DebuggerApi::StackWalker(ecmaVm_, walkerFunc); } bool JSBackend::GenerateCallFrame(CallFrame *callFrame, const InterpretedFrameHandler *frameHandler, int32_t callFrameId) { JSMethod *method = DebuggerApi::GetMethod(frameHandler); auto *pf = method->GetPandaFile(); PtJSExtractor *extractor = GetExtractor(pf); 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->GetFileId()); 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 offsetFunc = [&location](int32_t line) -> bool { location->SetLine(line); return true; }; if (!extractor->MatchWithOffset(offsetFunc, method->GetFileId(), 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(DebuggerApi::ToCString(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 InterpretedFrameHandler *frameHandler, std::unique_ptr *thisObj) { auto localScope = std::make_unique(); std::unique_ptr local = std::make_unique(); Local localObject(local->NewObject(ecmaVm_)); local->SetType(ObjectType::Object) .SetObjectId(curObjectId_) .SetClassName(ObjectClassName::Object) .SetDescription(RemoteObject::ObjectDescription); propertiesPair_[curObjectId_++] = Global(ecmaVm_, localObject); PtJSExtractor *extractor = GetExtractor(DebuggerApi::GetMethod(frameHandler)->GetPandaFile()); if (extractor == nullptr) { LOG(ERROR, DEBUGGER) << "GetScopeChain: extractor is null"; return localScope; } panda_file::File::EntityId methodId = DebuggerApi::GetMethod(frameHandler)->GetFileId(); Local name = JSValueRef::Undefined(ecmaVm_); Local value = JSValueRef::Undefined(ecmaVm_); for (const auto &var : extractor->GetLocalVariableTable(methodId)) { value = DebuggerApi::GetVRegValue(ecmaVm_, frameHandler, var.reg_number); if (var.name == "this") { *thisObj = RemoteObject::FromTagged(ecmaVm_, value); if (value->IsObject() && !value->IsProxy()) { (*thisObj)->SetObjectId(curObjectId_); propertiesPair_[curObjectId_++] = Global(ecmaVm_, value); } } else { name = StringRef::NewFromUtf8(ecmaVm_, var.name.c_str()); PropertyAttribute descriptor(value, true, true, true); localObject->DefineProperty(ecmaVm_, name, descriptor); } } const panda_file::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(static_cast(lines.front().line)) .SetColumn(0); endLoc->SetScriptId(script->GetScriptId()) .SetLine(static_cast(lines.back().line)) .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; } 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; } void JSBackend::SetPauseOnException(bool flag) { pauseOnException_ = flag; } std::optional JSBackend::SetValue(int32_t regIndex, std::unique_ptr *result, const CString &varValue) { Local taggedValue; 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_, "Unsupport value"))); return Error(Error::Type::METHOD_NOT_FOUND, "Unsupport value"); } taggedValue = NumberRef::New(ecmaVm_, d); } DebuggerApi::SetVRegValue(ecmaVm_, regIndex, taggedValue); *result = RemoteObject::FromTagged(ecmaVm_, taggedValue); return {}; } std::optional JSBackend::GetValue(int32_t regIndex, std::unique_ptr *result) { Local vValue = DebuggerApi::GetVRegValue(ecmaVm_, regIndex); *result = RemoteObject::FromTagged(ecmaVm_, vValue); if (vValue->IsObject() && !vValue->IsProxy()) { (*result)->SetObjectId(curObjectId_); propertiesPair_[curObjectId_++] = Global(ecmaVm_, vValue); } 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 = RemoteObject::FromTagged(ecmaVm_, prototype); if (prototype->IsObject() && !prototype->IsProxy()) { protoObj->SetObjectId(curObjectId_); propertiesPair_[curObjectId_++] = Global(ecmaVm_, prototype); } 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 = RemoteObject::FromTagged(ecmaVm_, proto); if (proto->IsObject() && !proto->IsProxy()) { protoObj->SetObjectId(curObjectId_); propertiesPair_[curObjectId_++] = Global(ecmaVm_, proto); } 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(uint32_t 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; } Local keys = Local(value)->GetOwnPropertyNames(ecmaVm_); array_size_t length = keys->Length(ecmaVm_); Local name = JSValueRef::Undefined(ecmaVm_); for (array_size_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); } } // namespace panda::tooling::ecmascript