• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "agent/debugger_impl.h"
17 
18 #include "base/pt_base64.h"
19 #include "base/pt_events.h"
20 #include "base/pt_params.h"
21 #include "base/pt_returns.h"
22 #include "base/pt_types.h"
23 #include "backend/debugger_executor.h"
24 #include "dispatcher.h"
25 #include "protocol_handler.h"
26 
27 #include "ecmascript/jspandafile/js_pandafile_manager.h"
28 #include "ecmascript/napi/jsnapi_helper.h"
29 namespace panda::ecmascript::tooling {
30 using namespace std::placeholders;
31 
32 using ObjectType = RemoteObject::TypeName;
33 using ObjectSubType = RemoteObject::SubTypeName;
34 using ObjectClassName = RemoteObject::ClassName;
35 using StepperType = SingleStepper::Type;
36 
37 #ifdef DEBUGGER_TEST
38 const std::string DATA_APP_PATH = "/";
39 #else
40 const std::string DATA_APP_PATH = "/data/";
41 #endif
42 
DebuggerImpl(const EcmaVM * vm,ProtocolChannel * channel,RuntimeImpl * runtime)43 DebuggerImpl::DebuggerImpl(const EcmaVM *vm, ProtocolChannel *channel, RuntimeImpl *runtime)
44     : vm_(vm), frontend_(channel), runtime_(runtime)
45 {
46     hooks_ = std::make_unique<JSPtHooks>(this);
47 
48     jsDebugger_ = DebuggerApi::CreateJSDebugger(vm_);
49     DebuggerApi::RegisterHooks(jsDebugger_, hooks_.get());
50 
51     DebuggerExecutor::Initialize(vm_);
52     updaterFunc_ = std::bind(&DebuggerImpl::UpdateScopeObject, this, _1, _2, _3);
53     stepperFunc_ = std::bind(&DebuggerImpl::ClearSingleStepper, this);
54     vm_->GetJsDebuggerManager()->SetLocalScopeUpdater(&updaterFunc_);
55     vm_->GetJsDebuggerManager()->SetStepperFunc(&stepperFunc_);
56 }
57 
~DebuggerImpl()58 DebuggerImpl::~DebuggerImpl()
59 {
60     DebuggerApi::DestroyJSDebugger(jsDebugger_);
61 }
62 
NotifyScriptParsed(ScriptId scriptId,const std::string & fileName,std::string_view entryPoint)63 bool DebuggerImpl::NotifyScriptParsed(ScriptId scriptId, const std::string &fileName, std::string_view entryPoint)
64 {
65 #if !defined(PANDA_TARGET_WINDOWS) && !defined(PANDA_TARGET_MACOS) \
66     && !defined(PANDA_TARGET_ANDROID) && !defined(PANDA_TARGET_IOS)
67     if (fileName.substr(0, DATA_APP_PATH.length()) != DATA_APP_PATH) {
68         LOG_DEBUGGER(DEBUG) << "NotifyScriptParsed: unsupport file: " << fileName;
69         return false;
70     }
71 #endif
72 
73     const JSPandaFile *jsPandaFile = nullptr;
74     JSPandaFileManager::GetInstance()->EnumerateJSPandaFiles([&jsPandaFile, &fileName](
75         const panda::ecmascript::JSPandaFile *pf) {
76         if (fileName == pf->GetJSPandaFileDesc().c_str()) {
77             jsPandaFile = pf;
78             return false;
79         }
80         return true;
81     });
82     if (jsPandaFile == nullptr) {
83         LOG_DEBUGGER(ERROR) << "NotifyScriptParsed: unknown file: " << fileName;
84         return false;
85     }
86 
87     DebugInfoExtractor *extractor = GetExtractor(jsPandaFile);
88     if (extractor == nullptr) {
89         LOG_DEBUGGER(ERROR) << "NotifyScriptParsed: Unsupported file: " << fileName;
90         return false;
91     }
92 
93     auto mainMethodIndex = panda_file::File::EntityId(jsPandaFile->GetMainMethodIndex(entryPoint.data()));
94     const std::string &source = extractor->GetSourceCode(mainMethodIndex);
95     const std::string &url = extractor->GetSourceFile(mainMethodIndex);
96     const uint32_t MIN_SOURCE_CODE_LENGTH = 5;  // maybe return 'ANDA' when source code is empty
97     if (source.size() < MIN_SOURCE_CODE_LENGTH) {
98         LOG_DEBUGGER(ERROR) << "NotifyScriptParsed: invalid file: " << fileName;
99         return false;
100     }
101     // store here for performance of get extractor from url
102     extractors_[url] = extractor;
103 
104     auto scriptFunc = [this](PtScript *script) -> bool {
105         frontend_.ScriptParsed(vm_, *script);
106         return true;
107     };
108     if (MatchScripts(scriptFunc, url, ScriptMatchType::URL)) {
109         LOG_DEBUGGER(WARN) << "NotifyScriptParsed: already loaded: " << url;
110         return false;
111     }
112 
113     // Notify script parsed event
114     std::unique_ptr<PtScript> script = std::make_unique<PtScript>(scriptId, fileName, url, source);
115 
116     frontend_.ScriptParsed(vm_, *script);
117 
118     // Store parsed script in map
119     scripts_[script->GetScriptId()] = std::move(script);
120     return true;
121 }
122 
NotifySingleStep(const JSPtLocation & location)123 bool DebuggerImpl::NotifySingleStep(const JSPtLocation &location)
124 {
125     if (UNLIKELY(pauseOnNextByteCode_)) {
126         if (IsSkipLine(location)) {
127             return false;
128         }
129         pauseOnNextByteCode_ = false;
130         LOG_DEBUGGER(INFO) << "StepComplete: pause on next bytecode";
131         return true;
132     }
133 
134     if (LIKELY(singleStepper_ == nullptr)) {
135         return false;
136     }
137 
138     // step not complete
139     if (!singleStepper_->StepComplete(location.GetBytecodeOffset())) {
140         return false;
141     }
142 
143     // skip unknown file or special line -1
144     if (IsSkipLine(location)) {
145         return false;
146     }
147 
148     singleStepper_.reset();
149     LOG_DEBUGGER(INFO) << "StepComplete: pause on current byte_code";
150     return true;
151 }
152 
IsSkipLine(const JSPtLocation & location)153 bool DebuggerImpl::IsSkipLine(const JSPtLocation &location)
154 {
155     DebugInfoExtractor *extractor = nullptr;
156     const auto *jsPandaFile = location.GetJsPandaFile();
157     auto scriptFunc = [this, &extractor, jsPandaFile](PtScript *) -> bool {
158         extractor = GetExtractor(jsPandaFile);
159         return true;
160     };
161 
162     // In hot reload scenario, use the base js panda file instead
163     const auto &fileName = DebuggerApi::GetBaseJSPandaFile(vm_, jsPandaFile)->GetJSPandaFileDesc();
164     if (!MatchScripts(scriptFunc, fileName.c_str(), ScriptMatchType::FILE_NAME) || extractor == nullptr) {
165         LOG_DEBUGGER(INFO) << "StepComplete: skip unknown file " << fileName.c_str();
166         return true;
167     }
168 
169     auto callbackFunc = [](int32_t line) -> bool {
170         return line == DebugInfoExtractor::SPECIAL_LINE_MARK;
171     };
172     File::EntityId methodId = location.GetMethodId();
173     uint32_t offset = location.GetBytecodeOffset();
174     if (extractor->MatchLineWithOffset(callbackFunc, methodId, offset)) {
175         LOG_DEBUGGER(INFO) << "StepComplete: skip -1";
176         return true;
177     }
178 
179     return false;
180 }
181 
CheckPauseOnException()182 bool DebuggerImpl::CheckPauseOnException()
183 {
184     if (pauseOnException_ == PauseOnExceptionsState::NONE) {
185         return false;
186     }
187     if (pauseOnException_ == PauseOnExceptionsState::UNCAUGHT) {
188         if (DebuggerApi::IsExceptionCaught(vm_)) {
189             return false;
190         }
191     }
192     return true;
193 }
194 
NotifyPaused(std::optional<JSPtLocation> location,PauseReason reason)195 void DebuggerImpl::NotifyPaused(std::optional<JSPtLocation> location, PauseReason reason)
196 {
197     if (reason == EXCEPTION && !CheckPauseOnException()) {
198         return;
199     }
200 
201     Local<JSValueRef> exception = DebuggerApi::GetAndClearException(vm_);
202 
203     std::vector<std::string> hitBreakpoints;
204     if (location.has_value()) {
205         BreakpointDetails detail;
206         DebugInfoExtractor *extractor = nullptr;
207         auto scriptFunc = [this, &location, &detail, &extractor](PtScript *script) -> bool {
208             detail.url_ = script->GetUrl();
209             extractor = GetExtractor(location->GetJsPandaFile());
210             return true;
211         };
212         auto callbackLineFunc = [&detail](int32_t line) -> bool {
213             detail.line_ = line;
214             return true;
215         };
216         auto callbackColumnFunc = [&detail](int32_t column) -> bool {
217             detail.column_ = column;
218             return true;
219         };
220         File::EntityId methodId = location->GetMethodId();
221         uint32_t offset = location->GetBytecodeOffset();
222         // In merge abc scenario, need to use the source file to match to get right url
223         if (!MatchScripts(scriptFunc, location->GetSourceFile(), ScriptMatchType::URL) ||
224             extractor == nullptr || !extractor->MatchLineWithOffset(callbackLineFunc, methodId, offset) ||
225             !extractor->MatchColumnWithOffset(callbackColumnFunc, methodId, offset)) {
226             LOG_DEBUGGER(ERROR) << "NotifyPaused: unknown file " << location->GetSourceFile();
227             return;
228         }
229         hitBreakpoints.emplace_back(BreakpointDetails::ToString(detail));
230     }
231 
232     // Do something cleaning on paused
233     CleanUpOnPaused();
234 
235     // Notify paused event
236     std::vector<std::unique_ptr<CallFrame>> callFrames;
237     if (!GenerateCallFrames(&callFrames)) {
238         LOG_DEBUGGER(ERROR) << "NotifyPaused: GenerateCallFrames failed";
239         return;
240     }
241     tooling::Paused paused;
242     paused.SetCallFrames(std::move(callFrames)).SetReason(reason).SetHitBreakpoints(std::move(hitBreakpoints));
243     if (reason == EXCEPTION && exception->IsError()) {
244         std::unique_ptr<RemoteObject> tmpException = RemoteObject::FromTagged(vm_, exception);
245         paused.SetData(std::move(tmpException));
246     }
247     frontend_.Paused(vm_, paused);
248 
249     frontend_.WaitForDebugger(vm_);
250     DebuggerApi::SetException(vm_, exception);
251 }
252 
NotifyNativeCalling(const void * nativeAddress)253 void DebuggerImpl::NotifyNativeCalling(const void *nativeAddress)
254 {
255     // native calling only after step into should be reported
256     if (singleStepper_ != nullptr &&
257         singleStepper_->GetStepperType() == StepperType::STEP_INTO) {
258         tooling::NativeCalling nativeCalling;
259         nativeCalling.SetNativeAddress(nativeAddress);
260         frontend_.NativeCalling(vm_, nativeCalling);
261         frontend_.WaitForDebugger(vm_);
262     }
263 }
264 
NotifyPendingJobEntry()265 void DebuggerImpl::NotifyPendingJobEntry()
266 {
267     if (singleStepper_ != nullptr) {
268         singleStepper_.reset();
269         pauseOnNextByteCode_ = true;
270     }
271 }
272 
NotifyHandleProtocolCommand()273 void DebuggerImpl::NotifyHandleProtocolCommand()
274 {
275     auto *handler = vm_->GetJsDebuggerManager()->GetDebuggerHandler();
276     handler->ProcessCommand();
277 }
278 
Dispatch(const DispatchRequest & request)279 void DebuggerImpl::DispatcherImpl::Dispatch(const DispatchRequest &request)
280 {
281     static std::unordered_map<std::string, AgentHandler> dispatcherTable {
282         { "enable", &DebuggerImpl::DispatcherImpl::Enable },
283         { "disable", &DebuggerImpl::DispatcherImpl::Disable },
284         { "evaluateOnCallFrame", &DebuggerImpl::DispatcherImpl::EvaluateOnCallFrame },
285         { "getPossibleBreakpoints", &DebuggerImpl::DispatcherImpl::GetPossibleBreakpoints },
286         { "getScriptSource", &DebuggerImpl::DispatcherImpl::GetScriptSource },
287         { "pause", &DebuggerImpl::DispatcherImpl::Pause },
288         { "removeBreakpoint", &DebuggerImpl::DispatcherImpl::RemoveBreakpoint },
289         { "resume", &DebuggerImpl::DispatcherImpl::Resume },
290         { "setAsyncCallStackDepth", &DebuggerImpl::DispatcherImpl::SetAsyncCallStackDepth },
291         { "setBreakpointByUrl", &DebuggerImpl::DispatcherImpl::SetBreakpointByUrl },
292         { "setPauseOnExceptions", &DebuggerImpl::DispatcherImpl::SetPauseOnExceptions },
293         { "stepInto", &DebuggerImpl::DispatcherImpl::StepInto },
294         { "stepOut", &DebuggerImpl::DispatcherImpl::StepOut },
295         { "stepOver", &DebuggerImpl::DispatcherImpl::StepOver },
296         { "setMixedDebugEnabled", &DebuggerImpl::DispatcherImpl::SetMixedDebugEnabled },
297         { "replyNativeCalling", &DebuggerImpl::DispatcherImpl::ReplyNativeCalling }
298     };
299 
300     const std::string &method = request.GetMethod();
301     LOG_DEBUGGER(DEBUG) << "dispatch [" << method << "] to DebuggerImpl";
302     auto entry = dispatcherTable.find(method);
303     if (entry != dispatcherTable.end() && entry->second != nullptr) {
304         (this->*(entry->second))(request);
305     } else {
306         SendResponse(request, DispatchResponse::Fail("Unknown method: " + method));
307     }
308 }
309 
Enable(const DispatchRequest & request)310 void DebuggerImpl::DispatcherImpl::Enable(const DispatchRequest &request)
311 {
312     std::unique_ptr<EnableParams> params = EnableParams::Create(request.GetParams());
313     if (params == nullptr) {
314         SendResponse(request, DispatchResponse::Fail("wrong params"));
315         return;
316     }
317 
318     UniqueDebuggerId id;
319     DispatchResponse response = debugger_->Enable(*params, &id);
320 
321     EnableReturns result(id);
322     SendResponse(request, response, result);
323 }
324 
Disable(const DispatchRequest & request)325 void DebuggerImpl::DispatcherImpl::Disable(const DispatchRequest &request)
326 {
327     DispatchResponse response = debugger_->Disable();
328     SendResponse(request, response);
329 }
330 
EvaluateOnCallFrame(const DispatchRequest & request)331 void DebuggerImpl::DispatcherImpl::EvaluateOnCallFrame(const DispatchRequest &request)
332 {
333     std::unique_ptr<EvaluateOnCallFrameParams> params = EvaluateOnCallFrameParams::Create(request.GetParams());
334     if (params == nullptr) {
335         SendResponse(request, DispatchResponse::Fail("wrong params"));
336         return;
337     }
338     std::unique_ptr<RemoteObject> result1;
339     DispatchResponse response = debugger_->EvaluateOnCallFrame(*params, &result1);
340     if (result1 == nullptr) {
341         SendResponse(request, response);
342         return;
343     }
344 
345     EvaluateOnCallFrameReturns result(std::move(result1));
346     SendResponse(request, response, result);
347 }
348 
GetPossibleBreakpoints(const DispatchRequest & request)349 void DebuggerImpl::DispatcherImpl::GetPossibleBreakpoints(const DispatchRequest &request)
350 {
351     std::unique_ptr<GetPossibleBreakpointsParams> params = GetPossibleBreakpointsParams::Create(request.GetParams());
352     if (params == nullptr) {
353         SendResponse(request, DispatchResponse::Fail("wrong params"));
354         return;
355     }
356     std::vector<std::unique_ptr<BreakLocation>> locations;
357     DispatchResponse response = debugger_->GetPossibleBreakpoints(*params, &locations);
358     GetPossibleBreakpointsReturns result(std::move(locations));
359     SendResponse(request, response, result);
360 }
361 
GetScriptSource(const DispatchRequest & request)362 void DebuggerImpl::DispatcherImpl::GetScriptSource(const DispatchRequest &request)
363 {
364     std::unique_ptr<GetScriptSourceParams> params = GetScriptSourceParams::Create(request.GetParams());
365     if (params == nullptr) {
366         SendResponse(request, DispatchResponse::Fail("wrong params"));
367         return;
368     }
369     std::string source;
370     DispatchResponse response = debugger_->GetScriptSource(*params, &source);
371     GetScriptSourceReturns result(source);
372     SendResponse(request, response, result);
373 }
374 
Pause(const DispatchRequest & request)375 void DebuggerImpl::DispatcherImpl::Pause(const DispatchRequest &request)
376 {
377     DispatchResponse response = debugger_->Pause();
378     SendResponse(request, response);
379 }
380 
RemoveBreakpoint(const DispatchRequest & request)381 void DebuggerImpl::DispatcherImpl::RemoveBreakpoint(const DispatchRequest &request)
382 {
383     std::unique_ptr<RemoveBreakpointParams> params = RemoveBreakpointParams::Create(request.GetParams());
384     if (params == nullptr) {
385         SendResponse(request, DispatchResponse::Fail("wrong params"));
386         return;
387     }
388     DispatchResponse response = debugger_->RemoveBreakpoint(*params);
389     SendResponse(request, response);
390 }
391 
Resume(const DispatchRequest & request)392 void DebuggerImpl::DispatcherImpl::Resume(const DispatchRequest &request)
393 {
394     std::unique_ptr<ResumeParams> params = ResumeParams::Create(request.GetParams());
395     if (params == nullptr) {
396         SendResponse(request, DispatchResponse::Fail("wrong params"));
397         return;
398     }
399     DispatchResponse response = debugger_->Resume(*params);
400     SendResponse(request, response);
401 }
402 
SetAsyncCallStackDepth(const DispatchRequest & request)403 void DebuggerImpl::DispatcherImpl::SetAsyncCallStackDepth(const DispatchRequest &request)
404 {
405     DispatchResponse response = debugger_->SetAsyncCallStackDepth();
406     SendResponse(request, response);
407 }
408 
SetBreakpointByUrl(const DispatchRequest & request)409 void DebuggerImpl::DispatcherImpl::SetBreakpointByUrl(const DispatchRequest &request)
410 {
411     std::unique_ptr<SetBreakpointByUrlParams> params = SetBreakpointByUrlParams::Create(request.GetParams());
412     if (params == nullptr) {
413         SendResponse(request, DispatchResponse::Fail("wrong params"));
414         return;
415     }
416 
417     std::string out_id;
418     std::vector<std::unique_ptr<Location>> outLocations;
419     DispatchResponse response = debugger_->SetBreakpointByUrl(*params, &out_id, &outLocations);
420     SetBreakpointByUrlReturns result(out_id, std::move(outLocations));
421     SendResponse(request, response, result);
422 }
423 
SetPauseOnExceptions(const DispatchRequest & request)424 void DebuggerImpl::DispatcherImpl::SetPauseOnExceptions(const DispatchRequest &request)
425 {
426     std::unique_ptr<SetPauseOnExceptionsParams> params = SetPauseOnExceptionsParams::Create(request.GetParams());
427     if (params == nullptr) {
428         SendResponse(request, DispatchResponse::Fail("wrong params"));
429         return;
430     }
431 
432     DispatchResponse response = debugger_->SetPauseOnExceptions(*params);
433     SendResponse(request, response);
434 }
435 
StepInto(const DispatchRequest & request)436 void DebuggerImpl::DispatcherImpl::StepInto(const DispatchRequest &request)
437 {
438     std::unique_ptr<StepIntoParams> params = StepIntoParams::Create(request.GetParams());
439     if (params == nullptr) {
440         SendResponse(request, DispatchResponse::Fail("wrong params"));
441         return;
442     }
443     DispatchResponse response = debugger_->StepInto(*params);
444     SendResponse(request, response);
445 }
446 
StepOut(const DispatchRequest & request)447 void DebuggerImpl::DispatcherImpl::StepOut(const DispatchRequest &request)
448 {
449     DispatchResponse response = debugger_->StepOut();
450     SendResponse(request, response);
451 }
452 
StepOver(const DispatchRequest & request)453 void DebuggerImpl::DispatcherImpl::StepOver(const DispatchRequest &request)
454 {
455     std::unique_ptr<StepOverParams> params = StepOverParams::Create(request.GetParams());
456     if (params == nullptr) {
457         SendResponse(request, DispatchResponse::Fail("wrong params"));
458         return;
459     }
460     DispatchResponse response = debugger_->StepOver(*params);
461     SendResponse(request, response);
462 }
463 
SetMixedDebugEnabled(const DispatchRequest & request)464 void DebuggerImpl::DispatcherImpl::SetMixedDebugEnabled(const DispatchRequest &request)
465 {
466     std::unique_ptr<SetMixedDebugParams> params = SetMixedDebugParams::Create(request.GetParams());
467     DispatchResponse response = debugger_->SetMixedDebugEnabled(*params);
468     SendResponse(request, response);
469 }
470 
ReplyNativeCalling(const DispatchRequest & request)471 void DebuggerImpl::DispatcherImpl::ReplyNativeCalling(const DispatchRequest &request)
472 {
473     std::unique_ptr<ReplyNativeCallingParams> params = ReplyNativeCallingParams::Create(request.GetParams());
474     DispatchResponse response = debugger_->ReplyNativeCalling(*params);
475     SendResponse(request, response);
476 }
477 
SetBlackboxPatterns(const DispatchRequest & request)478 void DebuggerImpl::DispatcherImpl::SetBlackboxPatterns(const DispatchRequest &request)
479 {
480     DispatchResponse response = debugger_->SetBlackboxPatterns();
481     SendResponse(request, response);
482 }
483 
AllowNotify(const EcmaVM * vm) const484 bool DebuggerImpl::Frontend::AllowNotify(const EcmaVM *vm) const
485 {
486     return vm->GetJsDebuggerManager()->IsDebugMode() && channel_ != nullptr;
487 }
488 
BreakpointResolved(const EcmaVM * vm)489 void DebuggerImpl::Frontend::BreakpointResolved(const EcmaVM *vm)
490 {
491     if (!AllowNotify(vm)) {
492         return;
493     }
494 
495     tooling::BreakpointResolved breakpointResolved;
496     channel_->SendNotification(breakpointResolved);
497 }
498 
Paused(const EcmaVM * vm,const tooling::Paused & paused)499 void DebuggerImpl::Frontend::Paused(const EcmaVM *vm, const tooling::Paused &paused)
500 {
501     if (!AllowNotify(vm)) {
502         return;
503     }
504 
505     channel_->SendNotification(paused);
506 }
507 
NativeCalling(const EcmaVM * vm,const tooling::NativeCalling & nativeCalling)508 void DebuggerImpl::Frontend::NativeCalling(const EcmaVM *vm, const tooling::NativeCalling &nativeCalling)
509 {
510     if (!AllowNotify(vm)) {
511         return;
512     }
513 
514     channel_->SendNotification(nativeCalling);
515 }
516 
Resumed(const EcmaVM * vm)517 void DebuggerImpl::Frontend::Resumed(const EcmaVM *vm)
518 {
519     if (!AllowNotify(vm)) {
520         return;
521     }
522 
523     channel_->RunIfWaitingForDebugger();
524     tooling::Resumed resumed;
525     channel_->SendNotification(resumed);
526 }
527 
ScriptFailedToParse(const EcmaVM * vm)528 void DebuggerImpl::Frontend::ScriptFailedToParse(const EcmaVM *vm)
529 {
530     if (!AllowNotify(vm)) {
531         return;
532     }
533 
534     tooling::ScriptFailedToParse scriptFailedToParse;
535     channel_->SendNotification(scriptFailedToParse);
536 }
537 
ScriptParsed(const EcmaVM * vm,const PtScript & script)538 void DebuggerImpl::Frontend::ScriptParsed(const EcmaVM *vm, const PtScript &script)
539 {
540     if (!AllowNotify(vm)) {
541         return;
542     }
543 
544     tooling::ScriptParsed scriptParsed;
545     scriptParsed.SetScriptId(script.GetScriptId())
546         .SetUrl(script.GetUrl())
547         .SetStartLine(0)
548         .SetStartColumn(0)
549         .SetEndLine(script.GetEndLine())
550         .SetEndColumn(0)
551         .SetExecutionContextId(0)
552         .SetHash(script.GetHash());
553 
554     channel_->SendNotification(scriptParsed);
555 }
556 
WaitForDebugger(const EcmaVM * vm)557 void DebuggerImpl::Frontend::WaitForDebugger(const EcmaVM *vm)
558 {
559     if (!AllowNotify(vm)) {
560         return;
561     }
562 
563     channel_->WaitForDebugger();
564 }
565 
RunIfWaitingForDebugger(const EcmaVM * vm)566 void DebuggerImpl::Frontend::RunIfWaitingForDebugger(const EcmaVM *vm)
567 {
568     if (!AllowNotify(vm)) {
569         return;
570     }
571 
572     channel_->RunIfWaitingForDebugger();
573 }
574 
Enable(const EnableParams & params,UniqueDebuggerId * id)575 DispatchResponse DebuggerImpl::Enable([[maybe_unused]] const EnableParams &params, UniqueDebuggerId *id)
576 {
577     ASSERT(id != nullptr);
578     *id = 0;
579     vm_->GetJsDebuggerManager()->SetDebugMode(true);
580     for (auto &script : scripts_) {
581         frontend_.ScriptParsed(vm_, *script.second);
582     }
583     return DispatchResponse::Ok();
584 }
585 
Disable()586 DispatchResponse DebuggerImpl::Disable()
587 {
588     DebuggerApi::RemoveAllBreakpoints(jsDebugger_);
589     frontend_.RunIfWaitingForDebugger(vm_);
590     vm_->GetJsDebuggerManager()->SetDebugMode(false);
591     return DispatchResponse::Ok();
592 }
593 
EvaluateOnCallFrame(const EvaluateOnCallFrameParams & params,std::unique_ptr<RemoteObject> * result)594 DispatchResponse DebuggerImpl::EvaluateOnCallFrame(const EvaluateOnCallFrameParams &params,
595                                                    std::unique_ptr<RemoteObject> *result)
596 {
597     CallFrameId callFrameId = params.GetCallFrameId();
598     const std::string &expression = params.GetExpression();
599     if (callFrameId < 0 || callFrameId >= static_cast<CallFrameId>(callFrameHandlers_.size())) {
600         return DispatchResponse::Fail("Invalid callFrameId.");
601     }
602 
603     std::string dest;
604     if (!DecodeAndCheckBase64(expression, dest)) {
605         LOG_DEBUGGER(ERROR) << "EvaluateValue: base64 decode failed";
606         auto ret = CmptEvaluateValue(callFrameId, expression, result);
607         if (ret.has_value()) {
608             LOG_DEBUGGER(ERROR) << "Evaluate fail, expression: " << expression;
609         }
610         return DispatchResponse::Create(ret);
611     }
612 
613     auto funcRef = DebuggerApi::GenerateFuncFromBuffer(vm_, dest.data(), dest.size(),
614         JSPandaFile::ENTRY_FUNCTION_NAME);
615     auto res = DebuggerApi::EvaluateViaFuncCall(const_cast<EcmaVM *>(vm_), funcRef,
616         callFrameHandlers_[callFrameId]);
617     if (vm_->GetJSThread()->HasPendingException()) {
618         LOG_DEBUGGER(ERROR) << "EvaluateValue: has pending exception";
619         std::string msg;
620         DebuggerApi::HandleUncaughtException(vm_, msg);
621         *result = RemoteObject::FromTagged(vm_,
622             Exception::EvalError(vm_, StringRef::NewFromUtf8(vm_, msg.data())));
623         return DispatchResponse::Fail(msg);
624     }
625 
626     *result = RemoteObject::FromTagged(vm_, res);
627     runtime_->CacheObjectIfNeeded(res, (*result).get());
628     return DispatchResponse::Ok();
629 }
630 
GetPossibleBreakpoints(const GetPossibleBreakpointsParams & params,std::vector<std::unique_ptr<BreakLocation>> * locations)631 DispatchResponse DebuggerImpl::GetPossibleBreakpoints(const GetPossibleBreakpointsParams &params,
632                                                       std::vector<std::unique_ptr<BreakLocation>> *locations)
633 {
634     Location *start = params.GetStart();
635     auto iter = scripts_.find(start->GetScriptId());
636     if (iter == scripts_.end()) {
637         return DispatchResponse::Fail("Unknown file name.");
638     }
639     const std::string &url = iter->second->GetUrl();
640     DebugInfoExtractor *extractor = GetExtractor(url);
641     if (extractor == nullptr) {
642         LOG_DEBUGGER(ERROR) << "GetPossibleBreakpoints: extractor is null";
643         return DispatchResponse::Fail("Unknown file name.");
644     }
645 
646     int32_t line = start->GetLine();
647     int32_t column = start->GetColumn();
648     auto callbackFunc = [](const JSPtLocation &) -> bool {
649         return true;
650     };
651     if (extractor->MatchWithLocation(callbackFunc, line, column, url)) {
652         std::unique_ptr<BreakLocation> location = std::make_unique<BreakLocation>();
653         location->SetScriptId(start->GetScriptId()).SetLine(line).SetColumn(column);
654         locations->emplace_back(std::move(location));
655     }
656     return DispatchResponse::Ok();
657 }
658 
GetScriptSource(const GetScriptSourceParams & params,std::string * source)659 DispatchResponse DebuggerImpl::GetScriptSource(const GetScriptSourceParams &params, std::string *source)
660 {
661     ScriptId scriptId = params.GetScriptId();
662     auto iter = scripts_.find(scriptId);
663     if (iter == scripts_.end()) {
664         *source = "";
665         return DispatchResponse::Fail("unknown script id: " + std::to_string(scriptId));
666     }
667     *source = iter->second->GetScriptSource();
668 
669     return DispatchResponse::Ok();
670 }
671 
Pause()672 DispatchResponse DebuggerImpl::Pause()
673 {
674     pauseOnNextByteCode_ = true;
675     return DispatchResponse::Ok();
676 }
677 
RemoveBreakpoint(const RemoveBreakpointParams & params)678 DispatchResponse DebuggerImpl::RemoveBreakpoint(const RemoveBreakpointParams &params)
679 {
680     std::string id = params.GetBreakpointId();
681     LOG_DEBUGGER(INFO) << "RemoveBreakpoint: " << id;
682     BreakpointDetails metaData{};
683     if (!BreakpointDetails::ParseBreakpointId(id, &metaData)) {
684         return DispatchResponse::Fail("Parse breakpoint id failed");
685     }
686     DebugInfoExtractor *extractor = GetExtractor(metaData.url_);
687     if (extractor == nullptr) {
688         LOG_DEBUGGER(ERROR) << "RemoveBreakpoint: extractor is null";
689         return DispatchResponse::Fail("Unknown file name.");
690     }
691 
692     auto scriptFunc = [](PtScript *) -> bool {
693         return true;
694     };
695     if (!MatchScripts(scriptFunc, metaData.url_, ScriptMatchType::URL)) {
696         LOG_DEBUGGER(ERROR) << "RemoveBreakpoint: Unknown url: " << metaData.url_;
697         return DispatchResponse::Fail("Unknown file name.");
698     }
699 
700     auto callbackFunc = [this](const JSPtLocation &location) -> bool {
701         LOG_DEBUGGER(INFO) << "remove breakpoint location: " << location.ToString();
702         return DebuggerApi::RemoveBreakpoint(jsDebugger_, location);
703     };
704     if (!extractor->MatchWithLocation(callbackFunc, metaData.line_, metaData.column_, metaData.url_)) {
705         LOG_DEBUGGER(ERROR) << "failed to remove breakpoint location number: "
706             << metaData.line_ << ":" << metaData.column_;
707         return DispatchResponse::Fail("Breakpoint not found.");
708     }
709 
710     LOG_DEBUGGER(INFO) << "remove breakpoint line number:" << metaData.line_;
711     return DispatchResponse::Ok();
712 }
713 
Resume(const ResumeParams & params)714 DispatchResponse DebuggerImpl::Resume([[maybe_unused]] const ResumeParams &params)
715 {
716     frontend_.Resumed(vm_);
717     singleStepper_.reset();
718     return DispatchResponse::Ok();
719 }
720 
SetAsyncCallStackDepth()721 DispatchResponse DebuggerImpl::SetAsyncCallStackDepth()
722 {
723     return DispatchResponse::Fail("SetAsyncCallStackDepth not support now");
724 }
725 
SetBreakpointByUrl(const SetBreakpointByUrlParams & params,std::string * outId,std::vector<std::unique_ptr<Location>> * outLocations)726 DispatchResponse DebuggerImpl::SetBreakpointByUrl(const SetBreakpointByUrlParams &params,
727                                                   std::string *outId,
728                                                   std::vector<std::unique_ptr<Location>> *outLocations)
729 {
730     if (!vm_->GetJsDebuggerManager()->IsDebugMode()) {
731         return DispatchResponse::Fail("SetBreakpointByUrl: debugger agent is not enabled");
732     }
733     const std::string &url = params.GetUrl();
734     int32_t lineNumber = params.GetLine();
735     int32_t columnNumber = params.GetColumn();
736     auto condition = params.HasCondition() ? params.GetCondition() : std::optional<std::string> {};
737 
738     DebugInfoExtractor *extractor = GetExtractor(url);
739     if (extractor == nullptr) {
740         LOG_DEBUGGER(ERROR) << "SetBreakpointByUrl: extractor is null";
741         return DispatchResponse::Fail("Unknown file name.");
742     }
743 
744     ScriptId scriptId;
745     std::string fileName;
746     auto scriptFunc = [&scriptId, &fileName](PtScript *script) -> bool {
747         scriptId = script->GetScriptId();
748         fileName = script->GetFileName();
749         return true;
750     };
751     if (!MatchScripts(scriptFunc, url, ScriptMatchType::URL)) {
752         LOG_DEBUGGER(ERROR) << "SetBreakpointByUrl: Unknown url: " << url;
753         return DispatchResponse::Fail("Unknown file name.");
754     }
755 
756     auto callbackFunc = [this, &condition](const JSPtLocation &location) -> bool {
757         LOG_DEBUGGER(INFO) << "set breakpoint location: " << location.ToString();
758         Local<FunctionRef> condFuncRef = FunctionRef::Undefined(vm_);
759         if (condition.has_value() && !condition.value().empty()) {
760             std::string dest;
761             if (!DecodeAndCheckBase64(condition.value(), dest)) {
762                 LOG_DEBUGGER(ERROR) << "SetBreakpointByUrl: base64 decode failed";
763                 return false;
764             }
765             condFuncRef = DebuggerApi::GenerateFuncFromBuffer(vm_, dest.data(), dest.size(),
766                 JSPandaFile::ENTRY_FUNCTION_NAME);
767             if (condFuncRef->IsUndefined()) {
768                 LOG_DEBUGGER(ERROR) << "SetBreakpointByUrl: generate function failed";
769                 return false;
770             }
771         }
772         return DebuggerApi::SetBreakpoint(jsDebugger_, location, condFuncRef);
773     };
774     if (!extractor->MatchWithLocation(callbackFunc, lineNumber, columnNumber, url)) {
775         LOG_DEBUGGER(ERROR) << "failed to set breakpoint location number: " << lineNumber << ":" << columnNumber;
776         return DispatchResponse::Fail("Breakpoint not found.");
777     }
778 
779     BreakpointDetails metaData{lineNumber, 0, url};
780     *outId = BreakpointDetails::ToString(metaData);
781     *outLocations = std::vector<std::unique_ptr<Location>>();
782     std::unique_ptr<Location> location = std::make_unique<Location>();
783     location->SetScriptId(scriptId).SetLine(lineNumber).SetColumn(0);
784     outLocations->emplace_back(std::move(location));
785 
786     return DispatchResponse::Ok();
787 }
788 
SetPauseOnExceptions(const SetPauseOnExceptionsParams & params)789 DispatchResponse DebuggerImpl::SetPauseOnExceptions(const SetPauseOnExceptionsParams &params)
790 {
791     pauseOnException_ = params.GetState();
792     return DispatchResponse::Ok();
793 }
794 
StepInto(const StepIntoParams & params)795 DispatchResponse DebuggerImpl::StepInto([[maybe_unused]] const StepIntoParams &params)
796 {
797     if (!vm_->GetJsDebuggerManager()->IsDebugMode()) {
798         return DispatchResponse::Fail("Can only perform operation while paused");
799     }
800     singleStepper_ = SingleStepper::GetStepIntoStepper(vm_);
801     if (singleStepper_ == nullptr) {
802         LOG_DEBUGGER(ERROR) << "StepInto: singleStepper is null";
803         return DispatchResponse::Fail("Failed to StepInto");
804     }
805     frontend_.Resumed(vm_);
806     return DispatchResponse::Ok();
807 }
808 
StepOut()809 DispatchResponse DebuggerImpl::StepOut()
810 {
811     if (!vm_->GetJsDebuggerManager()->IsDebugMode()) {
812         return DispatchResponse::Fail("Can only perform operation while paused");
813     }
814     singleStepper_ = SingleStepper::GetStepOutStepper(vm_);
815     if (singleStepper_ == nullptr) {
816         LOG_DEBUGGER(ERROR) << "StepOut: singleStepper is null";
817         return DispatchResponse::Fail("Failed to StepOut");
818     }
819     frontend_.Resumed(vm_);
820     return DispatchResponse::Ok();
821 }
822 
StepOver(const StepOverParams & params)823 DispatchResponse DebuggerImpl::StepOver([[maybe_unused]] const StepOverParams &params)
824 {
825     if (!vm_->GetJsDebuggerManager()->IsDebugMode()) {
826         return DispatchResponse::Fail("Can only perform operation while paused");
827     }
828     singleStepper_ = SingleStepper::GetStepOverStepper(vm_);
829     if (singleStepper_ == nullptr) {
830         LOG_DEBUGGER(ERROR) << "StepOver: singleStepper is null";
831         return DispatchResponse::Fail("Failed to StepOver");
832     }
833     frontend_.Resumed(vm_);
834     return DispatchResponse::Ok();
835 }
836 
SetBlackboxPatterns()837 DispatchResponse DebuggerImpl::SetBlackboxPatterns()
838 {
839     return DispatchResponse::Fail("SetBlackboxPatterns not support now");
840 }
841 
SetMixedDebugEnabled(const SetMixedDebugParams & params)842 DispatchResponse DebuggerImpl::SetMixedDebugEnabled([[maybe_unused]] const SetMixedDebugParams &params)
843 {
844     vm_->GetJsDebuggerManager()->SetMixedDebugEnabled(params.GetEnabled());
845     return DispatchResponse::Ok();
846 }
847 
ReplyNativeCalling(const ReplyNativeCallingParams & params)848 DispatchResponse DebuggerImpl::ReplyNativeCalling([[maybe_unused]] const ReplyNativeCallingParams &params)
849 {
850     frontend_.Resumed(vm_);
851     if (params.GetUserCode()) {
852         singleStepper_.reset();
853     }
854     return DispatchResponse::Ok();
855 }
856 
CleanUpOnPaused()857 void DebuggerImpl::CleanUpOnPaused()
858 {
859     runtime_->curObjectId_ = 0;
860     runtime_->properties_.clear();
861 
862     callFrameHandlers_.clear();
863     scopeObjects_.clear();
864 }
865 
Trim(const std::string & str)866 std::string DebuggerImpl::Trim(const std::string &str)
867 {
868     std::string ret = str;
869     // If ret has only ' ', remove all charactors.
870     ret.erase(ret.find_last_not_of(' ') + 1);
871     // If ret has only ' ', remove all charactors.
872     ret.erase(0, ret.find_first_not_of(' '));
873     return ret;
874 }
875 
GetExtractor(const JSPandaFile * jsPandaFile)876 DebugInfoExtractor *DebuggerImpl::GetExtractor(const JSPandaFile *jsPandaFile)
877 {
878     return JSPandaFileManager::GetInstance()->GetJSPtExtractor(jsPandaFile);
879 }
880 
881 // mainly used for breakpoints to match location
GetExtractor(const std::string & url)882 DebugInfoExtractor *DebuggerImpl::GetExtractor(const std::string &url)
883 {
884     // match patch file first if it contains diff for the url, and currently only support the file
885     // specified by the url change as a whole
886     auto *extractor = DebuggerApi::GetPatchExtractor(vm_, url);
887     if (extractor != nullptr) {
888         return extractor;
889     }
890 
891     auto iter = extractors_.find(url);
892     if (iter == extractors_.end()) {
893         return nullptr;
894     }
895 
896     return iter->second;
897 }
898 
GenerateCallFrames(std::vector<std::unique_ptr<CallFrame>> * callFrames)899 bool DebuggerImpl::GenerateCallFrames(std::vector<std::unique_ptr<CallFrame>> *callFrames)
900 {
901     CallFrameId callFrameId = 0;
902     auto walkerFunc = [this, &callFrameId, &callFrames](const FrameHandler *frameHandler) -> StackState {
903         if (DebuggerApi::IsNativeMethod(frameHandler)) {
904             LOG_DEBUGGER(INFO) << "GenerateCallFrames: Skip CFrame and Native method";
905             return StackState::CONTINUE;
906         }
907         std::unique_ptr<CallFrame> callFrame = std::make_unique<CallFrame>();
908         if (!GenerateCallFrame(callFrame.get(), frameHandler, callFrameId)) {
909             if (callFrameId == 0) {
910                 return StackState::FAILED;
911             }
912         } else {
913             SaveCallFrameHandler(frameHandler);
914             callFrames->emplace_back(std::move(callFrame));
915             callFrameId++;
916         }
917         return StackState::CONTINUE;
918     };
919     return DebuggerApi::StackWalker(vm_, walkerFunc);
920 }
921 
SaveCallFrameHandler(const FrameHandler * frameHandler)922 void DebuggerImpl::SaveCallFrameHandler(const FrameHandler *frameHandler)
923 {
924     auto handlerPtr = DebuggerApi::NewFrameHandler(vm_);
925     *handlerPtr = *frameHandler;
926     callFrameHandlers_.emplace_back(handlerPtr);
927 }
928 
GenerateCallFrame(CallFrame * callFrame,const FrameHandler * frameHandler,CallFrameId callFrameId)929 bool DebuggerImpl::GenerateCallFrame(CallFrame *callFrame,
930     const FrameHandler *frameHandler, CallFrameId callFrameId)
931 {
932     if (!frameHandler->HasFrame()) {
933         return false;
934     }
935     Method *method = DebuggerApi::GetMethod(frameHandler);
936     auto methodId = method->GetMethodId();
937     const JSPandaFile *jsPandaFile = method->GetJSPandaFile();
938     DebugInfoExtractor *extractor = GetExtractor(jsPandaFile);
939     if (extractor == nullptr) {
940         LOG_DEBUGGER(ERROR) << "GenerateCallFrame: extractor is null";
941         return false;
942     }
943 
944     // functionName
945     std::string functionName = method->ParseFunctionName();
946 
947     // location
948     std::unique_ptr<Location> location = std::make_unique<Location>();
949     std::string url = extractor->GetSourceFile(methodId);
950     auto scriptFunc = [&location](PtScript *script) -> bool {
951         location->SetScriptId(script->GetScriptId());
952         return true;
953     };
954     if (!MatchScripts(scriptFunc, url, ScriptMatchType::URL)) {
955         LOG_DEBUGGER(ERROR) << "GenerateCallFrame: Unknown url: " << url;
956         return false;
957     }
958     auto callbackLineFunc = [&location](int32_t line) -> bool {
959         location->SetLine(line);
960         return true;
961     };
962     auto callbackColumnFunc = [&location](int32_t column) -> bool {
963         location->SetColumn(column);
964         return true;
965     };
966     if (!extractor->MatchLineWithOffset(callbackLineFunc, methodId, DebuggerApi::GetBytecodeOffset(frameHandler)) ||
967         !extractor->MatchColumnWithOffset(callbackColumnFunc, methodId, DebuggerApi::GetBytecodeOffset(frameHandler))) {
968         LOG_DEBUGGER(ERROR) << "GenerateCallFrame: unknown offset: " << DebuggerApi::GetBytecodeOffset(frameHandler);
969         return false;
970     }
971 
972     // scopeChain & this
973     std::unique_ptr<RemoteObject> thisObj = std::make_unique<RemoteObject>();
974     thisObj->SetType(ObjectType::Undefined);
975 
976     std::vector<std::unique_ptr<Scope>> scopeChain;
977     scopeChain.emplace_back(GetLocalScopeChain(frameHandler, &thisObj));
978     if (jsPandaFile != nullptr && !jsPandaFile->IsBundlePack() && jsPandaFile->IsNewVersion()) {
979         JSTaggedValue currentModule = DebuggerApi::GetCurrentModule(vm_);
980         if (currentModule.IsSourceTextModule()) {
981             scopeChain.emplace_back(GetModuleScopeChain());
982         }
983     }
984     scopeChain.emplace_back(GetGlobalScopeChain());
985 
986     callFrame->SetCallFrameId(callFrameId)
987         .SetFunctionName(functionName)
988         .SetLocation(std::move(location))
989         .SetUrl(url)
990         .SetScopeChain(std::move(scopeChain))
991         .SetThis(std::move(thisObj));
992     return true;
993 }
994 
GetLocalScopeChain(const FrameHandler * frameHandler,std::unique_ptr<RemoteObject> * thisObj)995 std::unique_ptr<Scope> DebuggerImpl::GetLocalScopeChain(const FrameHandler *frameHandler,
996     std::unique_ptr<RemoteObject> *thisObj)
997 {
998     auto localScope = std::make_unique<Scope>();
999 
1000     Method *method = DebuggerApi::GetMethod(frameHandler);
1001     auto methodId = method->GetMethodId();
1002     const JSPandaFile *jsPandaFile = method->GetJSPandaFile();
1003     DebugInfoExtractor *extractor = GetExtractor(jsPandaFile);
1004     if (extractor == nullptr) {
1005         LOG_DEBUGGER(ERROR) << "GetScopeChain: extractor is null";
1006         return localScope;
1007     }
1008 
1009     std::unique_ptr<RemoteObject> local = std::make_unique<RemoteObject>();
1010     Local<ObjectRef> localObj = ObjectRef::New(vm_);
1011     local->SetType(ObjectType::Object)
1012         .SetObjectId(runtime_->curObjectId_)
1013         .SetClassName(ObjectClassName::Object)
1014         .SetDescription(RemoteObject::ObjectDescription);
1015     auto *sp = DebuggerApi::GetSp(frameHandler);
1016     scopeObjects_[sp] = runtime_->curObjectId_;
1017     runtime_->properties_[runtime_->curObjectId_++] = Global<JSValueRef>(vm_, localObj);
1018 
1019     Local<JSValueRef> thisVal = JSValueRef::Undefined(vm_);
1020     GetLocalVariables(frameHandler, methodId, jsPandaFile, thisVal, localObj);
1021     *thisObj = RemoteObject::FromTagged(vm_, thisVal);
1022     runtime_->CacheObjectIfNeeded(thisVal, (*thisObj).get());
1023 
1024     const LineNumberTable &lines = extractor->GetLineNumberTable(methodId);
1025     std::unique_ptr<Location> startLoc = std::make_unique<Location>();
1026     std::unique_ptr<Location> endLoc = std::make_unique<Location>();
1027     auto scriptFunc = [&startLoc, &endLoc, lines](PtScript *script) -> bool {
1028         startLoc->SetScriptId(script->GetScriptId())
1029             .SetLine(lines.front().line)
1030             .SetColumn(0);
1031         endLoc->SetScriptId(script->GetScriptId())
1032             .SetLine(lines.back().line + 1)
1033             .SetColumn(0);
1034         return true;
1035     };
1036     if (MatchScripts(scriptFunc, extractor->GetSourceFile(methodId), ScriptMatchType::URL)) {
1037         localScope->SetType(Scope::Type::Local())
1038             .SetObject(std::move(local))
1039             .SetStartLocation(std::move(startLoc))
1040             .SetEndLocation(std::move(endLoc));
1041     }
1042 
1043     return localScope;
1044 }
1045 
GetModuleScopeChain()1046 std::unique_ptr<Scope> DebuggerImpl::GetModuleScopeChain()
1047 {
1048     auto moduleScope = std::make_unique<Scope>();
1049 
1050     std::unique_ptr<RemoteObject> module = std::make_unique<RemoteObject>();
1051     Local<ObjectRef> moduleObj = ObjectRef::New(vm_);
1052     module->SetType(ObjectType::Object)
1053         .SetObjectId(runtime_->curObjectId_)
1054         .SetClassName(ObjectClassName::Object)
1055         .SetDescription(RemoteObject::ObjectDescription);
1056     moduleScope->SetType(Scope::Type::Module()).SetObject(std::move(module));
1057     runtime_->properties_[runtime_->curObjectId_++] = Global<JSValueRef>(vm_, moduleObj);
1058     JSThread *thread = vm_->GetJSThread();
1059     DebuggerApi::GetModuleVariables(vm_, moduleObj, thread);
1060     return moduleScope;
1061 }
1062 
GetLocalVariables(const FrameHandler * frameHandler,panda_file::File::EntityId methodId,const JSPandaFile * jsPandaFile,Local<JSValueRef> & thisVal,Local<ObjectRef> & localObj)1063 void DebuggerImpl::GetLocalVariables(const FrameHandler *frameHandler, panda_file::File::EntityId methodId,
1064     const JSPandaFile *jsPandaFile, Local<JSValueRef> &thisVal, Local<ObjectRef> &localObj)
1065 {
1066     auto *extractor = GetExtractor(jsPandaFile);
1067     Local<JSValueRef> value = JSValueRef::Undefined(vm_);
1068     // in case of arrow function, which doesn't have this in local variable table
1069     bool hasThis = false;
1070     for (const auto &[varName, regIndex] : extractor->GetLocalVariableTable(methodId)) {
1071         value = DebuggerApi::GetVRegValue(vm_, frameHandler, regIndex);
1072         if (varName == "4newTarget" || varName == "0this" || varName == "0newTarget" || varName == "0funcObj") {
1073             continue;
1074         }
1075 
1076         if (varName == "this") {
1077             thisVal = value;
1078             hasThis = true;
1079             continue;
1080         }
1081         Local<JSValueRef> name = JSValueRef::Undefined(vm_);
1082         if (varName == "4funcObj") {
1083             if (value->IsFunction()) {
1084                 auto funcName = Local<FunctionRef>(value)->GetName(vm_)->ToString();
1085                 name = StringRef::NewFromUtf8(vm_, funcName.c_str());
1086             } else {
1087                 continue;
1088             }
1089         } else {
1090             name = StringRef::NewFromUtf8(vm_, varName.c_str());
1091         }
1092         PropertyAttribute descriptor(value, true, true, true);
1093         localObj->DefineProperty(vm_, name, descriptor);
1094     }
1095 
1096     // closure variables are stored in env
1097     JSTaggedValue env = DebuggerApi::GetEnv(frameHandler);
1098     if (env.IsTaggedArray() && DebuggerApi::GetBytecodeOffset(frameHandler) != 0) {
1099         LexicalEnv *lexEnv = LexicalEnv::Cast(env.GetTaggedObject());
1100         if (lexEnv->GetScopeInfo().IsHole()) {
1101             return;
1102         }
1103         auto ptr = JSNativePointer::Cast(lexEnv->GetScopeInfo().GetTaggedObject())->GetExternalPointer();
1104         auto *scopeDebugInfo = reinterpret_cast<ScopeDebugInfo *>(ptr);
1105         JSThread *thread = vm_->GetJSThread();
1106         for (const auto &[varName, slot] : scopeDebugInfo->scopeInfo) {
1107             // skip possible duplicate variables both in local variable table and env
1108             if (varName == "4newTarget") {
1109                 continue;
1110             }
1111             value = JSNApiHelper::ToLocal<JSValueRef>(
1112                 JSHandle<JSTaggedValue>(thread, lexEnv->GetProperties(slot)));
1113             if (varName == "this") {
1114                 if (!hasThis) {
1115                     thisVal = value;
1116                 }
1117                 continue;
1118             }
1119             Local<JSValueRef> name = StringRef::NewFromUtf8(vm_, varName.c_str());
1120             PropertyAttribute descriptor(value, true, true, true);
1121             localObj->DefineProperty(vm_, name, descriptor);
1122         }
1123     }
1124 }
1125 
GetGlobalScopeChain()1126 std::unique_ptr<Scope> DebuggerImpl::GetGlobalScopeChain()
1127 {
1128     auto globalScope = std::make_unique<Scope>();
1129 
1130     std::unique_ptr<RemoteObject> global = std::make_unique<RemoteObject>();
1131     global->SetType(ObjectType::Object)
1132         .SetObjectId(runtime_->curObjectId_)
1133         .SetClassName(ObjectClassName::Global)
1134         .SetDescription(RemoteObject::GlobalDescription);
1135     globalScope->SetType(Scope::Type::Global()).SetObject(std::move(global));
1136     runtime_->properties_[runtime_->curObjectId_++] = Global<JSValueRef>(vm_, JSNApi::GetGlobalObject(vm_));
1137     return globalScope;
1138 }
1139 
UpdateScopeObject(const FrameHandler * frameHandler,std::string_view varName,Local<JSValueRef> newVal)1140 void DebuggerImpl::UpdateScopeObject(const FrameHandler *frameHandler,
1141     std::string_view varName, Local<JSValueRef> newVal)
1142 {
1143     auto *sp = DebuggerApi::GetSp(frameHandler);
1144     auto iter = scopeObjects_.find(sp);
1145     if (iter == scopeObjects_.end()) {
1146         LOG_DEBUGGER(ERROR) << "UpdateScopeObject: object not found";
1147         return;
1148     }
1149 
1150     auto objectId = iter->second;
1151     Local<ObjectRef> localObj = runtime_->properties_[objectId].ToLocal(vm_);
1152     Local<JSValueRef> name = StringRef::NewFromUtf8(vm_, varName.data());
1153     if (localObj->Has(vm_, name)) {
1154         LOG_DEBUGGER(DEBUG) << "UpdateScopeObject: set new value";
1155         PropertyAttribute descriptor(newVal, true, true, true);
1156         localObj->DefineProperty(vm_, name, descriptor);
1157     } else {
1158         LOG_DEBUGGER(ERROR) << "UpdateScopeObject: not found " << varName;
1159     }
1160 }
1161 
ClearSingleStepper()1162 void DebuggerImpl::ClearSingleStepper()
1163 {
1164     // ClearSingleStepper is originally called from Function::Call, if current depth is 0, then it is safe to reset
1165     if (singleStepper_ != nullptr && DebuggerApi::GetStackDepth(vm_) == 0) {
1166         singleStepper_.reset();
1167     }
1168 }
1169 
CmptEvaluateValue(CallFrameId callFrameId,const std::string & expression,std::unique_ptr<RemoteObject> * result)1170 std::optional<std::string> DebuggerImpl::CmptEvaluateValue(CallFrameId callFrameId, const std::string &expression,
1171     std::unique_ptr<RemoteObject> *result)
1172 {
1173     if (DebuggerApi::IsNativeMethod(vm_)) {
1174         *result = RemoteObject::FromTagged(vm_,
1175             Exception::EvalError(vm_, StringRef::NewFromUtf8(vm_, "Native Frame not support.")));
1176         return "Native Frame not support.";
1177     }
1178     DebugInfoExtractor *extractor = GetExtractor(DebuggerApi::GetJSPandaFile(vm_));
1179     if (extractor == nullptr) {
1180         *result = RemoteObject::FromTagged(vm_,
1181             Exception::EvalError(vm_, StringRef::NewFromUtf8(vm_, "Internal error.")));
1182         return "Internal error.";
1183     }
1184     std::string varName = expression;
1185     std::string varValue;
1186     std::string::size_type indexEqual = expression.find_first_of('=', 0);
1187     if (indexEqual != std::string::npos) {
1188         varName = Trim(expression.substr(0, indexEqual));
1189         varValue = Trim(expression.substr(indexEqual + 1, expression.length()));
1190     }
1191 
1192     Local<StringRef> name = StringRef::NewFromUtf8(vm_, varName.c_str());
1193     FrameHandler *frameHandler = callFrameHandlers_[callFrameId].get();
1194     if (varValue.empty()) {
1195         Local<JSValueRef> ret = DebuggerExecutor::GetValue(vm_, frameHandler, name);
1196         if (!ret.IsEmpty()) {
1197             *result = RemoteObject::FromTagged(vm_, ret);
1198             runtime_->CacheObjectIfNeeded(ret, (*result).get());
1199             return {};
1200         }
1201     } else {
1202         Local<JSValueRef> value = ConvertToLocal(varValue);
1203         if (value.IsEmpty()) {
1204             return "Unsupported expression.";
1205         }
1206         JsDebuggerManager *mgr = vm_->GetJsDebuggerManager();
1207         mgr->SetEvalFrameHandler(callFrameHandlers_[callFrameId]);
1208         bool ret = DebuggerExecutor::SetValue(vm_, frameHandler, name, value);
1209         mgr->SetEvalFrameHandler(nullptr);
1210         if (ret) {
1211             *result = RemoteObject::FromTagged(vm_, value);
1212             return {};
1213         }
1214     }
1215 
1216     *result = RemoteObject::FromTagged(vm_,
1217         Exception::EvalError(vm_, StringRef::NewFromUtf8(vm_, "Unsupported expression.")));
1218     return "Unsupported expression.";
1219 }
1220 
ConvertToLocal(const std::string & varValue)1221 Local<JSValueRef> DebuggerImpl::ConvertToLocal(const std::string &varValue)
1222 {
1223     Local<JSValueRef> taggedValue;
1224     if (varValue == "false") {
1225         taggedValue = JSValueRef::False(vm_);
1226     } else if (varValue == "true") {
1227         taggedValue = JSValueRef::True(vm_);
1228     } else if (varValue == "undefined") {
1229         taggedValue = JSValueRef::Undefined(vm_);
1230     } else if (varValue[0] == '\"' && varValue[varValue.length() - 1] == '\"') {
1231         // 2 : 2 means length
1232         taggedValue = StringRef::NewFromUtf8(vm_, varValue.substr(1, varValue.length() - 2).c_str());
1233     } else {
1234         auto begin = reinterpret_cast<const uint8_t *>((varValue.c_str()));
1235         auto end = begin + varValue.length();  // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
1236         double d = DebuggerApi::StringToDouble(begin, end, 0);
1237         if (!std::isnan(d)) {
1238             taggedValue = NumberRef::New(vm_, d);
1239         }
1240     }
1241     return taggedValue;
1242 }
1243 
DecodeAndCheckBase64(const std::string & src,std::string & dest)1244 bool DebuggerImpl::DecodeAndCheckBase64(const std::string &src, std::string &dest)
1245 {
1246     uint32_t numOctets = PtBase64::Decode(src, dest);
1247     if (numOctets > File::MAGIC_SIZE &&
1248         memcmp(dest.data(), File::MAGIC.data(), File::MAGIC_SIZE) == 0) {
1249         return true;
1250     }
1251     return false;
1252 }
1253 }  // namespace panda::ecmascript::tooling
1254