• 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 OHOS_UNIT_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 = JSPandaFileManager::GetInstance()->FindJSPandaFile(fileName.c_str()).get();
74     if (jsPandaFile == nullptr) {
75         LOG_DEBUGGER(ERROR) << "NotifyScriptParsed: unknown file: " << fileName;
76         return false;
77     }
78 
79     DebugInfoExtractor *extractor = GetExtractor(jsPandaFile);
80     if (extractor == nullptr) {
81         LOG_DEBUGGER(ERROR) << "NotifyScriptParsed: Unsupported file: " << fileName;
82         return false;
83     }
84 
85     const char *recordName = entryPoint.data();
86     auto mainMethodIndex = panda_file::File::EntityId(jsPandaFile->GetMainMethodIndex(recordName));
87     const std::string &source = extractor->GetSourceCode(mainMethodIndex);
88     const std::string &url = extractor->GetSourceFile(mainMethodIndex);
89     if (source.empty()) {
90         LOG_DEBUGGER(ERROR) << "NotifyScriptParsed: invalid file: " << fileName;
91         return false;
92     }
93     recordNames_[url] = recordName;
94 
95     auto scriptFunc = [this](PtScript *script) -> bool {
96         frontend_.ScriptParsed(vm_, *script);
97         return true;
98     };
99     if (MatchScripts(scriptFunc, url, ScriptMatchType::URL)) {
100         LOG_DEBUGGER(WARN) << "NotifyScriptParsed: already loaded: " << url;
101         return false;
102     }
103 
104     // Notify script parsed event
105     std::unique_ptr<PtScript> script = std::make_unique<PtScript>(scriptId, fileName, url, source);
106 
107     frontend_.ScriptParsed(vm_, *script);
108 
109     // Store parsed script in map
110     scripts_[script->GetScriptId()] = std::move(script);
111     return true;
112 }
113 
NotifySingleStep(const JSPtLocation & location)114 bool DebuggerImpl::NotifySingleStep(const JSPtLocation &location)
115 {
116     if (UNLIKELY(pauseOnNextByteCode_)) {
117         if (IsSkipLine(location)) {
118             return false;
119         }
120         pauseOnNextByteCode_ = false;
121         LOG_DEBUGGER(INFO) << "StepComplete: pause on next bytecode";
122         return true;
123     }
124 
125     if (LIKELY(singleStepper_ == nullptr)) {
126         return false;
127     }
128 
129     // step not complete
130     if (!singleStepper_->StepComplete(location.GetBytecodeOffset())) {
131         return false;
132     }
133 
134     // skip unknown file or special line -1
135     if (IsSkipLine(location)) {
136         return false;
137     }
138 
139     singleStepper_.reset();
140     LOG_DEBUGGER(INFO) << "StepComplete: pause on current byte_code";
141     return true;
142 }
143 
IsSkipLine(const JSPtLocation & location)144 bool DebuggerImpl::IsSkipLine(const JSPtLocation &location)
145 {
146     DebugInfoExtractor *extractor = nullptr;
147     const auto *jsPandaFile = location.GetJsPandaFile();
148     auto scriptFunc = [this, &extractor, jsPandaFile](PtScript *) -> bool {
149         extractor = GetExtractor(jsPandaFile);
150         return true;
151     };
152 
153     // In hot reload scenario, use the base js panda file instead
154     const auto &fileName = DebuggerApi::GetBaseJSPandaFile(vm_, jsPandaFile)->GetJSPandaFileDesc();
155     if (!MatchScripts(scriptFunc, fileName.c_str(), ScriptMatchType::FILE_NAME) || extractor == nullptr) {
156         LOG_DEBUGGER(INFO) << "StepComplete: skip unknown file " << fileName.c_str();
157         return true;
158     }
159 
160     auto callbackFunc = [](int32_t line) -> bool {
161         return line == DebugInfoExtractor::SPECIAL_LINE_MARK;
162     };
163     panda_file::File::EntityId methodId = location.GetMethodId();
164     uint32_t offset = location.GetBytecodeOffset();
165     if (extractor->MatchLineWithOffset(callbackFunc, methodId, offset)) {
166         LOG_DEBUGGER(INFO) << "StepComplete: skip -1";
167         return true;
168     }
169 
170     return false;
171 }
172 
CheckPauseOnException()173 bool DebuggerImpl::CheckPauseOnException()
174 {
175     if (pauseOnException_ == PauseOnExceptionsState::NONE) {
176         return false;
177     }
178     if (pauseOnException_ == PauseOnExceptionsState::UNCAUGHT) {
179         if (DebuggerApi::IsExceptionCaught(vm_)) {
180             return false;
181         }
182     }
183     return true;
184 }
185 
NotifyPaused(std::optional<JSPtLocation> location,PauseReason reason)186 void DebuggerImpl::NotifyPaused(std::optional<JSPtLocation> location, PauseReason reason)
187 {
188     if (reason == EXCEPTION && !CheckPauseOnException()) {
189         return;
190     }
191 
192     Local<JSValueRef> exception = DebuggerApi::GetAndClearException(vm_);
193 
194     std::vector<std::string> hitBreakpoints;
195     if (location.has_value()) {
196         BreakpointDetails detail;
197         DebugInfoExtractor *extractor = nullptr;
198         auto scriptFunc = [this, &location, &detail, &extractor](PtScript *script) -> bool {
199             detail.url_ = script->GetUrl();
200             extractor = GetExtractor(location->GetJsPandaFile());
201             return true;
202         };
203         auto callbackLineFunc = [&detail](int32_t line) -> bool {
204             detail.line_ = line;
205             return true;
206         };
207         auto callbackColumnFunc = [&detail](int32_t column) -> bool {
208             detail.column_ = column;
209             return true;
210         };
211         panda_file::File::EntityId methodId = location->GetMethodId();
212         uint32_t offset = location->GetBytecodeOffset();
213         // In merge abc scenario, need to use the source file to match to get right url
214         if (!MatchScripts(scriptFunc, location->GetSourceFile(), ScriptMatchType::URL) ||
215             extractor == nullptr || !extractor->MatchLineWithOffset(callbackLineFunc, methodId, offset) ||
216             !extractor->MatchColumnWithOffset(callbackColumnFunc, methodId, offset)) {
217             LOG_DEBUGGER(ERROR) << "NotifyPaused: unknown file " << location->GetSourceFile();
218             return;
219         }
220         hitBreakpoints.emplace_back(BreakpointDetails::ToString(detail));
221     }
222 
223     // Do something cleaning on paused
224     CleanUpOnPaused();
225 
226     // Notify paused event
227     std::vector<std::unique_ptr<CallFrame>> callFrames;
228     if (!GenerateCallFrames(&callFrames)) {
229         LOG_DEBUGGER(ERROR) << "NotifyPaused: GenerateCallFrames failed";
230         return;
231     }
232     tooling::Paused paused;
233     paused.SetCallFrames(std::move(callFrames)).SetReason(reason).SetHitBreakpoints(std::move(hitBreakpoints));
234     if (reason == EXCEPTION && exception->IsError()) {
235         std::unique_ptr<RemoteObject> tmpException = RemoteObject::FromTagged(vm_, exception);
236         paused.SetData(std::move(tmpException));
237     }
238     frontend_.Paused(vm_, paused);
239     if (reason != BREAK_ON_START) {
240         singleStepper_.reset();
241     }
242     debuggerState_ = DebuggerState::PAUSED;
243     frontend_.WaitForDebugger(vm_);
244     DebuggerApi::SetException(vm_, exception);
245 }
246 
NotifyNativeCalling(const void * nativeAddress)247 void DebuggerImpl::NotifyNativeCalling(const void *nativeAddress)
248 {
249     tooling::NativeCalling nativeCalling;
250     if (singleStepper_ != nullptr &&
251         singleStepper_->GetStepperType() == StepperType::STEP_INTO) {
252         nativeCalling.SetNativeAddress(nativeAddress);
253         nativeCalling.SetIntoStatus(true);
254     }
255 
256     nativePointer_ = DebuggerApi::GetNativePointer(vm_);
257     nativeCalling.SetNativePointer(nativePointer_);
258     std::vector<std::unique_ptr<CallFrame>> callFrames;
259     if (GenerateCallFrames(&callFrames)) {
260         nativeCalling.SetCallFrames(std::move(callFrames));
261     }
262     frontend_.NativeCalling(vm_, nativeCalling);
263     frontend_.WaitForDebugger(vm_);
264 }
265 
266 // only use for test case
SetDebuggerState(DebuggerState debuggerState)267 void DebuggerImpl::SetDebuggerState(DebuggerState debuggerState)
268 {
269     debuggerState_ = debuggerState;
270 }
271 
NotifyHandleProtocolCommand()272 void DebuggerImpl::NotifyHandleProtocolCommand()
273 {
274     auto *handler = vm_->GetJsDebuggerManager()->GetDebuggerHandler();
275     handler->ProcessCommand();
276 }
277 
Dispatch(const DispatchRequest & request)278 void DebuggerImpl::DispatcherImpl::Dispatch(const DispatchRequest &request)
279 {
280     static std::unordered_map<std::string, AgentHandler> dispatcherTable {
281         { "enable", &DebuggerImpl::DispatcherImpl::Enable },
282         { "disable", &DebuggerImpl::DispatcherImpl::Disable },
283         { "evaluateOnCallFrame", &DebuggerImpl::DispatcherImpl::EvaluateOnCallFrame },
284         { "getPossibleBreakpoints", &DebuggerImpl::DispatcherImpl::GetPossibleBreakpoints },
285         { "getScriptSource", &DebuggerImpl::DispatcherImpl::GetScriptSource },
286         { "pause", &DebuggerImpl::DispatcherImpl::Pause },
287         { "removeBreakpoint", &DebuggerImpl::DispatcherImpl::RemoveBreakpoint },
288         { "resume", &DebuggerImpl::DispatcherImpl::Resume },
289         { "setAsyncCallStackDepth", &DebuggerImpl::DispatcherImpl::SetAsyncCallStackDepth },
290         { "setBreakpointByUrl", &DebuggerImpl::DispatcherImpl::SetBreakpointByUrl },
291         { "setPauseOnExceptions", &DebuggerImpl::DispatcherImpl::SetPauseOnExceptions },
292         { "stepInto", &DebuggerImpl::DispatcherImpl::StepInto },
293         { "stepOut", &DebuggerImpl::DispatcherImpl::StepOut },
294         { "stepOver", &DebuggerImpl::DispatcherImpl::StepOver },
295         { "setMixedDebugEnabled", &DebuggerImpl::DispatcherImpl::SetMixedDebugEnabled },
296         { "setBlackboxPatterns", &DebuggerImpl::DispatcherImpl::SetBlackboxPatterns },
297         { "replyNativeCalling", &DebuggerImpl::DispatcherImpl::ReplyNativeCalling },
298         { "getPossibleAndSetBreakpointByUrl", &DebuggerImpl::DispatcherImpl::GetPossibleAndSetBreakpointByUrl },
299         { "dropFrame", &DebuggerImpl::DispatcherImpl::DropFrame }
300     };
301 
302     const std::string &method = request.GetMethod();
303     LOG_DEBUGGER(DEBUG) << "dispatch [" << method << "] to DebuggerImpl";
304     auto entry = dispatcherTable.find(method);
305     if (entry != dispatcherTable.end() && entry->second != nullptr) {
306         (this->*(entry->second))(request);
307     } else {
308         SendResponse(request, DispatchResponse::Fail("Unknown method: " + method));
309     }
310 }
311 
Enable(const DispatchRequest & request)312 void DebuggerImpl::DispatcherImpl::Enable(const DispatchRequest &request)
313 {
314     std::unique_ptr<EnableParams> params = EnableParams::Create(request.GetParams());
315     if (params == nullptr) {
316         SendResponse(request, DispatchResponse::Fail("wrong params"));
317         return;
318     }
319 
320     UniqueDebuggerId id;
321     DispatchResponse response = debugger_->Enable(*params, &id);
322 
323     EnableReturns result(id);
324     SendResponse(request, response, result);
325 }
326 
Disable(const DispatchRequest & request)327 void DebuggerImpl::DispatcherImpl::Disable(const DispatchRequest &request)
328 {
329     DispatchResponse response = debugger_->Disable();
330     SendResponse(request, response);
331 }
332 
EvaluateOnCallFrame(const DispatchRequest & request)333 void DebuggerImpl::DispatcherImpl::EvaluateOnCallFrame(const DispatchRequest &request)
334 {
335     std::unique_ptr<EvaluateOnCallFrameParams> params = EvaluateOnCallFrameParams::Create(request.GetParams());
336     if (params == nullptr) {
337         SendResponse(request, DispatchResponse::Fail("wrong params"));
338         return;
339     }
340     std::unique_ptr<RemoteObject> result1;
341     DispatchResponse response = debugger_->EvaluateOnCallFrame(*params, &result1);
342     if (result1 == nullptr) {
343         SendResponse(request, response);
344         return;
345     }
346 
347     EvaluateOnCallFrameReturns result(std::move(result1));
348     SendResponse(request, response, result);
349 }
350 
GetPossibleBreakpoints(const DispatchRequest & request)351 void DebuggerImpl::DispatcherImpl::GetPossibleBreakpoints(const DispatchRequest &request)
352 {
353     std::unique_ptr<GetPossibleBreakpointsParams> params = GetPossibleBreakpointsParams::Create(request.GetParams());
354     if (params == nullptr) {
355         SendResponse(request, DispatchResponse::Fail("wrong params"));
356         return;
357     }
358     std::vector<std::unique_ptr<BreakLocation>> locations;
359     DispatchResponse response = debugger_->GetPossibleBreakpoints(*params, &locations);
360     GetPossibleBreakpointsReturns result(std::move(locations));
361     SendResponse(request, response, result);
362 }
363 
GetScriptSource(const DispatchRequest & request)364 void DebuggerImpl::DispatcherImpl::GetScriptSource(const DispatchRequest &request)
365 {
366     std::unique_ptr<GetScriptSourceParams> params = GetScriptSourceParams::Create(request.GetParams());
367     if (params == nullptr) {
368         SendResponse(request, DispatchResponse::Fail("wrong params"));
369         return;
370     }
371     std::string source;
372     DispatchResponse response = debugger_->GetScriptSource(*params, &source);
373     GetScriptSourceReturns result(source);
374     SendResponse(request, response, result);
375 }
376 
Pause(const DispatchRequest & request)377 void DebuggerImpl::DispatcherImpl::Pause(const DispatchRequest &request)
378 {
379     DispatchResponse response = debugger_->Pause();
380     SendResponse(request, response);
381 }
382 
RemoveBreakpoint(const DispatchRequest & request)383 void DebuggerImpl::DispatcherImpl::RemoveBreakpoint(const DispatchRequest &request)
384 {
385     std::unique_ptr<RemoveBreakpointParams> params = RemoveBreakpointParams::Create(request.GetParams());
386     if (params == nullptr) {
387         SendResponse(request, DispatchResponse::Fail("wrong params"));
388         return;
389     }
390     DispatchResponse response = debugger_->RemoveBreakpoint(*params);
391     SendResponse(request, response);
392 }
393 
Resume(const DispatchRequest & request)394 void DebuggerImpl::DispatcherImpl::Resume(const DispatchRequest &request)
395 {
396     std::unique_ptr<ResumeParams> params = ResumeParams::Create(request.GetParams());
397     if (params == nullptr) {
398         SendResponse(request, DispatchResponse::Fail("wrong params"));
399         return;
400     }
401     DispatchResponse response = debugger_->Resume(*params);
402     SendResponse(request, response);
403 }
404 
SetAsyncCallStackDepth(const DispatchRequest & request)405 void DebuggerImpl::DispatcherImpl::SetAsyncCallStackDepth(const DispatchRequest &request)
406 {
407     DispatchResponse response = debugger_->SetAsyncCallStackDepth();
408     SendResponse(request, response);
409 }
410 
SetBreakpointByUrl(const DispatchRequest & request)411 void DebuggerImpl::DispatcherImpl::SetBreakpointByUrl(const DispatchRequest &request)
412 {
413     std::unique_ptr<SetBreakpointByUrlParams> params = SetBreakpointByUrlParams::Create(request.GetParams());
414     if (params == nullptr) {
415         SendResponse(request, DispatchResponse::Fail("wrong params"));
416         return;
417     }
418 
419     std::string outId;
420     std::vector<std::unique_ptr<Location>> outLocations;
421     DispatchResponse response = debugger_->SetBreakpointByUrl(*params, &outId, &outLocations);
422     SetBreakpointByUrlReturns result(outId, std::move(outLocations));
423     SendResponse(request, response, result);
424 }
425 
GetPossibleAndSetBreakpointByUrl(const DispatchRequest & request)426 void DebuggerImpl::DispatcherImpl::GetPossibleAndSetBreakpointByUrl(const DispatchRequest &request)
427 {
428     std::unique_ptr<GetPossibleAndSetBreakpointParams> params;
429     params = GetPossibleAndSetBreakpointParams::Create(request.GetParams());
430     if (params == nullptr) {
431         SendResponse(request, DispatchResponse::Fail("wrong params"));
432         return;
433     }
434 
435     std::vector<std::unique_ptr<BreakpointReturnInfo>> outLoc;
436     DispatchResponse response = debugger_->GetPossibleAndSetBreakpointByUrl(*params, outLoc);
437     GetPossibleAndSetBreakpointByUrlReturns result(std::move(outLoc));
438     SendResponse(request, response, result);
439 }
440 
SetPauseOnExceptions(const DispatchRequest & request)441 void DebuggerImpl::DispatcherImpl::SetPauseOnExceptions(const DispatchRequest &request)
442 {
443     std::unique_ptr<SetPauseOnExceptionsParams> params = SetPauseOnExceptionsParams::Create(request.GetParams());
444     if (params == nullptr) {
445         SendResponse(request, DispatchResponse::Fail("wrong params"));
446         return;
447     }
448 
449     DispatchResponse response = debugger_->SetPauseOnExceptions(*params);
450     SendResponse(request, response);
451 }
452 
StepInto(const DispatchRequest & request)453 void DebuggerImpl::DispatcherImpl::StepInto(const DispatchRequest &request)
454 {
455     std::unique_ptr<StepIntoParams> params = StepIntoParams::Create(request.GetParams());
456     if (params == nullptr) {
457         SendResponse(request, DispatchResponse::Fail("wrong params"));
458         return;
459     }
460     DispatchResponse response = debugger_->StepInto(*params);
461     SendResponse(request, response);
462 }
463 
StepOut(const DispatchRequest & request)464 void DebuggerImpl::DispatcherImpl::StepOut(const DispatchRequest &request)
465 {
466     DispatchResponse response = debugger_->StepOut();
467     SendResponse(request, response);
468 }
469 
StepOver(const DispatchRequest & request)470 void DebuggerImpl::DispatcherImpl::StepOver(const DispatchRequest &request)
471 {
472     std::unique_ptr<StepOverParams> params = StepOverParams::Create(request.GetParams());
473     if (params == nullptr) {
474         SendResponse(request, DispatchResponse::Fail("wrong params"));
475         return;
476     }
477     DispatchResponse response = debugger_->StepOver(*params);
478     SendResponse(request, response);
479 }
480 
SetMixedDebugEnabled(const DispatchRequest & request)481 void DebuggerImpl::DispatcherImpl::SetMixedDebugEnabled(const DispatchRequest &request)
482 {
483     std::unique_ptr<SetMixedDebugParams> params = SetMixedDebugParams::Create(request.GetParams());
484     if (params == nullptr) {
485         SendResponse(request, DispatchResponse::Fail("wrong params"));
486         return;
487     }
488     DispatchResponse response = debugger_->SetMixedDebugEnabled(*params);
489     SendResponse(request, response);
490 }
491 
ReplyNativeCalling(const DispatchRequest & request)492 void DebuggerImpl::DispatcherImpl::ReplyNativeCalling(const DispatchRequest &request)
493 {
494     std::unique_ptr<ReplyNativeCallingParams> params = ReplyNativeCallingParams::Create(request.GetParams());
495     if (params == nullptr) {
496         SendResponse(request, DispatchResponse::Fail("wrong params"));
497         return;
498     }
499     DispatchResponse response = debugger_->ReplyNativeCalling(*params);
500     SendResponse(request, response);
501 }
502 
SetBlackboxPatterns(const DispatchRequest & request)503 void DebuggerImpl::DispatcherImpl::SetBlackboxPatterns(const DispatchRequest &request)
504 {
505     DispatchResponse response = debugger_->SetBlackboxPatterns();
506     SendResponse(request, response);
507 }
508 
DropFrame(const DispatchRequest & request)509 void DebuggerImpl::DispatcherImpl::DropFrame(const DispatchRequest &request)
510 {
511     std::unique_ptr<DropFrameParams> params = DropFrameParams::Create(request.GetParams());
512     if (params == nullptr) {
513         SendResponse(request, DispatchResponse::Fail("wrong params"));
514         return;
515     }
516     DispatchResponse response = debugger_->DropFrame(*params);
517     SendResponse(request, response);
518 }
519 
AllowNotify(const EcmaVM * vm) const520 bool DebuggerImpl::Frontend::AllowNotify(const EcmaVM *vm) const
521 {
522     return vm->GetJsDebuggerManager()->IsDebugMode() && channel_ != nullptr;
523 }
524 
BreakpointResolved(const EcmaVM * vm)525 void DebuggerImpl::Frontend::BreakpointResolved(const EcmaVM *vm)
526 {
527     if (!AllowNotify(vm)) {
528         return;
529     }
530 
531     tooling::BreakpointResolved breakpointResolved;
532     channel_->SendNotification(breakpointResolved);
533 }
534 
Paused(const EcmaVM * vm,const tooling::Paused & paused)535 void DebuggerImpl::Frontend::Paused(const EcmaVM *vm, const tooling::Paused &paused)
536 {
537     if (!AllowNotify(vm)) {
538         return;
539     }
540 
541     channel_->SendNotification(paused);
542 }
543 
NativeCalling(const EcmaVM * vm,const tooling::NativeCalling & nativeCalling)544 void DebuggerImpl::Frontend::NativeCalling(const EcmaVM *vm, const tooling::NativeCalling &nativeCalling)
545 {
546     if (!AllowNotify(vm)) {
547         return;
548     }
549 
550     channel_->SendNotification(nativeCalling);
551 }
552 
Resumed(const EcmaVM * vm)553 void DebuggerImpl::Frontend::Resumed(const EcmaVM *vm)
554 {
555     if (!AllowNotify(vm)) {
556         return;
557     }
558 
559     channel_->RunIfWaitingForDebugger();
560     tooling::Resumed resumed;
561     channel_->SendNotification(resumed);
562 }
563 
ScriptFailedToParse(const EcmaVM * vm)564 void DebuggerImpl::Frontend::ScriptFailedToParse(const EcmaVM *vm)
565 {
566     if (!AllowNotify(vm)) {
567         return;
568     }
569 
570     tooling::ScriptFailedToParse scriptFailedToParse;
571     channel_->SendNotification(scriptFailedToParse);
572 }
573 
ScriptParsed(const EcmaVM * vm,const PtScript & script)574 void DebuggerImpl::Frontend::ScriptParsed(const EcmaVM *vm, const PtScript &script)
575 {
576     if (!AllowNotify(vm)) {
577         return;
578     }
579 
580     tooling::ScriptParsed scriptParsed;
581     scriptParsed.SetScriptId(script.GetScriptId())
582         .SetUrl(script.GetUrl())
583         .SetStartLine(0)
584         .SetStartColumn(0)
585         .SetEndLine(script.GetEndLine())
586         .SetEndColumn(0)
587         .SetExecutionContextId(0)
588         .SetHash(script.GetHash());
589 
590     channel_->SendNotification(scriptParsed);
591 }
592 
WaitForDebugger(const EcmaVM * vm)593 void DebuggerImpl::Frontend::WaitForDebugger(const EcmaVM *vm)
594 {
595     if (!AllowNotify(vm)) {
596         return;
597     }
598 
599     channel_->WaitForDebugger();
600 }
601 
RunIfWaitingForDebugger(const EcmaVM * vm)602 void DebuggerImpl::Frontend::RunIfWaitingForDebugger(const EcmaVM *vm)
603 {
604     if (!AllowNotify(vm)) {
605         return;
606     }
607 
608     channel_->RunIfWaitingForDebugger();
609 }
610 
Enable(const EnableParams & params,UniqueDebuggerId * id)611 DispatchResponse DebuggerImpl::Enable([[maybe_unused]] const EnableParams &params, UniqueDebuggerId *id)
612 {
613     ASSERT(id != nullptr);
614     *id = 0;
615     vm_->GetJsDebuggerManager()->SetDebugMode(true);
616     for (auto &script : scripts_) {
617         frontend_.ScriptParsed(vm_, *script.second);
618     }
619     debuggerState_ = DebuggerState::ENABLED;
620     return DispatchResponse::Ok();
621 }
622 
Disable()623 DispatchResponse DebuggerImpl::Disable()
624 {
625     DebuggerApi::RemoveAllBreakpoints(jsDebugger_);
626     frontend_.RunIfWaitingForDebugger(vm_);
627     frontend_.Resumed(vm_);
628     vm_->GetJsDebuggerManager()->SetDebugMode(false);
629     debuggerState_ = DebuggerState::DISABLED;
630     return DispatchResponse::Ok();
631 }
632 
EvaluateOnCallFrame(const EvaluateOnCallFrameParams & params,std::unique_ptr<RemoteObject> * result)633 DispatchResponse DebuggerImpl::EvaluateOnCallFrame(const EvaluateOnCallFrameParams &params,
634                                                    std::unique_ptr<RemoteObject> *result)
635 {
636     CallFrameId callFrameId = params.GetCallFrameId();
637     const std::string &expression = params.GetExpression();
638     if (callFrameId < 0 || callFrameId >= static_cast<CallFrameId>(callFrameHandlers_.size())) {
639         return DispatchResponse::Fail("Invalid callFrameId.");
640     }
641 
642     std::vector<uint8_t> dest;
643     if (!DecodeAndCheckBase64(expression, dest)) {
644         LOG_DEBUGGER(ERROR) << "EvaluateValue: base64 decode failed";
645         auto ret = CmptEvaluateValue(callFrameId, expression, result);
646         if (ret.has_value()) {
647             LOG_DEBUGGER(ERROR) << "Evaluate fail, expression: " << expression;
648         }
649         return DispatchResponse::Create(ret);
650     }
651 
652     auto funcRef = DebuggerApi::GenerateFuncFromBuffer(vm_, dest.data(), dest.size(),
653         JSPandaFile::ENTRY_FUNCTION_NAME);
654     auto res = DebuggerApi::EvaluateViaFuncCall(const_cast<EcmaVM *>(vm_), funcRef,
655         callFrameHandlers_[callFrameId]);
656     if (vm_->GetJSThread()->HasPendingException()) {
657         LOG_DEBUGGER(ERROR) << "EvaluateValue: has pending exception";
658         std::string msg;
659         DebuggerApi::HandleUncaughtException(vm_, msg);
660         *result = RemoteObject::FromTagged(vm_,
661             Exception::EvalError(vm_, StringRef::NewFromUtf8(vm_, msg.data())));
662         return DispatchResponse::Fail(msg);
663     }
664 
665     *result = RemoteObject::FromTagged(vm_, res);
666     runtime_->CacheObjectIfNeeded(res, (*result).get());
667     return DispatchResponse::Ok();
668 }
669 
GetPossibleBreakpoints(const GetPossibleBreakpointsParams & params,std::vector<std::unique_ptr<BreakLocation>> * locations)670 DispatchResponse DebuggerImpl::GetPossibleBreakpoints(const GetPossibleBreakpointsParams &params,
671                                                       std::vector<std::unique_ptr<BreakLocation>> *locations)
672 {
673     Location *start = params.GetStart();
674     auto iter = scripts_.find(start->GetScriptId());
675     if (iter == scripts_.end()) {
676         return DispatchResponse::Fail("Unknown file name.");
677     }
678     const std::string &url = iter->second->GetUrl();
679     DebugInfoExtractor *extractor = GetExtractor(url);
680     if (extractor == nullptr) {
681         LOG_DEBUGGER(ERROR) << "GetPossibleBreakpoints: extractor is null";
682         return DispatchResponse::Fail("Unknown file name.");
683     }
684 
685     int32_t line = start->GetLine();
686     int32_t column = start->GetColumn();
687     auto callbackFunc = [](const JSPtLocation &) -> bool {
688         return true;
689     };
690     if (extractor->MatchWithLocation(callbackFunc, line, column, url, GetRecordName(url))) {
691         std::unique_ptr<BreakLocation> location = std::make_unique<BreakLocation>();
692         location->SetScriptId(start->GetScriptId()).SetLine(line).SetColumn(column);
693         locations->emplace_back(std::move(location));
694     }
695     return DispatchResponse::Ok();
696 }
697 
GetScriptSource(const GetScriptSourceParams & params,std::string * source)698 DispatchResponse DebuggerImpl::GetScriptSource(const GetScriptSourceParams &params, std::string *source)
699 {
700     ScriptId scriptId = params.GetScriptId();
701     auto iter = scripts_.find(scriptId);
702     if (iter == scripts_.end()) {
703         *source = "";
704         return DispatchResponse::Fail("unknown script id: " + std::to_string(scriptId));
705     }
706     *source = iter->second->GetScriptSource();
707 
708     return DispatchResponse::Ok();
709 }
710 
Pause()711 DispatchResponse DebuggerImpl::Pause()
712 {
713     pauseOnNextByteCode_ = true;
714     return DispatchResponse::Ok();
715 }
716 
RemoveBreakpoint(const RemoveBreakpointParams & params)717 DispatchResponse DebuggerImpl::RemoveBreakpoint(const RemoveBreakpointParams &params)
718 {
719     std::string id = params.GetBreakpointId();
720     LOG_DEBUGGER(INFO) << "RemoveBreakpoint: " << id;
721     BreakpointDetails metaData{};
722     if (!BreakpointDetails::ParseBreakpointId(id, &metaData)) {
723         return DispatchResponse::Fail("Parse breakpoint id failed");
724     }
725     DebugInfoExtractor *extractor = GetExtractor(metaData.url_);
726     if (extractor == nullptr) {
727         LOG_DEBUGGER(ERROR) << "RemoveBreakpoint: extractor is null";
728         return DispatchResponse::Fail("Unknown file name.");
729     }
730 
731     auto scriptFunc = [](PtScript *) -> bool {
732         return true;
733     };
734     if (!MatchScripts(scriptFunc, metaData.url_, ScriptMatchType::URL)) {
735         LOG_DEBUGGER(ERROR) << "RemoveBreakpoint: Unknown url: " << metaData.url_;
736         return DispatchResponse::Fail("Unknown file name.");
737     }
738 
739     auto callbackFunc = [this](const JSPtLocation &location) -> bool {
740         LOG_DEBUGGER(INFO) << "remove breakpoint location: " << location.ToString();
741         return DebuggerApi::RemoveBreakpoint(jsDebugger_, location);
742     };
743     if (!extractor->MatchWithLocation(callbackFunc, metaData.line_, metaData.column_,
744         metaData.url_, GetRecordName(metaData.url_))) {
745         LOG_DEBUGGER(ERROR) << "failed to remove breakpoint location number: "
746             << metaData.line_ << ":" << metaData.column_;
747         return DispatchResponse::Fail("Breakpoint not found.");
748     }
749 
750     LOG_DEBUGGER(INFO) << "remove breakpoint line number:" << metaData.line_;
751     return DispatchResponse::Ok();
752 }
753 
Resume(const ResumeParams & params)754 DispatchResponse DebuggerImpl::Resume([[maybe_unused]] const ResumeParams &params)
755 {
756     if (debuggerState_ != DebuggerState::PAUSED) {
757         return DispatchResponse::Fail("Can only perform operation while paused");
758     }
759     frontend_.Resumed(vm_);
760     debuggerState_ = DebuggerState::ENABLED;
761     return DispatchResponse::Ok();
762 }
763 
SetAsyncCallStackDepth()764 DispatchResponse DebuggerImpl::SetAsyncCallStackDepth()
765 {
766     return DispatchResponse::Fail("SetAsyncCallStackDepth not support now");
767 }
768 
SetBreakpointByUrl(const SetBreakpointByUrlParams & params,std::string * outId,std::vector<std::unique_ptr<Location>> * outLocations)769 DispatchResponse DebuggerImpl::SetBreakpointByUrl(const SetBreakpointByUrlParams &params,
770                                                   std::string *outId,
771                                                   std::vector<std::unique_ptr<Location>> *outLocations)
772 {
773     if (!vm_->GetJsDebuggerManager()->IsDebugMode()) {
774         return DispatchResponse::Fail("SetBreakpointByUrl: debugger agent is not enabled");
775     }
776     const std::string &url = params.GetUrl();
777     int32_t lineNumber = params.GetLine();
778     int32_t columnNumber = params.GetColumn();
779     auto condition = params.HasCondition() ? params.GetCondition() : std::optional<std::string> {};
780 
781     DebugInfoExtractor *extractor = GetExtractor(url);
782     if (extractor == nullptr) {
783         LOG_DEBUGGER(ERROR) << "SetBreakpointByUrl: extractor is null";
784         return DispatchResponse::Fail("Unknown file name.");
785     }
786 
787     ScriptId scriptId;
788     std::string fileName;
789     auto scriptFunc = [&scriptId, &fileName](PtScript *script) -> bool {
790         scriptId = script->GetScriptId();
791         fileName = script->GetFileName();
792         return true;
793     };
794     if (!MatchScripts(scriptFunc, url, ScriptMatchType::URL)) {
795         LOG_DEBUGGER(ERROR) << "SetBreakpointByUrl: Unknown url: " << url;
796         return DispatchResponse::Fail("Unknown file name.");
797     }
798 
799     auto callbackFunc = [this, &condition](const JSPtLocation &location) -> bool {
800         LOG_DEBUGGER(INFO) << "set breakpoint location: " << location.ToString();
801         Local<FunctionRef> condFuncRef = FunctionRef::Undefined(vm_);
802         if (condition.has_value() && !condition.value().empty()) {
803             std::vector<uint8_t> dest;
804             if (!DecodeAndCheckBase64(condition.value(), dest)) {
805                 LOG_DEBUGGER(ERROR) << "SetBreakpointByUrl: base64 decode failed";
806                 return false;
807             }
808             condFuncRef = DebuggerApi::GenerateFuncFromBuffer(vm_, dest.data(), dest.size(),
809                 JSPandaFile::ENTRY_FUNCTION_NAME);
810             if (condFuncRef->IsUndefined()) {
811                 LOG_DEBUGGER(ERROR) << "SetBreakpointByUrl: generate function failed";
812                 return false;
813             }
814         }
815         return DebuggerApi::SetBreakpoint(jsDebugger_, location, condFuncRef);
816     };
817     if (!extractor->MatchWithLocation(callbackFunc, lineNumber, columnNumber, url, GetRecordName(url))) {
818         LOG_DEBUGGER(ERROR) << "failed to set breakpoint location number: " << lineNumber << ":" << columnNumber;
819         return DispatchResponse::Fail("Breakpoint not found.");
820     }
821 
822     BreakpointDetails metaData{lineNumber, 0, url};
823     *outId = BreakpointDetails::ToString(metaData);
824     *outLocations = std::vector<std::unique_ptr<Location>>();
825     std::unique_ptr<Location> location = std::make_unique<Location>();
826     location->SetScriptId(scriptId).SetLine(lineNumber).SetColumn(0);
827     outLocations->emplace_back(std::move(location));
828 
829     return DispatchResponse::Ok();
830 }
831 
GetPossibleAndSetBreakpointByUrl(const GetPossibleAndSetBreakpointParams & params,std::vector<std::unique_ptr<BreakpointReturnInfo>> & outLocations)832 DispatchResponse DebuggerImpl::GetPossibleAndSetBreakpointByUrl(const GetPossibleAndSetBreakpointParams &params,
833     std::vector<std::unique_ptr<BreakpointReturnInfo>> &outLocations)
834 {
835     if (!vm_->GetJsDebuggerManager()->IsDebugMode()) {
836         return DispatchResponse::Fail("GetPossibleAndSetBreakpointByUrl: debugger agent is not enabled");
837     }
838     if (!params.HasBreakpointsList()) {
839         return DispatchResponse::Fail("GetPossibleAndSetBreakpointByUrl: no pennding breakpoint exists");
840     }
841     auto breakpointList = params.GetBreakpointsList();
842     for (const auto &breakpoint : *breakpointList) {
843         bool isProcessSucceed = ProcessSingleBreakpoint(*breakpoint, outLocations);
844         if (!isProcessSucceed) {
845             std::string invalidBpId = "invalid";
846             std::unique_ptr<BreakpointReturnInfo> bpInfo = std::make_unique<BreakpointReturnInfo>();
847             bpInfo->SetId(invalidBpId)
848                 .SetLineNumber(breakpoint->GetLineNumber())
849                 .SetColumnNumber(breakpoint->GetColumnNumber());
850             outLocations.emplace_back(std::move(bpInfo));
851         }
852     }
853     return DispatchResponse::Ok();
854 }
855 
ProcessSingleBreakpoint(const BreakpointInfo & breakpoint,std::vector<std::unique_ptr<BreakpointReturnInfo>> & outLocations)856 bool DebuggerImpl::ProcessSingleBreakpoint(const BreakpointInfo &breakpoint,
857                                            std::vector<std::unique_ptr<BreakpointReturnInfo>> &outLocations)
858 {
859     const std::string &url = breakpoint.GetUrl();
860     int32_t lineNumber = breakpoint.GetLineNumber();
861     int32_t columnNumber = breakpoint.GetColumnNumber();
862     auto condition = breakpoint.HasCondition() ? breakpoint.GetCondition() : std::optional<std::string> {};
863 
864     DebugInfoExtractor *extractor = GetExtractor(url);
865     if (extractor == nullptr) {
866         LOG_DEBUGGER(ERROR) << "GetPossibleAndSetBreakpointByUrl: extractor is null";
867         return false;
868     }
869 
870     ScriptId scriptId;
871     std::string fileName;
872     auto matchScriptCbFunc = [&scriptId, &fileName](PtScript *script) -> bool {
873         scriptId = script->GetScriptId();
874         fileName = script->GetFileName();
875         return true;
876     };
877     if (!MatchScripts(matchScriptCbFunc, url, ScriptMatchType::URL)) {
878         LOG_DEBUGGER(ERROR) << "GetPossibleAndSetBreakpointByUrl: unknown Url: " << url;
879         return false;
880     }
881 
882     // check breakpoint condition before doing matchWithLocation
883     Local<FunctionRef> funcRef = FunctionRef::Undefined(vm_);
884     if (condition.has_value() && !condition.value().empty()) {
885         std::vector<uint8_t> dest;
886         if (!DecodeAndCheckBase64(condition.value(), dest)) {
887             LOG_DEBUGGER(ERROR) << "GetPossibleAndSetBreakpointByUrl: base64 decode failed";
888             return false;
889         }
890         funcRef = DebuggerApi::GenerateFuncFromBuffer(vm_, dest.data(), dest.size(), JSPandaFile::ENTRY_FUNCTION_NAME);
891         if (funcRef->IsUndefined()) {
892             LOG_DEBUGGER(ERROR) << "GetPossibleAndSetBreakpointByUrl: generate condition function failed";
893             return false;
894         }
895     }
896 
897     auto matchLocationCbFunc = [this, &funcRef](const JSPtLocation &location) -> bool {
898         return DebuggerApi::SetBreakpoint(jsDebugger_, location, funcRef);
899     };
900     if (!extractor->MatchWithLocation(matchLocationCbFunc, lineNumber, columnNumber, url, GetRecordName(url))) {
901         LOG_DEBUGGER(ERROR) << "failed to set breakpoint location number: " << lineNumber << ":" << columnNumber;
902         return false;
903     }
904 
905     BreakpointDetails bpMetaData {lineNumber, columnNumber, url};
906     std::string outId = BreakpointDetails::ToString(bpMetaData);
907     std::unique_ptr<BreakpointReturnInfo> bpInfo = std::make_unique<BreakpointReturnInfo>();
908     bpInfo->SetScriptId(scriptId).SetLineNumber(lineNumber).SetColumnNumber(columnNumber).SetId(outId);
909     outLocations.emplace_back(std::move(bpInfo));
910 
911     return true;
912 }
913 
SetPauseOnExceptions(const SetPauseOnExceptionsParams & params)914 DispatchResponse DebuggerImpl::SetPauseOnExceptions(const SetPauseOnExceptionsParams &params)
915 {
916     pauseOnException_ = params.GetState();
917     return DispatchResponse::Ok();
918 }
919 
StepInto(const StepIntoParams & params)920 DispatchResponse DebuggerImpl::StepInto([[maybe_unused]] const StepIntoParams &params)
921 {
922     if (debuggerState_ != DebuggerState::PAUSED) {
923         return DispatchResponse::Fail("Can only perform operation while paused");
924     }
925     singleStepper_ = SingleStepper::GetStepIntoStepper(vm_);
926     if (singleStepper_ == nullptr) {
927         LOG_DEBUGGER(ERROR) << "StepInto: singleStepper is null";
928         return DispatchResponse::Fail("Failed to StepInto");
929     }
930     frontend_.Resumed(vm_);
931     debuggerState_ = DebuggerState::ENABLED;
932     return DispatchResponse::Ok();
933 }
934 
StepOut()935 DispatchResponse DebuggerImpl::StepOut()
936 {
937     if (debuggerState_ != DebuggerState::PAUSED) {
938         return DispatchResponse::Fail("Can only perform operation while paused");
939     }
940     singleStepper_ = SingleStepper::GetStepOutStepper(vm_);
941     if (singleStepper_ == nullptr) {
942         LOG_DEBUGGER(ERROR) << "StepOut: singleStepper is null";
943         return DispatchResponse::Fail("Failed to StepOut");
944     }
945     frontend_.Resumed(vm_);
946     debuggerState_ = DebuggerState::ENABLED;
947     return DispatchResponse::Ok();
948 }
949 
StepOver(const StepOverParams & params)950 DispatchResponse DebuggerImpl::StepOver([[maybe_unused]] const StepOverParams &params)
951 {
952     if (debuggerState_ != DebuggerState::PAUSED) {
953         return DispatchResponse::Fail("Can only perform operation while paused");
954     }
955     singleStepper_ = SingleStepper::GetStepOverStepper(vm_);
956     if (singleStepper_ == nullptr) {
957         LOG_DEBUGGER(ERROR) << "StepOver: singleStepper is null";
958         return DispatchResponse::Fail("Failed to StepOver");
959     }
960     frontend_.Resumed(vm_);
961     debuggerState_ = DebuggerState::ENABLED;
962     return DispatchResponse::Ok();
963 }
964 
SetBlackboxPatterns()965 DispatchResponse DebuggerImpl::SetBlackboxPatterns()
966 {
967     return DispatchResponse::Fail("SetBlackboxPatterns not support now");
968 }
969 
SetMixedDebugEnabled(const SetMixedDebugParams & params)970 DispatchResponse DebuggerImpl::SetMixedDebugEnabled([[maybe_unused]] const SetMixedDebugParams &params)
971 {
972     vm_->GetJsDebuggerManager()->SetMixedDebugEnabled(params.GetEnabled());
973     return DispatchResponse::Ok();
974 }
975 
ReplyNativeCalling(const ReplyNativeCallingParams & params)976 DispatchResponse DebuggerImpl::ReplyNativeCalling([[maybe_unused]] const ReplyNativeCallingParams &params)
977 {
978     frontend_.Resumed(vm_);
979     if (params.GetUserCode()) {
980         singleStepper_.reset();
981     }
982     return DispatchResponse::Ok();
983 }
984 
DropFrame(const DropFrameParams & params)985 DispatchResponse DebuggerImpl::DropFrame(const DropFrameParams &params)
986 {
987     if (debuggerState_ != DebuggerState::PAUSED) {
988         return DispatchResponse::Fail("Can only perform operation while paused");
989     }
990     uint32_t droppedDepth = 1;
991     if (params.HasDroppedDepth()) {
992         droppedDepth = params.GetDroppedDepth();
993     }
994     uint32_t stackDepth = DebuggerApi::GetStackDepth(vm_);
995     if (droppedDepth > stackDepth) {
996         return DispatchResponse::Fail("The input depth exceeds stackDepth");
997     }
998     if (droppedDepth == stackDepth) {
999         return DispatchResponse::Fail("The bottom frame cannot be dropped");
1000     }
1001     for (uint32_t i = 0; i < droppedDepth; i++) {
1002         DebuggerApi::DropLastFrame(vm_);
1003     }
1004     pauseOnNextByteCode_ = true;
1005     frontend_.RunIfWaitingForDebugger(vm_);
1006     debuggerState_ = DebuggerState::ENABLED;
1007     return DispatchResponse::Ok();
1008 }
1009 
CleanUpOnPaused()1010 void DebuggerImpl::CleanUpOnPaused()
1011 {
1012     runtime_->curObjectId_ = 0;
1013     runtime_->properties_.clear();
1014 
1015     callFrameHandlers_.clear();
1016     scopeObjects_.clear();
1017 }
1018 
Trim(const std::string & str)1019 std::string DebuggerImpl::Trim(const std::string &str)
1020 {
1021     std::string ret = str;
1022     // If ret has only ' ', remove all charactors.
1023     ret.erase(ret.find_last_not_of(' ') + 1);
1024     // If ret has only ' ', remove all charactors.
1025     ret.erase(0, ret.find_first_not_of(' '));
1026     return ret;
1027 }
1028 
GetExtractor(const JSPandaFile * jsPandaFile)1029 DebugInfoExtractor *DebuggerImpl::GetExtractor(const JSPandaFile *jsPandaFile)
1030 {
1031     return JSPandaFileManager::GetInstance()->GetJSPtExtractor(jsPandaFile);
1032 }
1033 
1034 // mainly used for breakpoints to match location
GetExtractor(const std::string & url)1035 DebugInfoExtractor *DebuggerImpl::GetExtractor(const std::string &url)
1036 {
1037     // match patch file first if it contains diff for the url, and currently only support the file
1038     // specified by the url change as a whole
1039     auto *extractor = DebuggerApi::GetPatchExtractor(vm_, url);
1040     if (extractor != nullptr) {
1041         return extractor;
1042     }
1043 
1044     std::string fileName;
1045     auto scriptFunc = [&fileName](const PtScript *script) -> bool {
1046         fileName = script->GetFileName();
1047         return true;
1048     };
1049     if (!MatchScripts(scriptFunc, url, ScriptMatchType::URL)) {
1050         return nullptr;
1051     }
1052 
1053     const JSPandaFile *jsPandaFile = JSPandaFileManager::GetInstance()->FindJSPandaFile(fileName.c_str()).get();
1054     if (jsPandaFile == nullptr) {
1055         return nullptr;
1056     }
1057     return JSPandaFileManager::GetInstance()->GetJSPtExtractor(jsPandaFile);
1058 }
1059 
GenerateCallFrames(std::vector<std::unique_ptr<CallFrame>> * callFrames)1060 bool DebuggerImpl::GenerateCallFrames(std::vector<std::unique_ptr<CallFrame>> *callFrames)
1061 {
1062     CallFrameId callFrameId = 0;
1063     auto walkerFunc = [this, &callFrameId, &callFrames](const FrameHandler *frameHandler) -> StackState {
1064         if (DebuggerApi::IsNativeMethod(frameHandler)) {
1065             LOG_DEBUGGER(INFO) << "GenerateCallFrames: Skip CFrame and Native method";
1066             return StackState::CONTINUE;
1067         }
1068         std::unique_ptr<CallFrame> callFrame = std::make_unique<CallFrame>();
1069         if (!GenerateCallFrame(callFrame.get(), frameHandler, callFrameId)) {
1070             if (callFrameId == 0) {
1071                 return StackState::FAILED;
1072             }
1073         } else {
1074             SaveCallFrameHandler(frameHandler);
1075             callFrames->emplace_back(std::move(callFrame));
1076             callFrameId++;
1077         }
1078         return StackState::CONTINUE;
1079     };
1080     return DebuggerApi::StackWalker(vm_, walkerFunc);
1081 }
1082 
SaveCallFrameHandler(const FrameHandler * frameHandler)1083 void DebuggerImpl::SaveCallFrameHandler(const FrameHandler *frameHandler)
1084 {
1085     auto handlerPtr = DebuggerApi::NewFrameHandler(vm_);
1086     *handlerPtr = *frameHandler;
1087     callFrameHandlers_.emplace_back(handlerPtr);
1088 }
1089 
GenerateCallFrame(CallFrame * callFrame,const FrameHandler * frameHandler,CallFrameId callFrameId)1090 bool DebuggerImpl::GenerateCallFrame(CallFrame *callFrame,
1091     const FrameHandler *frameHandler, CallFrameId callFrameId)
1092 {
1093     if (!frameHandler->HasFrame()) {
1094         return false;
1095     }
1096     Method *method = DebuggerApi::GetMethod(frameHandler);
1097     auto methodId = method->GetMethodId();
1098     const JSPandaFile *jsPandaFile = method->GetJSPandaFile();
1099     DebugInfoExtractor *extractor = GetExtractor(jsPandaFile);
1100     if (extractor == nullptr) {
1101         LOG_DEBUGGER(ERROR) << "GenerateCallFrame: extractor is null";
1102         return false;
1103     }
1104 
1105     // functionName
1106     std::string functionName = method->ParseFunctionName();
1107 
1108     // location
1109     std::unique_ptr<Location> location = std::make_unique<Location>();
1110     std::string url = extractor->GetSourceFile(methodId);
1111     auto scriptFunc = [&location](PtScript *script) -> bool {
1112         location->SetScriptId(script->GetScriptId());
1113         return true;
1114     };
1115     if (!MatchScripts(scriptFunc, url, ScriptMatchType::URL)) {
1116         LOG_DEBUGGER(ERROR) << "GenerateCallFrame: Unknown url: " << url;
1117         return false;
1118     }
1119     auto callbackLineFunc = [&location](int32_t line) -> bool {
1120         location->SetLine(line);
1121         return true;
1122     };
1123     auto callbackColumnFunc = [&location](int32_t column) -> bool {
1124         location->SetColumn(column);
1125         return true;
1126     };
1127     if (!extractor->MatchLineWithOffset(callbackLineFunc, methodId, DebuggerApi::GetBytecodeOffset(frameHandler)) ||
1128         !extractor->MatchColumnWithOffset(callbackColumnFunc, methodId, DebuggerApi::GetBytecodeOffset(frameHandler))) {
1129         LOG_DEBUGGER(ERROR) << "GenerateCallFrame: unknown offset: " << DebuggerApi::GetBytecodeOffset(frameHandler);
1130         return false;
1131     }
1132 
1133     // scopeChain & this
1134     std::unique_ptr<RemoteObject> thisObj = std::make_unique<RemoteObject>();
1135     thisObj->SetType(ObjectType::Undefined);
1136 
1137     JSThread *thread = vm_->GetJSThread();
1138     std::vector<std::unique_ptr<Scope>> scopeChain;
1139     scopeChain.emplace_back(GetLocalScopeChain(frameHandler, &thisObj));
1140     if (jsPandaFile != nullptr && !jsPandaFile->IsBundlePack() && jsPandaFile->IsNewVersion()) {
1141         JSHandle<JSTaggedValue> currentModule(thread, DebuggerApi::GetCurrentModule(vm_));
1142         if (currentModule->IsSourceTextModule()) { // CJS module is string
1143             scopeChain.emplace_back(GetModuleScopeChain());
1144         }
1145     }
1146     scopeChain.emplace_back(GetGlobalScopeChain());
1147 
1148     callFrame->SetCallFrameId(callFrameId)
1149         .SetFunctionName(functionName)
1150         .SetLocation(std::move(location))
1151         .SetUrl(url)
1152         .SetScopeChain(std::move(scopeChain))
1153         .SetThis(std::move(thisObj));
1154     return true;
1155 }
1156 
GetLocalScopeChain(const FrameHandler * frameHandler,std::unique_ptr<RemoteObject> * thisObj)1157 std::unique_ptr<Scope> DebuggerImpl::GetLocalScopeChain(const FrameHandler *frameHandler,
1158     std::unique_ptr<RemoteObject> *thisObj)
1159 {
1160     auto localScope = std::make_unique<Scope>();
1161 
1162     Method *method = DebuggerApi::GetMethod(frameHandler);
1163     auto methodId = method->GetMethodId();
1164     const JSPandaFile *jsPandaFile = method->GetJSPandaFile();
1165     DebugInfoExtractor *extractor = GetExtractor(jsPandaFile);
1166     if (extractor == nullptr) {
1167         LOG_DEBUGGER(ERROR) << "GetScopeChain: extractor is null";
1168         return localScope;
1169     }
1170 
1171     std::unique_ptr<RemoteObject> local = std::make_unique<RemoteObject>();
1172     Local<ObjectRef> localObj = ObjectRef::New(vm_);
1173     local->SetType(ObjectType::Object)
1174         .SetObjectId(runtime_->curObjectId_)
1175         .SetClassName(ObjectClassName::Object)
1176         .SetDescription(RemoteObject::ObjectDescription);
1177     auto *sp = DebuggerApi::GetSp(frameHandler);
1178     scopeObjects_[sp] = runtime_->curObjectId_;
1179     DebuggerApi::AddInternalProperties(vm_, localObj, ArkInternalValueType::Scope,  runtime_->internalObjects_);
1180     runtime_->properties_[runtime_->curObjectId_++] = Global<JSValueRef>(vm_, localObj);
1181 
1182     Local<JSValueRef> thisVal = JSNApiHelper::ToLocal<JSValueRef>(
1183         JSHandle<JSTaggedValue>(vm_->GetJSThread(), JSTaggedValue::Hole()));
1184     GetLocalVariables(frameHandler, methodId, jsPandaFile, thisVal, localObj);
1185     GetClosureVariables(frameHandler, thisVal, localObj);
1186     *thisObj = RemoteObject::FromTagged(vm_, thisVal);
1187     runtime_->CacheObjectIfNeeded(thisVal, (*thisObj).get());
1188 
1189     const LineNumberTable &lines = extractor->GetLineNumberTable(methodId);
1190     std::unique_ptr<Location> startLoc = std::make_unique<Location>();
1191     std::unique_ptr<Location> endLoc = std::make_unique<Location>();
1192     auto scriptFunc = [&startLoc, &endLoc, lines](PtScript *script) -> bool {
1193         startLoc->SetScriptId(script->GetScriptId())
1194             .SetLine(lines.front().line)
1195             .SetColumn(0);
1196         endLoc->SetScriptId(script->GetScriptId())
1197             .SetLine(lines.back().line + 1)
1198             .SetColumn(0);
1199         return true;
1200     };
1201     if (MatchScripts(scriptFunc, extractor->GetSourceFile(methodId), ScriptMatchType::URL)) {
1202         localScope->SetType(Scope::Type::Local())
1203             .SetObject(std::move(local))
1204             .SetStartLocation(std::move(startLoc))
1205             .SetEndLocation(std::move(endLoc));
1206     }
1207 
1208     return localScope;
1209 }
1210 
GetModuleScopeChain()1211 std::unique_ptr<Scope> DebuggerImpl::GetModuleScopeChain()
1212 {
1213     auto moduleScope = std::make_unique<Scope>();
1214 
1215     std::unique_ptr<RemoteObject> module = std::make_unique<RemoteObject>();
1216     Local<ObjectRef> moduleObj = ObjectRef::New(vm_);
1217     module->SetType(ObjectType::Object)
1218         .SetObjectId(runtime_->curObjectId_)
1219         .SetClassName(ObjectClassName::Object)
1220         .SetDescription(RemoteObject::ObjectDescription);
1221     moduleScope->SetType(Scope::Type::Module()).SetObject(std::move(module));
1222     DebuggerApi::AddInternalProperties(vm_, moduleObj, ArkInternalValueType::Scope,  runtime_->internalObjects_);
1223     runtime_->properties_[runtime_->curObjectId_++] = Global<JSValueRef>(vm_, moduleObj);
1224     JSThread *thread = vm_->GetJSThread();
1225     JSHandle<JSTaggedValue> currentModule(thread, DebuggerApi::GetCurrentModule(vm_));
1226     DebuggerApi::GetLocalExportVariables(vm_, moduleObj, currentModule, false);
1227     DebuggerApi::GetIndirectExportVariables(vm_, moduleObj, currentModule);
1228     DebuggerApi::GetImportVariables(vm_, moduleObj, currentModule);
1229     return moduleScope;
1230 }
1231 
GetLocalVariables(const FrameHandler * frameHandler,panda_file::File::EntityId methodId,const JSPandaFile * jsPandaFile,Local<JSValueRef> & thisVal,Local<ObjectRef> & localObj)1232 void DebuggerImpl::GetLocalVariables(const FrameHandler *frameHandler, panda_file::File::EntityId methodId,
1233     const JSPandaFile *jsPandaFile, Local<JSValueRef> &thisVal, Local<ObjectRef> &localObj)
1234 {
1235     auto *extractor = GetExtractor(jsPandaFile);
1236     Local<JSValueRef> value = JSValueRef::Undefined(vm_);
1237     // in case of arrow function, which doesn't have this in local variable table
1238     for (const auto &localVariableInfo : extractor->GetLocalVariableTable(methodId)) {
1239         std::string varName = localVariableInfo.name;
1240         int32_t regIndex = localVariableInfo.regNumber;
1241         uint32_t bcOffset = DebuggerApi::GetBytecodeOffset(frameHandler);
1242         // if the bytecodeOffset is not in the range of the variable's scope,
1243         // which is indicated as [start_offset, end_offset), ignore it.
1244         if (!IsWithinVariableScope(localVariableInfo, bcOffset)) {
1245             continue;
1246         }
1247 
1248         if (varName == "4newTarget" || varName == "0this" || varName == "0newTarget" || varName == "0funcObj") {
1249             continue;
1250         }
1251 
1252         value = DebuggerApi::GetVRegValue(vm_, frameHandler, regIndex);
1253         if (varName == "this") {
1254             LOG_DEBUGGER(INFO) << "find 'this' in local variable table";
1255             thisVal = value;
1256             continue;
1257         }
1258         Local<JSValueRef> name = JSValueRef::Undefined(vm_);
1259         if (varName == "4funcObj") {
1260             if (value->IsFunction()) {
1261                 auto funcName = Local<FunctionRef>(value)->GetName(vm_)->ToString();
1262                 name = StringRef::NewFromUtf8(vm_, funcName.c_str());
1263             } else {
1264                 continue;
1265             }
1266         } else {
1267             name = StringRef::NewFromUtf8(vm_, varName.c_str());
1268         }
1269         PropertyAttribute descriptor(value, true, true, true);
1270         localObj->DefineProperty(vm_, name, descriptor);
1271     }
1272 }
1273 
IsWithinVariableScope(const LocalVariableInfo & localVariableInfo,uint32_t bcOffset)1274 bool DebuggerImpl::IsWithinVariableScope(const LocalVariableInfo &localVariableInfo, uint32_t bcOffset)
1275 {
1276     return bcOffset >= localVariableInfo.startOffset && bcOffset < localVariableInfo.endOffset;
1277 }
1278 
GetClosureVariables(const FrameHandler * frameHandler,Local<JSValueRef> & thisVal,Local<ObjectRef> & localObj)1279 void DebuggerImpl::GetClosureVariables(const FrameHandler *frameHandler, Local<JSValueRef> &thisVal,
1280     Local<ObjectRef> &localObj)
1281 {
1282     JSThread *thread = vm_->GetJSThread();
1283     JSHandle<JSTaggedValue> envHandle = JSHandle<JSTaggedValue>(thread, DebuggerApi::GetEnv(frameHandler));
1284     JSTaggedValue env = envHandle.GetTaggedValue();
1285     if (env.IsTaggedArray() && DebuggerApi::GetBytecodeOffset(frameHandler) != 0) {
1286         LexicalEnv *lexEnv = LexicalEnv::Cast(env.GetTaggedObject());
1287         if (lexEnv->GetScopeInfo().IsHole()) {
1288             return;
1289         }
1290         auto *scopeDebugInfo = reinterpret_cast<ScopeDebugInfo *>(JSNativePointer::Cast(
1291             lexEnv->GetScopeInfo().GetTaggedObject())->GetExternalPointer());
1292         for (const auto &[varName, slot] : scopeDebugInfo->scopeInfo) {
1293             // skip possible duplicate variables both in local variable table and env
1294             if (varName == "4newTarget") {
1295                 continue;
1296             }
1297             env = envHandle.GetTaggedValue();
1298             lexEnv = LexicalEnv::Cast(env.GetTaggedObject());
1299             ASSERT(slot < lexEnv->GetLength() - LexicalEnv::RESERVED_ENV_LENGTH);
1300             Local<JSValueRef> value = JSNApiHelper::ToLocal<JSValueRef>(
1301                 JSHandle<JSTaggedValue>(thread, lexEnv->GetProperties(slot)));
1302             if (varName == "this") {
1303                 if (thisVal->IsHole()) {
1304                     LOG_DEBUGGER(INFO) << "find 'this' in current lexical env";
1305                     thisVal = value;
1306                 }
1307                 continue;
1308             }
1309             Local<JSValueRef> name = StringRef::NewFromUtf8(vm_, varName.c_str());
1310             if (value->IsHole()) {
1311                 value = JSValueRef::Undefined(vm_);
1312             }
1313             PropertyAttribute descriptor(value, true, true, true);
1314             localObj->DefineProperty(vm_, name, descriptor);
1315         }
1316     }
1317 
1318     // if 'this' is not in current lexical env, we should try to find from it's parent env
1319     if (thisVal->IsHole()) {
1320         auto [level, slot] = DebuggerApi::GetLevelSlot(frameHandler, "this");
1321         if (LIKELY(level != -1)) {
1322             LOG_DEBUGGER(INFO) << "find 'this' in parent lexical env";
1323             thisVal = DebuggerApi::GetProperties(vm_, frameHandler, level, slot);
1324         } else {
1325             thisVal = JSValueRef::Undefined(vm_);
1326         }
1327     }
1328 }
1329 
GetGlobalScopeChain()1330 std::unique_ptr<Scope> DebuggerImpl::GetGlobalScopeChain()
1331 {
1332     auto globalScope = std::make_unique<Scope>();
1333 
1334     std::unique_ptr<RemoteObject> global = std::make_unique<RemoteObject>();
1335     Local<ObjectRef> globalObj = ObjectRef::New(vm_);
1336     global->SetType(ObjectType::Object)
1337         .SetObjectId(runtime_->curObjectId_)
1338         .SetClassName(ObjectClassName::Global)
1339         .SetDescription(RemoteObject::GlobalDescription);
1340     globalScope->SetType(Scope::Type::Global()).SetObject(std::move(global));
1341     globalObj = JSNApi::GetGlobalObject(vm_);
1342     DebuggerApi::AddInternalProperties(vm_, globalObj, ArkInternalValueType::Scope,  runtime_->internalObjects_);
1343     runtime_->properties_[runtime_->curObjectId_++] = Global<JSValueRef>(vm_, globalObj);
1344     return globalScope;
1345 }
1346 
UpdateScopeObject(const FrameHandler * frameHandler,std::string_view varName,Local<JSValueRef> newVal)1347 void DebuggerImpl::UpdateScopeObject(const FrameHandler *frameHandler,
1348     std::string_view varName, Local<JSValueRef> newVal)
1349 {
1350     auto *sp = DebuggerApi::GetSp(frameHandler);
1351     auto iter = scopeObjects_.find(sp);
1352     if (iter == scopeObjects_.end()) {
1353         LOG_DEBUGGER(ERROR) << "UpdateScopeObject: object not found";
1354         return;
1355     }
1356 
1357     auto objectId = iter->second;
1358     Local<ObjectRef> localObj = runtime_->properties_[objectId].ToLocal(vm_);
1359     Local<JSValueRef> name = StringRef::NewFromUtf8(vm_, varName.data());
1360     if (localObj->Has(vm_, name)) {
1361         LOG_DEBUGGER(DEBUG) << "UpdateScopeObject: set new value";
1362         PropertyAttribute descriptor(newVal, true, true, true);
1363         localObj->DefineProperty(vm_, name, descriptor);
1364     } else {
1365         LOG_DEBUGGER(ERROR) << "UpdateScopeObject: not found " << varName;
1366     }
1367 }
1368 
ClearSingleStepper()1369 void DebuggerImpl::ClearSingleStepper()
1370 {
1371     // ClearSingleStepper is originally called from Function::Call, if current depth is 0, then it is safe to reset
1372     if (singleStepper_ != nullptr && DebuggerApi::GetStackDepth(vm_) == 0) {
1373         singleStepper_.reset();
1374     }
1375 }
1376 
CmptEvaluateValue(CallFrameId callFrameId,const std::string & expression,std::unique_ptr<RemoteObject> * result)1377 std::optional<std::string> DebuggerImpl::CmptEvaluateValue(CallFrameId callFrameId, const std::string &expression,
1378     std::unique_ptr<RemoteObject> *result)
1379 {
1380     if (DebuggerApi::IsNativeMethod(vm_)) {
1381         *result = RemoteObject::FromTagged(vm_,
1382             Exception::EvalError(vm_, StringRef::NewFromUtf8(vm_, "Native Frame not support.")));
1383         return "Native Frame not support.";
1384     }
1385     DebugInfoExtractor *extractor = GetExtractor(DebuggerApi::GetJSPandaFile(vm_));
1386     if (extractor == nullptr) {
1387         *result = RemoteObject::FromTagged(vm_,
1388             Exception::EvalError(vm_, StringRef::NewFromUtf8(vm_, "Internal error.")));
1389         return "Internal error.";
1390     }
1391     std::string varName = expression;
1392     std::string varValue;
1393     std::string::size_type indexEqual = expression.find_first_of('=', 0);
1394     if (indexEqual != std::string::npos) {
1395         varName = Trim(expression.substr(0, indexEqual));
1396         varValue = Trim(expression.substr(indexEqual + 1, expression.length()));
1397     }
1398 
1399     Local<StringRef> name = StringRef::NewFromUtf8(vm_, varName.c_str());
1400     FrameHandler *frameHandler = callFrameHandlers_[callFrameId].get();
1401     if (varValue.empty()) {
1402         Local<JSValueRef> ret = DebuggerExecutor::GetValue(vm_, frameHandler, name);
1403         if (!ret.IsEmpty()) {
1404             *result = RemoteObject::FromTagged(vm_, ret);
1405             runtime_->CacheObjectIfNeeded(ret, (*result).get());
1406             return {};
1407         }
1408     } else {
1409         Local<JSValueRef> value = ConvertToLocal(varValue);
1410         if (value.IsEmpty()) {
1411             return "Unsupported expression.";
1412         }
1413         JsDebuggerManager *mgr = vm_->GetJsDebuggerManager();
1414         mgr->SetEvalFrameHandler(callFrameHandlers_[callFrameId]);
1415         bool ret = DebuggerExecutor::SetValue(vm_, frameHandler, name, value);
1416         mgr->SetEvalFrameHandler(nullptr);
1417         if (ret) {
1418             *result = RemoteObject::FromTagged(vm_, value);
1419             return {};
1420         }
1421     }
1422 
1423     *result = RemoteObject::FromTagged(vm_,
1424         Exception::EvalError(vm_, StringRef::NewFromUtf8(vm_, "Unsupported expression.")));
1425     return "Unsupported expression.";
1426 }
1427 
ConvertToLocal(const std::string & varValue)1428 Local<JSValueRef> DebuggerImpl::ConvertToLocal(const std::string &varValue)
1429 {
1430     Local<JSValueRef> taggedValue;
1431     if (varValue == "false") {
1432         taggedValue = JSValueRef::False(vm_);
1433     } else if (varValue == "true") {
1434         taggedValue = JSValueRef::True(vm_);
1435     } else if (varValue == "undefined") {
1436         taggedValue = JSValueRef::Undefined(vm_);
1437     } else if (varValue[0] == '\"' && varValue[varValue.length() - 1] == '\"') {
1438         // 2 : 2 means length
1439         taggedValue = StringRef::NewFromUtf8(vm_, varValue.substr(1, varValue.length() - 2).c_str());
1440     } else {
1441         auto begin = reinterpret_cast<const uint8_t *>((varValue.c_str()));
1442         auto end = begin + varValue.length();  // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
1443         double d = DebuggerApi::StringToDouble(begin, end, 0);
1444         if (!std::isnan(d)) {
1445             taggedValue = NumberRef::New(vm_, d);
1446         }
1447     }
1448     return taggedValue;
1449 }
1450 
DecodeAndCheckBase64(const std::string & src,std::vector<uint8_t> & dest)1451 bool DebuggerImpl::DecodeAndCheckBase64(const std::string &src, std::vector<uint8_t> &dest)
1452 {
1453     dest.resize(PtBase64::DecodedSize(src.size()));
1454     auto [numOctets, done] = PtBase64::Decode(dest.data(), src.data(), src.size());
1455     dest.resize(numOctets);
1456     if ((done && numOctets > panda_file::File::MAGIC_SIZE) &&
1457         memcmp(dest.data(), panda_file::File::MAGIC.data(), panda_file::File::MAGIC_SIZE) == 0) {
1458         return true;
1459     }
1460     return false;
1461 }
1462 }  // namespace panda::ecmascript::tooling
1463