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 ¶ms, 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 ¶ms,
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 ¶ms,
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 ¶ms, 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 ¶ms)
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 ¶ms)
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 ¶ms,
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 ¶ms)
790 {
791 pauseOnException_ = params.GetState();
792 return DispatchResponse::Ok();
793 }
794
StepInto(const StepIntoParams & params)795 DispatchResponse DebuggerImpl::StepInto([[maybe_unused]] const StepIntoParams ¶ms)
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 ¶ms)
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 ¶ms)
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 ¶ms)
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