1 /*
2 * Copyright (c) 2021-2023 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 "tooling/base/pt_base64.h"
19 #include "tooling/base/pt_events.h"
20 #include "tooling/base/pt_params.h"
21 #include "tooling/base/pt_returns.h"
22 #include "tooling/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 returnNative_ = std::bind(&DebuggerImpl::NotifyReturnNative, this);
55 vm_->GetJsDebuggerManager()->SetLocalScopeUpdater(&updaterFunc_);
56 vm_->GetJsDebuggerManager()->SetStepperFunc(&stepperFunc_);
57 vm_->GetJsDebuggerManager()->SetJSReturnNativeFunc(&returnNative_);
58 }
59
~DebuggerImpl()60 DebuggerImpl::~DebuggerImpl()
61 {
62 // in worker thread, it will ~DebuggerImpl before release worker thread
63 // after ~DebuggerImpl, it maybe call these methods
64 vm_->GetJsDebuggerManager()->SetLocalScopeUpdater(nullptr);
65 vm_->GetJsDebuggerManager()->SetStepperFunc(nullptr);
66 vm_->GetJsDebuggerManager()->SetJSReturnNativeFunc(nullptr);
67 DebuggerApi::DestroyJSDebugger(jsDebugger_);
68 }
69
NotifyScriptParsed(ScriptId scriptId,const std::string & fileName,std::string_view entryPoint)70 bool DebuggerImpl::NotifyScriptParsed(ScriptId scriptId, const std::string &fileName, std::string_view entryPoint)
71 {
72 #if !defined(PANDA_TARGET_WINDOWS) && !defined(PANDA_TARGET_MACOS) \
73 && !defined(PANDA_TARGET_ANDROID) && !defined(PANDA_TARGET_IOS) \
74 && !defined(PANDA_TARGET_LINUX)
75 if (fileName.substr(0, DATA_APP_PATH.length()) != DATA_APP_PATH) {
76 LOG_DEBUGGER(DEBUG) << "NotifyScriptParsed: unsupport file: " << fileName;
77 return false;
78 }
79 #endif
80
81 // The release application does not require scriptParsed
82 if (!vm_->GetJsDebuggerManager()->IsDebugApp()) {
83 return false;
84 }
85
86 const JSPandaFile *jsPandaFile = JSPandaFileManager::GetInstance()->FindJSPandaFile(fileName.c_str()).get();
87 if (jsPandaFile == nullptr) {
88 LOG_DEBUGGER(ERROR) << "NotifyScriptParsed: unknown file: " << fileName;
89 return false;
90 }
91
92 DebugInfoExtractor *extractor = GetExtractor(jsPandaFile);
93 if (extractor == nullptr) {
94 LOG_DEBUGGER(ERROR) << "NotifyScriptParsed: Unsupported file: " << fileName;
95 return false;
96 }
97
98 const char *recordName = entryPoint.data();
99 auto mainMethodIndex = panda_file::File::EntityId(jsPandaFile->GetMainMethodIndex(recordName));
100 const std::string &source = extractor->GetSourceCode(mainMethodIndex);
101 const std::string &url = extractor->GetSourceFile(mainMethodIndex);
102 if (source.empty()) {
103 LOG_DEBUGGER(WARN) << "NotifyScriptParsed: invalid debugger file: " << fileName;
104 return false;
105 }
106
107 recordNames_[url].insert(recordName);
108
109 // if load module, it needs to check whether clear singlestepper_
110 ClearSingleStepper();
111 if (MatchUrlAndFileName(url, fileName)) {
112 LOG_DEBUGGER(WARN) << "NotifyScriptParsed: already loaded: " << url;
113 return false;
114 }
115
116 // Notify script parsed event
117 std::unique_ptr<PtScript> script = std::make_unique<PtScript>(scriptId, fileName, url, source);
118
119 frontend_.ScriptParsed(vm_, *script);
120
121 // Store parsed script in map
122 scripts_[script->GetScriptId()] = std::move(script);
123 return true;
124 }
125
NotifyNativeOut()126 bool DebuggerImpl::NotifyNativeOut()
127 {
128 if (nativeOutPause_) {
129 nativeOutPause_ = false;
130 return true;
131 }
132 return false;
133 }
134
NotifySingleStep(const JSPtLocation & location)135 bool DebuggerImpl::NotifySingleStep(const JSPtLocation &location)
136 {
137 if (UNLIKELY(pauseOnNextByteCode_)) {
138 if (IsSkipLine(location)) {
139 return false;
140 }
141 pauseOnNextByteCode_ = false;
142 LOG_DEBUGGER(INFO) << "StepComplete: pause on next bytecode";
143 return true;
144 }
145
146 if (LIKELY(singleStepper_ == nullptr)) {
147 return false;
148 }
149
150 // step not complete
151 if (!singleStepper_->StepComplete(location.GetBytecodeOffset())) {
152 return false;
153 }
154
155 // skip unknown file or special line -1
156 if (IsSkipLine(location)) {
157 return false;
158 }
159
160 singleStepper_.reset();
161 LOG_DEBUGGER(INFO) << "StepComplete: pause on current byte_code";
162 if (!DebuggerApi::GetSingleStepStatus(jsDebugger_)) {
163 DebuggerApi::SetSingleStepStatus(jsDebugger_, true);
164 }
165 return true;
166 }
167
IsSkipLine(const JSPtLocation & location)168 bool DebuggerImpl::IsSkipLine(const JSPtLocation &location)
169 {
170 DebugInfoExtractor *extractor = nullptr;
171 const auto *jsPandaFile = location.GetJsPandaFile();
172 auto scriptFunc = [this, &extractor, jsPandaFile](PtScript *) -> bool {
173 extractor = GetExtractor(jsPandaFile);
174 return true;
175 };
176
177 // In hot reload scenario, use the base js panda file instead
178 const auto &fileName = DebuggerApi::GetBaseJSPandaFile(vm_, jsPandaFile)->GetJSPandaFileDesc();
179 if (!MatchScripts(scriptFunc, fileName.c_str(), ScriptMatchType::FILE_NAME) || extractor == nullptr) {
180 LOG_DEBUGGER(INFO) << "StepComplete: skip unknown file " << fileName.c_str();
181 return true;
182 }
183
184 auto callbackFunc = [](int32_t line) -> bool {
185 return line == DebugInfoExtractor::SPECIAL_LINE_MARK;
186 };
187 panda_file::File::EntityId methodId = location.GetMethodId();
188 uint32_t offset = location.GetBytecodeOffset();
189 if (extractor->MatchLineWithOffset(callbackFunc, methodId, offset)) {
190 LOG_DEBUGGER(INFO) << "StepComplete: skip -1";
191 return true;
192 }
193
194 return false;
195 }
196
CheckPauseOnException()197 bool DebuggerImpl::CheckPauseOnException()
198 {
199 if (pauseOnException_ == PauseOnExceptionsState::NONE) {
200 return false;
201 }
202 if (pauseOnException_ == PauseOnExceptionsState::UNCAUGHT) {
203 if (DebuggerApi::IsExceptionCaught(vm_)) {
204 return false;
205 }
206 }
207 return true;
208 }
209
NotifyPaused(std::optional<JSPtLocation> location,PauseReason reason)210 void DebuggerImpl::NotifyPaused(std::optional<JSPtLocation> location, PauseReason reason)
211 {
212 if (skipAllPausess_) {
213 return;
214 }
215
216 if (location.has_value() && !breakpointsState_) {
217 return;
218 }
219
220 if (reason == EXCEPTION && !CheckPauseOnException()) {
221 return;
222 }
223
224 Local<JSValueRef> exception = DebuggerApi::GetAndClearException(vm_);
225
226 std::vector<std::string> hitBreakpoints;
227 if (location.has_value()) {
228 BreakpointDetails detail;
229 DebugInfoExtractor *extractor = nullptr;
230 auto scriptFunc = [this, &location, &detail, &extractor](PtScript *script) -> bool {
231 detail.url_ = script->GetUrl();
232 extractor = GetExtractor(location->GetJsPandaFile());
233 return true;
234 };
235 auto callbackLineFunc = [&detail](int32_t line) -> bool {
236 detail.line_ = line;
237 return true;
238 };
239 auto callbackColumnFunc = [&detail](int32_t column) -> bool {
240 detail.column_ = column;
241 return true;
242 };
243 panda_file::File::EntityId methodId = location->GetMethodId();
244 uint32_t offset = location->GetBytecodeOffset();
245 // In merge abc scenario, need to use the source file to match to get right url
246 if (!MatchScripts(scriptFunc, location->GetSourceFile(), ScriptMatchType::URL) ||
247 extractor == nullptr || !extractor->MatchLineWithOffset(callbackLineFunc, methodId, offset) ||
248 !extractor->MatchColumnWithOffset(callbackColumnFunc, methodId, offset)) {
249 LOG_DEBUGGER(ERROR) << "NotifyPaused: unknown file " << location->GetSourceFile();
250 return;
251 }
252 hitBreakpoints.emplace_back(BreakpointDetails::ToString(detail));
253 }
254
255 // Do something cleaning on paused
256 CleanUpOnPaused();
257
258 // Notify paused event
259 std::vector<std::unique_ptr<CallFrame>> callFrames;
260 if (!GenerateCallFrames(&callFrames, true)) {
261 LOG_DEBUGGER(ERROR) << "NotifyPaused: GenerateCallFrames failed";
262 return;
263 }
264 tooling::Paused paused;
265 if (reason == DEBUGGERSTMT) {
266 BreakpointDetails detail;
267 hitBreakpoints.emplace_back(BreakpointDetails::ToString(detail));
268 paused.SetCallFrames(std::move(callFrames))
269 .SetReason(PauseReason::OTHER)
270 .SetHitBreakpoints(std::move(hitBreakpoints));
271 } else {
272 paused.SetCallFrames(std::move(callFrames)).SetReason(reason).SetHitBreakpoints(std::move(hitBreakpoints));
273 }
274 if (reason == EXCEPTION && exception->IsError()) {
275 std::unique_ptr<RemoteObject> tmpException = RemoteObject::FromTagged(vm_, exception);
276 paused.SetData(std::move(tmpException));
277 }
278 frontend_.Paused(vm_, paused);
279 if (reason != BREAK_ON_START && reason != NATIVE_OUT) {
280 singleStepper_.reset();
281 }
282 nativeOutPause_ = false;
283 debuggerState_ = DebuggerState::PAUSED;
284 frontend_.WaitForDebugger(vm_);
285 DebuggerApi::SetException(vm_, exception);
286 }
287
IsUserCode(const void * nativeAddress)288 bool DebuggerImpl::IsUserCode(const void *nativeAddress)
289 {
290 uint64_t nativeEntry = reinterpret_cast<uint64_t>(nativeAddress);
291 for (const auto &nativeRange : nativeRanges_) {
292 if (nativeEntry >= nativeRange.GetStart() && nativeEntry <= nativeRange.GetEnd()) {
293 return true;
294 }
295 }
296 return false;
297 }
298
NotifyNativeCalling(const void * nativeAddress)299 void DebuggerImpl::NotifyNativeCalling(const void *nativeAddress)
300 {
301 // native calling only after step into should be reported
302 if (singleStepper_ != nullptr &&
303 singleStepper_->GetStepperType() == StepperType::STEP_INTO) {
304 tooling::NativeCalling nativeCalling;
305 nativeCalling.SetIntoStatus(true);
306 nativeCalling.SetNativeAddress(nativeAddress);
307 frontend_.NativeCalling(vm_, nativeCalling);
308 frontend_.WaitForDebugger(vm_);
309 }
310
311 if (mixStackEnabled_ && IsUserCode(nativeAddress)) {
312 tooling::MixedStack mixedStack;
313 nativePointer_ = DebuggerApi::GetNativePointer(vm_);
314 mixedStack.SetNativePointers(nativePointer_);
315 std::vector<std::unique_ptr<CallFrame>> callFrames;
316 if (GenerateCallFrames(&callFrames, false)) {
317 mixedStack.SetCallFrames(std::move(callFrames));
318 }
319 frontend_.MixedStack(vm_, mixedStack);
320 }
321 }
322
NotifyNativeReturn(const void * nativeAddress)323 void DebuggerImpl::NotifyNativeReturn(const void *nativeAddress)
324 {
325 if (mixStackEnabled_ && IsUserCode(nativeAddress)) {
326 nativeOutPause_ = true;
327 }
328 }
329
NotifyReturnNative()330 void DebuggerImpl::NotifyReturnNative()
331 {
332 if (mixStackEnabled_) {
333 tooling::MixedStack mixedStack;
334 nativePointer_ = DebuggerApi::GetNativePointer(vm_);
335 if (nativePointer_.empty()) {
336 return;
337 }
338 mixedStack.SetNativePointers(nativePointer_);
339 std::vector<std::unique_ptr<CallFrame>> callFrames;
340 if (GenerateCallFrames(&callFrames, false)) {
341 mixedStack.SetCallFrames(std::move(callFrames));
342 }
343 frontend_.MixedStack(vm_, mixedStack);
344 }
345 }
346
347 // only use for test case
SetDebuggerState(DebuggerState debuggerState)348 void DebuggerImpl::SetDebuggerState(DebuggerState debuggerState)
349 {
350 debuggerState_ = debuggerState;
351 }
352
NotifyHandleProtocolCommand()353 void DebuggerImpl::NotifyHandleProtocolCommand()
354 {
355 auto *handler = vm_->GetJsDebuggerManager()->GetDebuggerHandler();
356 handler->ProcessCommand();
357 }
358
Dispatch(const DispatchRequest & request)359 void DebuggerImpl::DispatcherImpl::Dispatch(const DispatchRequest &request)
360 {
361 static std::unordered_map<std::string, AgentHandler> dispatcherTable {
362 { "continueToLocation", &DebuggerImpl::DispatcherImpl::ContinueToLocation },
363 { "enable", &DebuggerImpl::DispatcherImpl::Enable },
364 { "disable", &DebuggerImpl::DispatcherImpl::Disable },
365 { "evaluateOnCallFrame", &DebuggerImpl::DispatcherImpl::EvaluateOnCallFrame },
366 { "getPossibleBreakpoints", &DebuggerImpl::DispatcherImpl::GetPossibleBreakpoints },
367 { "getScriptSource", &DebuggerImpl::DispatcherImpl::GetScriptSource },
368 { "pause", &DebuggerImpl::DispatcherImpl::Pause },
369 { "removeBreakpoint", &DebuggerImpl::DispatcherImpl::RemoveBreakpoint },
370 { "resume", &DebuggerImpl::DispatcherImpl::Resume },
371 { "setAsyncCallStackDepth", &DebuggerImpl::DispatcherImpl::SetAsyncCallStackDepth },
372 { "setBreakpointByUrl", &DebuggerImpl::DispatcherImpl::SetBreakpointByUrl },
373 { "setBreakpointsActive", &DebuggerImpl::DispatcherImpl::SetBreakpointsActive },
374 { "setPauseOnExceptions", &DebuggerImpl::DispatcherImpl::SetPauseOnExceptions },
375 { "setSkipAllPauses", &DebuggerImpl::DispatcherImpl::SetSkipAllPauses },
376 { "stepInto", &DebuggerImpl::DispatcherImpl::StepInto },
377 { "stepOut", &DebuggerImpl::DispatcherImpl::StepOut },
378 { "stepOver", &DebuggerImpl::DispatcherImpl::StepOver },
379 { "setMixedDebugEnabled", &DebuggerImpl::DispatcherImpl::SetMixedDebugEnabled },
380 { "setBlackboxPatterns", &DebuggerImpl::DispatcherImpl::SetBlackboxPatterns },
381 { "replyNativeCalling", &DebuggerImpl::DispatcherImpl::ReplyNativeCalling },
382 { "getPossibleAndSetBreakpointByUrl", &DebuggerImpl::DispatcherImpl::GetPossibleAndSetBreakpointByUrl },
383 { "dropFrame", &DebuggerImpl::DispatcherImpl::DropFrame },
384 { "setNativeRange", &DebuggerImpl::DispatcherImpl::SetNativeRange },
385 { "resetSingleStepper", &DebuggerImpl::DispatcherImpl::ResetSingleStepper },
386 { "clientDisconnect", &DebuggerImpl::DispatcherImpl::ClientDisconnect },
387 { "callFunctionOn", &DebuggerImpl::DispatcherImpl::CallFunctionOn }
388 };
389
390 const std::string &method = request.GetMethod();
391 LOG_DEBUGGER(DEBUG) << "dispatch [" << method << "] to DebuggerImpl";
392 auto entry = dispatcherTable.find(method);
393 if (entry != dispatcherTable.end() && entry->second != nullptr) {
394 (this->*(entry->second))(request);
395 } else {
396 SendResponse(request, DispatchResponse::Fail("Unknown method: " + method));
397 }
398 }
399
ContinueToLocation(const DispatchRequest & request)400 void DebuggerImpl::DispatcherImpl::ContinueToLocation(const DispatchRequest &request)
401 {
402 std::unique_ptr<ContinueToLocationParams> params = ContinueToLocationParams::Create(request.GetParams());
403 if (params == nullptr) {
404 SendResponse(request, DispatchResponse::Fail("wrong params"));
405 return;
406 }
407
408 DispatchResponse response = debugger_->ContinueToLocation(*params);
409 SendResponse(request, response);
410 }
411
Enable(const DispatchRequest & request)412 void DebuggerImpl::DispatcherImpl::Enable(const DispatchRequest &request)
413 {
414 std::unique_ptr<EnableParams> params = EnableParams::Create(request.GetParams());
415 if (params == nullptr) {
416 SendResponse(request, DispatchResponse::Fail("wrong params"));
417 return;
418 }
419
420 UniqueDebuggerId id;
421 DispatchResponse response = debugger_->Enable(*params, &id);
422
423 EnableReturns result(id);
424 SendResponse(request, response, result);
425 }
426
Disable(const DispatchRequest & request)427 void DebuggerImpl::DispatcherImpl::Disable(const DispatchRequest &request)
428 {
429 DispatchResponse response = debugger_->Disable();
430 SendResponse(request, response);
431 }
432
EvaluateOnCallFrame(const DispatchRequest & request)433 void DebuggerImpl::DispatcherImpl::EvaluateOnCallFrame(const DispatchRequest &request)
434 {
435 std::unique_ptr<EvaluateOnCallFrameParams> params = EvaluateOnCallFrameParams::Create(request.GetParams());
436 if (params == nullptr) {
437 SendResponse(request, DispatchResponse::Fail("wrong params"));
438 return;
439 }
440 std::unique_ptr<RemoteObject> result1;
441 DispatchResponse response = debugger_->EvaluateOnCallFrame(*params, &result1);
442 if (result1 == nullptr) {
443 SendResponse(request, response);
444 return;
445 }
446
447 EvaluateOnCallFrameReturns result(std::move(result1));
448 SendResponse(request, response, result);
449 }
450
GetPossibleBreakpoints(const DispatchRequest & request)451 void DebuggerImpl::DispatcherImpl::GetPossibleBreakpoints(const DispatchRequest &request)
452 {
453 std::unique_ptr<GetPossibleBreakpointsParams> params = GetPossibleBreakpointsParams::Create(request.GetParams());
454 if (params == nullptr) {
455 SendResponse(request, DispatchResponse::Fail("wrong params"));
456 return;
457 }
458 std::vector<std::unique_ptr<BreakLocation>> locations;
459 DispatchResponse response = debugger_->GetPossibleBreakpoints(*params, &locations);
460 GetPossibleBreakpointsReturns result(std::move(locations));
461 SendResponse(request, response, result);
462 }
463
GetScriptSource(const DispatchRequest & request)464 void DebuggerImpl::DispatcherImpl::GetScriptSource(const DispatchRequest &request)
465 {
466 std::unique_ptr<GetScriptSourceParams> params = GetScriptSourceParams::Create(request.GetParams());
467 if (params == nullptr) {
468 SendResponse(request, DispatchResponse::Fail("wrong params"));
469 return;
470 }
471 std::string source;
472 DispatchResponse response = debugger_->GetScriptSource(*params, &source);
473 GetScriptSourceReturns result(source);
474 SendResponse(request, response, result);
475 }
476
Pause(const DispatchRequest & request)477 void DebuggerImpl::DispatcherImpl::Pause(const DispatchRequest &request)
478 {
479 DispatchResponse response = debugger_->Pause();
480 SendResponse(request, response);
481 }
482
RemoveBreakpoint(const DispatchRequest & request)483 void DebuggerImpl::DispatcherImpl::RemoveBreakpoint(const DispatchRequest &request)
484 {
485 std::unique_ptr<RemoveBreakpointParams> params = RemoveBreakpointParams::Create(request.GetParams());
486 if (params == nullptr) {
487 SendResponse(request, DispatchResponse::Fail("wrong params"));
488 return;
489 }
490 DispatchResponse response = debugger_->RemoveBreakpoint(*params);
491 SendResponse(request, response);
492 }
493
Resume(const DispatchRequest & request)494 void DebuggerImpl::DispatcherImpl::Resume(const DispatchRequest &request)
495 {
496 std::unique_ptr<ResumeParams> params = ResumeParams::Create(request.GetParams());
497 if (params == nullptr) {
498 SendResponse(request, DispatchResponse::Fail("wrong params"));
499 return;
500 }
501 DispatchResponse response = debugger_->Resume(*params);
502 SendResponse(request, response);
503 }
504
SetAsyncCallStackDepth(const DispatchRequest & request)505 void DebuggerImpl::DispatcherImpl::SetAsyncCallStackDepth(const DispatchRequest &request)
506 {
507 DispatchResponse response = debugger_->SetAsyncCallStackDepth();
508 SendResponse(request, response);
509 }
510
SetBreakpointByUrl(const DispatchRequest & request)511 void DebuggerImpl::DispatcherImpl::SetBreakpointByUrl(const DispatchRequest &request)
512 {
513 std::unique_ptr<SetBreakpointByUrlParams> params = SetBreakpointByUrlParams::Create(request.GetParams());
514 if (params == nullptr) {
515 SendResponse(request, DispatchResponse::Fail("wrong params"));
516 return;
517 }
518
519 std::string outId;
520 std::vector<std::unique_ptr<Location>> outLocations;
521 DispatchResponse response = debugger_->SetBreakpointByUrl(*params, &outId, &outLocations);
522 SetBreakpointByUrlReturns result(outId, std::move(outLocations));
523 SendResponse(request, response, result);
524 }
525
SetBreakpointsActive(const DispatchRequest & request)526 void DebuggerImpl::DispatcherImpl::SetBreakpointsActive(const DispatchRequest &request)
527 {
528 std::unique_ptr<SetBreakpointsActiveParams> params = SetBreakpointsActiveParams::Create(request.GetParams());
529 if (params == nullptr) {
530 SendResponse(request, DispatchResponse::Fail("wrong params"));
531 return;
532 }
533
534 DispatchResponse response = debugger_->SetBreakpointsActive(*params);
535 SendResponse(request, response);
536 }
537
GetPossibleAndSetBreakpointByUrl(const DispatchRequest & request)538 void DebuggerImpl::DispatcherImpl::GetPossibleAndSetBreakpointByUrl(const DispatchRequest &request)
539 {
540 std::unique_ptr<GetPossibleAndSetBreakpointParams> params =
541 GetPossibleAndSetBreakpointParams::Create(request.GetParams());
542 if (params == nullptr) {
543 SendResponse(request, DispatchResponse::Fail("wrong params"));
544 return;
545 }
546
547 std::vector<std::unique_ptr<BreakpointReturnInfo>> outLocation;
548 DispatchResponse response = debugger_->GetPossibleAndSetBreakpointByUrl(*params, outLocation);
549 GetPossibleAndSetBreakpointByUrlReturns result(std::move(outLocation));
550 SendResponse(request, response, result);
551 }
552
SetPauseOnExceptions(const DispatchRequest & request)553 void DebuggerImpl::DispatcherImpl::SetPauseOnExceptions(const DispatchRequest &request)
554 {
555 std::unique_ptr<SetPauseOnExceptionsParams> params = SetPauseOnExceptionsParams::Create(request.GetParams());
556 if (params == nullptr) {
557 SendResponse(request, DispatchResponse::Fail("wrong params"));
558 return;
559 }
560
561 DispatchResponse response = debugger_->SetPauseOnExceptions(*params);
562 SendResponse(request, response);
563 }
564
SetSkipAllPauses(const DispatchRequest & request)565 void DebuggerImpl::DispatcherImpl::SetSkipAllPauses(const DispatchRequest &request)
566 {
567 std::unique_ptr<SetSkipAllPausesParams> params = SetSkipAllPausesParams::Create(request.GetParams());
568 if (params == nullptr) {
569 SendResponse(request, DispatchResponse::Fail("wrong params"));
570 return;
571 }
572
573 DispatchResponse response = debugger_->SetSkipAllPauses(*params);
574 SendResponse(request, response);
575 }
576
SetNativeRange(const DispatchRequest & request)577 void DebuggerImpl::DispatcherImpl::SetNativeRange(const DispatchRequest &request)
578 {
579 std::unique_ptr<SetNativeRangeParams> params = SetNativeRangeParams::Create(request.GetParams());
580 if (params == nullptr) {
581 SendResponse(request, DispatchResponse::Fail("wrong params"));
582 return;
583 }
584 DispatchResponse response = debugger_->SetNativeRange(*params);
585 SendResponse(request, response);
586 }
587
ResetSingleStepper(const DispatchRequest & request)588 void DebuggerImpl::DispatcherImpl::ResetSingleStepper(const DispatchRequest &request)
589 {
590 std::unique_ptr<ResetSingleStepperParams> params = ResetSingleStepperParams::Create(request.GetParams());
591 if (params == nullptr) {
592 SendResponse(request, DispatchResponse::Fail("wrong params"));
593 return;
594 }
595 DispatchResponse response = debugger_->ResetSingleStepper(*params);
596 SendResponse(request, response);
597 }
598
StepInto(const DispatchRequest & request)599 void DebuggerImpl::DispatcherImpl::StepInto(const DispatchRequest &request)
600 {
601 std::unique_ptr<StepIntoParams> params = StepIntoParams::Create(request.GetParams());
602 if (params == nullptr) {
603 SendResponse(request, DispatchResponse::Fail("wrong params"));
604 return;
605 }
606 DispatchResponse response = debugger_->StepInto(*params);
607 SendResponse(request, response);
608 }
609
StepOut(const DispatchRequest & request)610 void DebuggerImpl::DispatcherImpl::StepOut(const DispatchRequest &request)
611 {
612 DispatchResponse response = debugger_->StepOut();
613 SendResponse(request, response);
614 }
615
StepOver(const DispatchRequest & request)616 void DebuggerImpl::DispatcherImpl::StepOver(const DispatchRequest &request)
617 {
618 std::unique_ptr<StepOverParams> params = StepOverParams::Create(request.GetParams());
619 if (params == nullptr) {
620 SendResponse(request, DispatchResponse::Fail("wrong params"));
621 return;
622 }
623 DispatchResponse response = debugger_->StepOver(*params);
624 SendResponse(request, response);
625 }
626
SetMixedDebugEnabled(const DispatchRequest & request)627 void DebuggerImpl::DispatcherImpl::SetMixedDebugEnabled(const DispatchRequest &request)
628 {
629 std::unique_ptr<SetMixedDebugParams> params = SetMixedDebugParams::Create(request.GetParams());
630 if (params == nullptr) {
631 SendResponse(request, DispatchResponse::Fail("wrong params"));
632 return;
633 }
634 DispatchResponse response = debugger_->SetMixedDebugEnabled(*params);
635 SendResponse(request, response);
636 }
637
ReplyNativeCalling(const DispatchRequest & request)638 void DebuggerImpl::DispatcherImpl::ReplyNativeCalling(const DispatchRequest &request)
639 {
640 std::unique_ptr<ReplyNativeCallingParams> params = ReplyNativeCallingParams::Create(request.GetParams());
641 if (params == nullptr) {
642 SendResponse(request, DispatchResponse::Fail("wrong params"));
643 return;
644 }
645 DispatchResponse response = debugger_->ReplyNativeCalling(*params);
646 SendResponse(request, response);
647 }
648
SetBlackboxPatterns(const DispatchRequest & request)649 void DebuggerImpl::DispatcherImpl::SetBlackboxPatterns(const DispatchRequest &request)
650 {
651 DispatchResponse response = debugger_->SetBlackboxPatterns();
652 SendResponse(request, response);
653 }
654
DropFrame(const DispatchRequest & request)655 void DebuggerImpl::DispatcherImpl::DropFrame(const DispatchRequest &request)
656 {
657 std::unique_ptr<DropFrameParams> params = DropFrameParams::Create(request.GetParams());
658 if (params == nullptr) {
659 SendResponse(request, DispatchResponse::Fail("wrong params"));
660 return;
661 }
662 DispatchResponse response = debugger_->DropFrame(*params);
663 SendResponse(request, response);
664 }
665
666 // inner message, not SendResponse to outer
ClientDisconnect(const DispatchRequest & request)667 void DebuggerImpl::DispatcherImpl::ClientDisconnect([[maybe_unused]] const DispatchRequest &request)
668 {
669 debugger_->ClientDisconnect();
670 }
671
CallFunctionOn(const DispatchRequest & request)672 void DebuggerImpl::DispatcherImpl::CallFunctionOn(const DispatchRequest &request)
673 {
674 std::unique_ptr<CallFunctionOnParams> params = CallFunctionOnParams::Create(request.GetParams());
675 if (params == nullptr) {
676 SendResponse(request, DispatchResponse::Fail("wrong params"));
677 return;
678 }
679
680 std::unique_ptr<RemoteObject> outRemoteObject;
681 std::optional<std::unique_ptr<ExceptionDetails>> outExceptionDetails;
682 DispatchResponse response = debugger_->CallFunctionOn(*params, &outRemoteObject, &outExceptionDetails);
683 if (outExceptionDetails) {
684 ASSERT(outExceptionDetails.value() != nullptr);
685 LOG_DEBUGGER(WARN) << "CallFunctionOn thrown an exception";
686 }
687 if (outRemoteObject == nullptr) {
688 SendResponse(request, response);
689 return;
690 }
691
692 CallFunctionOnReturns result(std::move(outRemoteObject), std::move(outExceptionDetails));
693 SendResponse(request, response, result);
694 }
695
AllowNotify(const EcmaVM * vm) const696 bool DebuggerImpl::Frontend::AllowNotify(const EcmaVM *vm) const
697 {
698 return vm->GetJsDebuggerManager()->IsDebugMode() && channel_ != nullptr;
699 }
700
BreakpointResolved(const EcmaVM * vm)701 void DebuggerImpl::Frontend::BreakpointResolved(const EcmaVM *vm)
702 {
703 if (!AllowNotify(vm)) {
704 return;
705 }
706
707 tooling::BreakpointResolved breakpointResolved;
708 channel_->SendNotification(breakpointResolved);
709 }
710
Paused(const EcmaVM * vm,const tooling::Paused & paused)711 void DebuggerImpl::Frontend::Paused(const EcmaVM *vm, const tooling::Paused &paused)
712 {
713 if (!AllowNotify(vm)) {
714 return;
715 }
716
717 channel_->SendNotification(paused);
718 }
719
NativeCalling(const EcmaVM * vm,const tooling::NativeCalling & nativeCalling)720 void DebuggerImpl::Frontend::NativeCalling(const EcmaVM *vm, const tooling::NativeCalling &nativeCalling)
721 {
722 if (!AllowNotify(vm)) {
723 return;
724 }
725
726 channel_->SendNotification(nativeCalling);
727 }
728
MixedStack(const EcmaVM * vm,const tooling::MixedStack & mixedStack)729 void DebuggerImpl::Frontend::MixedStack(const EcmaVM *vm, const tooling::MixedStack &mixedStack)
730 {
731 if (!AllowNotify(vm)) {
732 return;
733 }
734
735 channel_->SendNotification(mixedStack);
736 }
737
Resumed(const EcmaVM * vm)738 void DebuggerImpl::Frontend::Resumed(const EcmaVM *vm)
739 {
740 if (!AllowNotify(vm)) {
741 return;
742 }
743
744 channel_->RunIfWaitingForDebugger();
745 tooling::Resumed resumed;
746 channel_->SendNotification(resumed);
747 }
748
ScriptFailedToParse(const EcmaVM * vm)749 void DebuggerImpl::Frontend::ScriptFailedToParse(const EcmaVM *vm)
750 {
751 if (!AllowNotify(vm)) {
752 return;
753 }
754
755 tooling::ScriptFailedToParse scriptFailedToParse;
756 channel_->SendNotification(scriptFailedToParse);
757 }
758
ScriptParsed(const EcmaVM * vm,const PtScript & script)759 void DebuggerImpl::Frontend::ScriptParsed(const EcmaVM *vm, const PtScript &script)
760 {
761 if (!AllowNotify(vm)) {
762 return;
763 }
764
765 tooling::ScriptParsed scriptParsed;
766 scriptParsed.SetScriptId(script.GetScriptId())
767 .SetUrl(script.GetUrl())
768 .SetStartLine(0)
769 .SetStartColumn(0)
770 .SetEndLine(script.GetEndLine())
771 .SetEndColumn(0)
772 .SetExecutionContextId(0)
773 .SetHash(script.GetHash());
774
775 channel_->SendNotification(scriptParsed);
776 }
777
WaitForDebugger(const EcmaVM * vm)778 void DebuggerImpl::Frontend::WaitForDebugger(const EcmaVM *vm)
779 {
780 if (!AllowNotify(vm)) {
781 return;
782 }
783
784 channel_->WaitForDebugger();
785 }
786
RunIfWaitingForDebugger(const EcmaVM * vm)787 void DebuggerImpl::Frontend::RunIfWaitingForDebugger([[maybe_unused]] const EcmaVM *vm)
788 {
789 // Because release hap can WaitForDebugger, need RunIfWaitingForDebugger to run continue.
790 // But release hap debugMode is false, so not check debugMode.
791 if (channel_ == nullptr) {
792 return;
793 }
794
795 channel_->RunIfWaitingForDebugger();
796 }
797
ContinueToLocation(const ContinueToLocationParams & params)798 DispatchResponse DebuggerImpl::ContinueToLocation(const ContinueToLocationParams ¶ms)
799 {
800 location_ = *params.GetLocation();
801 return DispatchResponse::Ok();
802 }
803
Enable(const EnableParams & params,UniqueDebuggerId * id)804 DispatchResponse DebuggerImpl::Enable([[maybe_unused]] const EnableParams ¶ms, UniqueDebuggerId *id)
805 {
806 ASSERT(id != nullptr);
807 *id = 0;
808 vm_->GetJsDebuggerManager()->SetDebugMode(true);
809 for (auto &script : scripts_) {
810 frontend_.ScriptParsed(vm_, *script.second);
811 }
812 debuggerState_ = DebuggerState::ENABLED;
813 return DispatchResponse::Ok();
814 }
815
Disable()816 DispatchResponse DebuggerImpl::Disable()
817 {
818 DebuggerApi::RemoveAllBreakpoints(jsDebugger_);
819 frontend_.RunIfWaitingForDebugger(vm_);
820 frontend_.Resumed(vm_);
821 vm_->GetJsDebuggerManager()->SetDebugMode(false);
822 debuggerState_ = DebuggerState::DISABLED;
823 return DispatchResponse::Ok();
824 }
825
EvaluateOnCallFrame(const EvaluateOnCallFrameParams & params,std::unique_ptr<RemoteObject> * result)826 DispatchResponse DebuggerImpl::EvaluateOnCallFrame(const EvaluateOnCallFrameParams ¶ms,
827 std::unique_ptr<RemoteObject> *result)
828 {
829 CallFrameId callFrameId = params.GetCallFrameId();
830 const std::string &expression = params.GetExpression();
831 if (callFrameId < 0 || callFrameId >= static_cast<CallFrameId>(callFrameHandlers_.size())) {
832 return DispatchResponse::Fail("Invalid callFrameId.");
833 }
834
835 std::vector<uint8_t> dest;
836 if (!DecodeAndCheckBase64(expression, dest)) {
837 LOG_DEBUGGER(ERROR) << "EvaluateValue: base64 decode failed";
838 auto ret = CmptEvaluateValue(callFrameId, expression, result);
839 if (ret.has_value()) {
840 LOG_DEBUGGER(ERROR) << "Evaluate fail, expression: " << expression;
841 }
842 return DispatchResponse::Create(ret);
843 }
844
845 auto funcRef = DebuggerApi::GenerateFuncFromBuffer(vm_, dest.data(), dest.size(),
846 JSPandaFile::ENTRY_FUNCTION_NAME);
847 auto res = DebuggerApi::EvaluateViaFuncCall(const_cast<EcmaVM *>(vm_), funcRef,
848 callFrameHandlers_[callFrameId]);
849 if (vm_->GetJSThread()->HasPendingException()) {
850 LOG_DEBUGGER(ERROR) << "EvaluateValue: has pending exception";
851 std::string msg;
852 DebuggerApi::HandleUncaughtException(vm_, msg);
853 *result = RemoteObject::FromTagged(vm_,
854 Exception::EvalError(vm_, StringRef::NewFromUtf8(vm_, msg.data())));
855 return DispatchResponse::Fail(msg);
856 }
857
858 *result = RemoteObject::FromTagged(vm_, res);
859 runtime_->CacheObjectIfNeeded(res, (*result).get());
860 return DispatchResponse::Ok();
861 }
862
GetPossibleBreakpoints(const GetPossibleBreakpointsParams & params,std::vector<std::unique_ptr<BreakLocation>> * locations)863 DispatchResponse DebuggerImpl::GetPossibleBreakpoints(const GetPossibleBreakpointsParams ¶ms,
864 std::vector<std::unique_ptr<BreakLocation>> *locations)
865 {
866 Location *start = params.GetStart();
867 auto iter = scripts_.find(start->GetScriptId());
868 if (iter == scripts_.end()) {
869 return DispatchResponse::Fail("Unknown file name.");
870 }
871 const std::string &url = iter->second->GetUrl();
872 std::vector<DebugInfoExtractor *> extractors = GetExtractors(url);
873 for (auto extractor : extractors) {
874 if (extractor == nullptr) {
875 LOG_DEBUGGER(ERROR) << "GetPossibleBreakpoints: extractor is null";
876 continue;
877 }
878
879 int32_t line = start->GetLine();
880 int32_t column = start->GetColumn();
881 auto callbackFunc = [](const JSPtLocation &) -> bool {
882 return true;
883 };
884 if (extractor->MatchWithLocation(callbackFunc, line, column, url, GetRecordName(url))) {
885 std::unique_ptr<BreakLocation> location = std::make_unique<BreakLocation>();
886 location->SetScriptId(start->GetScriptId()).SetLine(line).SetColumn(column);
887 locations->emplace_back(std::move(location));
888 break;
889 }
890 }
891 return DispatchResponse::Ok();
892 }
893
GetScriptSource(const GetScriptSourceParams & params,std::string * source)894 DispatchResponse DebuggerImpl::GetScriptSource(const GetScriptSourceParams ¶ms, std::string *source)
895 {
896 ScriptId scriptId = params.GetScriptId();
897 auto iter = scripts_.find(scriptId);
898 if (iter == scripts_.end()) {
899 *source = "";
900 return DispatchResponse::Fail("unknown script id: " + std::to_string(scriptId));
901 }
902 *source = iter->second->GetScriptSource();
903
904 return DispatchResponse::Ok();
905 }
906
Pause()907 DispatchResponse DebuggerImpl::Pause()
908 {
909 if (debuggerState_ == DebuggerState::PAUSED) {
910 return DispatchResponse::Fail("Can only perform operation while running");
911 }
912 pauseOnNextByteCode_ = true;
913 return DispatchResponse::Ok();
914 }
915
RemoveBreakpoint(const RemoveBreakpointParams & params)916 DispatchResponse DebuggerImpl::RemoveBreakpoint(const RemoveBreakpointParams ¶ms)
917 {
918 std::string id = params.GetBreakpointId();
919 LOG_DEBUGGER(INFO) << "RemoveBreakpoint: " << id;
920 BreakpointDetails metaData{};
921 if (!BreakpointDetails::ParseBreakpointId(id, &metaData)) {
922 return DispatchResponse::Fail("Parse breakpoint id failed");
923 }
924
925 auto scriptFunc = [](PtScript *) -> bool {
926 return true;
927 };
928 if (!MatchScripts(scriptFunc, metaData.url_, ScriptMatchType::URL)) {
929 LOG_DEBUGGER(ERROR) << "RemoveBreakpoint: Unknown url: " << metaData.url_;
930 return DispatchResponse::Fail("Unknown file name.");
931 }
932
933 std::vector<DebugInfoExtractor *> extractors = GetExtractors(metaData.url_);
934 for (auto extractor : extractors) {
935 if (extractor == nullptr) {
936 LOG_DEBUGGER(ERROR) << "RemoveBreakpoint: extractor is null";
937 continue;
938 }
939
940 auto callbackFunc = [this](const JSPtLocation &location) -> bool {
941 LOG_DEBUGGER(INFO) << "remove breakpoint location: " << location.ToString();
942 return DebuggerApi::RemoveBreakpoint(jsDebugger_, location);
943 };
944 if (!extractor->MatchWithLocation(callbackFunc, metaData.line_, metaData.column_,
945 metaData.url_, GetRecordName(metaData.url_))) {
946 LOG_DEBUGGER(ERROR) << "failed to remove breakpoint location number: "
947 << metaData.line_ << ":" << metaData.column_;
948 }
949 }
950
951 LOG_DEBUGGER(INFO) << "remove breakpoint line number:" << metaData.line_;
952 return DispatchResponse::Ok();
953 }
954
Resume(const ResumeParams & params)955 DispatchResponse DebuggerImpl::Resume([[maybe_unused]] const ResumeParams ¶ms)
956 {
957 if (debuggerState_ != DebuggerState::PAUSED) {
958 return DispatchResponse::Fail("Can only perform operation while paused");
959 }
960 frontend_.Resumed(vm_);
961 debuggerState_ = DebuggerState::ENABLED;
962 return DispatchResponse::Ok();
963 }
964
SetAsyncCallStackDepth()965 DispatchResponse DebuggerImpl::SetAsyncCallStackDepth()
966 {
967 return DispatchResponse::Fail("SetAsyncCallStackDepth not support now");
968 }
969
SetBreakpointByUrl(const SetBreakpointByUrlParams & params,std::string * outId,std::vector<std::unique_ptr<Location>> * outLocations)970 DispatchResponse DebuggerImpl::SetBreakpointByUrl(const SetBreakpointByUrlParams ¶ms,
971 std::string *outId,
972 std::vector<std::unique_ptr<Location>> *outLocations)
973 {
974 if (!vm_->GetJsDebuggerManager()->IsDebugMode()) {
975 return DispatchResponse::Fail("SetBreakpointByUrl: debugger agent is not enabled");
976 }
977 const std::string &url = params.GetUrl();
978 int32_t lineNumber = params.GetLine();
979 // it is not support column breakpoint now, so columnNumber is not useful
980 int32_t columnNumber = -1;
981 auto condition = params.HasCondition() ? params.GetCondition() : std::optional<std::string> {};
982 *outLocations = std::vector<std::unique_ptr<Location>>();
983
984 auto scriptFunc = [](PtScript *) -> bool {
985 return true;
986 };
987 if (!MatchScripts(scriptFunc, url, ScriptMatchType::URL)) {
988 LOG_DEBUGGER(ERROR) << "SetBreakpointByUrl: Unknown url: " << url;
989 return DispatchResponse::Fail("Unknown file name.");
990 }
991
992 std::vector<DebugInfoExtractor *> extractors = GetExtractors(url);
993 for (auto extractor : extractors) {
994 if (extractor == nullptr) {
995 LOG_DEBUGGER(ERROR) << "SetBreakpointByUrl: extractor is null";
996 continue;
997 }
998
999 auto callbackFunc = [this, &condition](const JSPtLocation &location) -> bool {
1000 LOG_DEBUGGER(INFO) << "set breakpoint location: " << location.ToString();
1001 Local<FunctionRef> condFuncRef = FunctionRef::Undefined(vm_);
1002 if (condition.has_value() && !condition.value().empty()) {
1003 condFuncRef = CheckAndGenerateCondFunc(condition);
1004 if (condFuncRef->IsUndefined()) {
1005 LOG_DEBUGGER(ERROR) << "SetBreakpointByUrl: generate function failed";
1006 return false;
1007 }
1008 }
1009 return DebuggerApi::SetBreakpoint(jsDebugger_, location, condFuncRef);
1010 };
1011 if (!extractor->MatchWithLocation(callbackFunc, lineNumber, columnNumber, url, GetRecordName(url))) {
1012 LOG_DEBUGGER(ERROR) << "failed to set breakpoint location number: "
1013 << lineNumber << ":" << columnNumber;
1014 return DispatchResponse::Fail("Breakpoint not found.");
1015 }
1016 }
1017
1018 std::vector<PtScript *> ptScripts = MatchAllScripts(url);
1019 for (auto ptScript : ptScripts) {
1020 ScriptId scriptId = ptScript->GetScriptId();
1021 std::unique_ptr<Location> location = std::make_unique<Location>();
1022 location->SetScriptId(scriptId).SetLine(lineNumber).SetColumn(0);
1023 outLocations->emplace_back(std::move(location));
1024 }
1025 BreakpointDetails metaData{lineNumber, 0, url};
1026 *outId = BreakpointDetails::ToString(metaData);
1027 return DispatchResponse::Ok();
1028 }
1029
SetBreakpointsActive(const SetBreakpointsActiveParams & params)1030 DispatchResponse DebuggerImpl::SetBreakpointsActive(const SetBreakpointsActiveParams ¶ms)
1031 {
1032 breakpointsState_ = params.GetBreakpointsState();
1033 return DispatchResponse::Ok();
1034 }
1035
GetPossibleAndSetBreakpointByUrl(const GetPossibleAndSetBreakpointParams & params,std::vector<std::unique_ptr<BreakpointReturnInfo>> & outLocations)1036 DispatchResponse DebuggerImpl::GetPossibleAndSetBreakpointByUrl(const GetPossibleAndSetBreakpointParams ¶ms,
1037 std::vector<std::unique_ptr<BreakpointReturnInfo>> &outLocations)
1038 {
1039 if (!vm_->GetJsDebuggerManager()->IsDebugMode()) {
1040 return DispatchResponse::Fail("GetPossibleAndSetBreakpointByUrl: debugger agent is not enabled");
1041 }
1042 if (!params.HasBreakpointsList()) {
1043 return DispatchResponse::Fail("GetPossibleAndSetBreakpointByUrl: no pennding breakpoint exists");
1044 }
1045 auto breakpointList = params.GetBreakpointsList();
1046 for (const auto &breakpoint : *breakpointList) {
1047 if (!ProcessSingleBreakpoint(*breakpoint, outLocations)) {
1048 std::string invalidBpId = "invalid";
1049 std::unique_ptr<BreakpointReturnInfo> bpInfo = std::make_unique<BreakpointReturnInfo>();
1050 bpInfo->SetId(invalidBpId)
1051 .SetLineNumber(breakpoint->GetLineNumber())
1052 .SetColumnNumber(breakpoint->GetColumnNumber());
1053 outLocations.emplace_back(std::move(bpInfo));
1054 }
1055 }
1056 return DispatchResponse::Ok();
1057 }
1058
ProcessSingleBreakpoint(const BreakpointInfo & breakpoint,std::vector<std::unique_ptr<BreakpointReturnInfo>> & outLocations)1059 bool DebuggerImpl::ProcessSingleBreakpoint(const BreakpointInfo &breakpoint,
1060 std::vector<std::unique_ptr<BreakpointReturnInfo>> &outLocations)
1061 {
1062 const std::string &url = breakpoint.GetUrl();
1063 int32_t lineNumber = breakpoint.GetLineNumber();
1064 // it is not support column breakpoint now, so columnNumber is not useful
1065 int32_t columnNumber = -1;
1066 auto condition = breakpoint.HasCondition() ? breakpoint.GetCondition() : std::optional<std::string> {};
1067
1068 std::vector<DebugInfoExtractor *> extractors = GetExtractors(url);
1069 for (auto extractor : extractors) {
1070 if (extractor == nullptr) {
1071 LOG_DEBUGGER(ERROR) << "GetPossibleAndSetBreakpointByUrl: extractor is null";
1072 continue;
1073 }
1074 // decode and convert condition to function before doing matchWithLocation
1075 Local<FunctionRef> funcRef = FunctionRef::Undefined(vm_);
1076 if (condition.has_value() && !condition.value().empty()) {
1077 funcRef = CheckAndGenerateCondFunc(condition);
1078 if (funcRef->IsUndefined()) {
1079 LOG_DEBUGGER(ERROR) << "GetPossibleAndSetBreakpointByUrl: generate function failed";
1080 return false;
1081 }
1082 }
1083 auto matchLocationCbFunc = [this, &funcRef](const JSPtLocation &location) -> bool {
1084 return DebuggerApi::SetBreakpoint(jsDebugger_, location, funcRef);
1085 };
1086 if (!extractor->MatchWithLocation(matchLocationCbFunc, lineNumber, columnNumber, url, GetRecordName(url))) {
1087 LOG_DEBUGGER(ERROR) << "failed to set breakpoint location number: " << lineNumber << ":" << columnNumber;
1088 return false;
1089 }
1090 }
1091
1092 BreakpointDetails bpMetaData {lineNumber, 0, url};
1093 std::string outId = BreakpointDetails::ToString(bpMetaData);
1094 std::vector<PtScript *> ptScripts = MatchAllScripts(url);
1095 for (auto ptScript : ptScripts) {
1096 ScriptId scriptId = ptScript->GetScriptId();
1097 std::unique_ptr<BreakpointReturnInfo> bpInfo = std::make_unique<BreakpointReturnInfo>();
1098 bpInfo->SetScriptId(scriptId).SetLineNumber(lineNumber).SetColumnNumber(0).SetId(outId);
1099 outLocations.emplace_back(std::move(bpInfo));
1100 }
1101
1102 return true;
1103 }
1104
SetNativeRange(const SetNativeRangeParams & params)1105 DispatchResponse DebuggerImpl::SetNativeRange(const SetNativeRangeParams ¶ms)
1106 {
1107 nativeRanges_ = params.GetNativeRange();
1108 return DispatchResponse::Ok();
1109 }
1110
ResetSingleStepper(const ResetSingleStepperParams & params)1111 DispatchResponse DebuggerImpl::ResetSingleStepper(const ResetSingleStepperParams ¶ms)
1112 {
1113 // if JS to C++ and C++ has breakpoint; it need to clear singleStepper_
1114 if (params.GetResetSingleStepper()) {
1115 singleStepper_.reset();
1116 }
1117 return DispatchResponse::Ok();
1118 }
1119
SetPauseOnExceptions(const SetPauseOnExceptionsParams & params)1120 DispatchResponse DebuggerImpl::SetPauseOnExceptions(const SetPauseOnExceptionsParams ¶ms)
1121 {
1122 pauseOnException_ = params.GetState();
1123 return DispatchResponse::Ok();
1124 }
1125
SetSkipAllPauses(const SetSkipAllPausesParams & params)1126 DispatchResponse DebuggerImpl::SetSkipAllPauses(const SetSkipAllPausesParams ¶ms)
1127 {
1128 skipAllPausess_ = params.GetSkipAllPausesState();
1129 return DispatchResponse::Ok();
1130 }
1131
StepInto(const StepIntoParams & params)1132 DispatchResponse DebuggerImpl::StepInto([[maybe_unused]] const StepIntoParams ¶ms)
1133 {
1134 if (debuggerState_ != DebuggerState::PAUSED) {
1135 return DispatchResponse::Fail("Can only perform operation while paused");
1136 }
1137 singleStepper_ = SingleStepper::GetStepIntoStepper(vm_);
1138 if (singleStepper_ == nullptr) {
1139 LOG_DEBUGGER(ERROR) << "StepInto: singleStepper is null";
1140 return DispatchResponse::Fail("Failed to StepInto");
1141 }
1142 frontend_.Resumed(vm_);
1143 debuggerState_ = DebuggerState::ENABLED;
1144 return DispatchResponse::Ok();
1145 }
1146
StepOut()1147 DispatchResponse DebuggerImpl::StepOut()
1148 {
1149 if (debuggerState_ != DebuggerState::PAUSED) {
1150 return DispatchResponse::Fail("Can only perform operation while paused");
1151 }
1152 singleStepper_ = SingleStepper::GetStepOutStepper(vm_);
1153 if (singleStepper_ == nullptr) {
1154 LOG_DEBUGGER(ERROR) << "StepOut: singleStepper is null";
1155 return DispatchResponse::Fail("Failed to StepOut");
1156 }
1157 frontend_.Resumed(vm_);
1158 debuggerState_ = DebuggerState::ENABLED;
1159 return DispatchResponse::Ok();
1160 }
1161
StepOver(const StepOverParams & params)1162 DispatchResponse DebuggerImpl::StepOver([[maybe_unused]] const StepOverParams ¶ms)
1163 {
1164 if (debuggerState_ != DebuggerState::PAUSED) {
1165 return DispatchResponse::Fail("Can only perform operation while paused");
1166 }
1167 singleStepper_ = SingleStepper::GetStepOverStepper(vm_);
1168 if (singleStepper_ == nullptr) {
1169 LOG_DEBUGGER(ERROR) << "StepOver: singleStepper is null";
1170 return DispatchResponse::Fail("Failed to StepOver");
1171 }
1172 frontend_.Resumed(vm_);
1173 debuggerState_ = DebuggerState::ENABLED;
1174 return DispatchResponse::Ok();
1175 }
1176
SetBlackboxPatterns()1177 DispatchResponse DebuggerImpl::SetBlackboxPatterns()
1178 {
1179 return DispatchResponse::Fail("SetBlackboxPatterns not support now");
1180 }
1181
SetMixedDebugEnabled(const SetMixedDebugParams & params)1182 DispatchResponse DebuggerImpl::SetMixedDebugEnabled([[maybe_unused]] const SetMixedDebugParams ¶ms)
1183 {
1184 vm_->GetJsDebuggerManager()->SetMixedDebugEnabled(params.GetEnabled());
1185 vm_->GetJsDebuggerManager()->SetMixedStackEnabled(params.GetMixedStackEnabled());
1186 mixStackEnabled_ = params.GetMixedStackEnabled();
1187 return DispatchResponse::Ok();
1188 }
1189
ReplyNativeCalling(const ReplyNativeCallingParams & params)1190 DispatchResponse DebuggerImpl::ReplyNativeCalling([[maybe_unused]] const ReplyNativeCallingParams ¶ms)
1191 {
1192 frontend_.Resumed(vm_);
1193 if (params.GetUserCode()) {
1194 singleStepper_.reset();
1195 }
1196 return DispatchResponse::Ok();
1197 }
1198
DropFrame(const DropFrameParams & params)1199 DispatchResponse DebuggerImpl::DropFrame(const DropFrameParams ¶ms)
1200 {
1201 if (debuggerState_ != DebuggerState::PAUSED) {
1202 return DispatchResponse::Fail("Can only perform operation while paused");
1203 }
1204 uint32_t droppedDepth = 1;
1205 if (params.HasDroppedDepth()) {
1206 droppedDepth = params.GetDroppedDepth();
1207 if (droppedDepth == 0) {
1208 return DispatchResponse::Ok();
1209 }
1210 if (droppedDepth > 1) {
1211 return DispatchResponse::Fail("Not yet support dropping multiple frames");
1212 }
1213 }
1214 uint32_t stackDepth = DebuggerApi::GetStackDepth(vm_);
1215 if (droppedDepth > stackDepth) {
1216 return DispatchResponse::Fail("The input depth exceeds stackDepth");
1217 }
1218 if (droppedDepth == stackDepth) {
1219 return DispatchResponse::Fail("The bottom frame cannot be dropped");
1220 }
1221 uint32_t stackDepthOverBuiltin = DebuggerApi::GetStackDepthOverBuiltin(vm_);
1222 if (droppedDepth >= stackDepthOverBuiltin) {
1223 return DispatchResponse::Fail("Frames to be dropped contain builtin frame");
1224 }
1225 if (!DebuggerApi::CheckPromiseQueueSize(vm_)) {
1226 return DispatchResponse::Fail("Detect promise enqueued in current frame");
1227 }
1228 for (uint32_t i = 0; i < droppedDepth; i++) {
1229 DebuggerApi::DropLastFrame(vm_);
1230 }
1231 pauseOnNextByteCode_ = true;
1232 frontend_.RunIfWaitingForDebugger(vm_);
1233 debuggerState_ = DebuggerState::ENABLED;
1234 return DispatchResponse::Ok();
1235 }
1236
ClientDisconnect()1237 DispatchResponse DebuggerImpl::ClientDisconnect()
1238 {
1239 DeviceDisconnectCallback cb = vm_->GetDeviceDisconnectCallback();
1240 if (cb == nullptr) {
1241 LOG_DEBUGGER(ERROR) << "DebuggerImpl::ClientDisconnect callback is nullptr";
1242 } else {
1243 cb();
1244 }
1245 return DispatchResponse::Ok();
1246 }
1247
CallFunctionOn(const CallFunctionOnParams & params,std::unique_ptr<RemoteObject> * outRemoteObject,std::optional<std::unique_ptr<ExceptionDetails>> * outExceptionDetails)1248 DispatchResponse DebuggerImpl::CallFunctionOn([[maybe_unused]] const CallFunctionOnParams ¶ms,
1249 std::unique_ptr<RemoteObject> *outRemoteObject,
1250 [[maybe_unused]] std::optional<std::unique_ptr<ExceptionDetails>> *outExceptionDetails)
1251 {
1252 // get callFrameId
1253 CallFrameId callFrameId = params.GetCallFrameId();
1254 if (callFrameId < 0 || callFrameId >= static_cast<CallFrameId>(callFrameHandlers_.size())) {
1255 return DispatchResponse::Fail("Invalid callFrameId.");
1256 }
1257 // get function declaration
1258 std::string functionDeclaration = params.GetFunctionDeclaration();
1259 std::vector<uint8_t> dest;
1260 if (!DecodeAndCheckBase64(functionDeclaration, dest)) {
1261 LOG_DEBUGGER(ERROR) << "CallFunctionOn: base64 decode failed";
1262 return DispatchResponse::Fail("base64 decode failed, functionDeclaration: " +
1263 functionDeclaration);
1264 }
1265 auto funcRef = DebuggerApi::GenerateFuncFromBuffer(vm_, dest.data(), dest.size(),
1266 JSPandaFile::ENTRY_FUNCTION_NAME);
1267 // call function
1268 auto res = DebuggerApi::CallFunctionOnCall(const_cast<EcmaVM *>(vm_), funcRef,
1269 callFrameHandlers_[callFrameId]);
1270 if (vm_->GetJSThread()->HasPendingException()) {
1271 LOG_DEBUGGER(ERROR) << "CallFunctionOn: has pending exception";
1272 std::string msg;
1273 DebuggerApi::HandleUncaughtException(vm_, msg);
1274 *outRemoteObject = RemoteObject::FromTagged(vm_,
1275 Exception::EvalError(vm_, StringRef::NewFromUtf8(vm_, msg.data())));
1276 return DispatchResponse::Fail(msg);
1277 }
1278
1279 *outRemoteObject = RemoteObject::FromTagged(vm_, res);
1280 runtime_->CacheObjectIfNeeded(res, (*outRemoteObject).get());
1281 return DispatchResponse::Ok();
1282 }
1283
CleanUpOnPaused()1284 void DebuggerImpl::CleanUpOnPaused()
1285 {
1286 CleanUpRuntimeProperties();
1287 callFrameHandlers_.clear();
1288 scopeObjects_.clear();
1289 }
1290
CleanUpRuntimeProperties()1291 void DebuggerImpl::CleanUpRuntimeProperties()
1292 {
1293 LOG_DEBUGGER(INFO) << "CleanUpRuntimeProperties OnPaused";
1294 if (runtime_->properties_.empty()) {
1295 return;
1296 }
1297 RemoteObjectId validObjId = runtime_->curObjectId_ - 1;
1298 for (; validObjId >= 0; validObjId--) {
1299 runtime_->properties_[validObjId].FreeGlobalHandleAddr();
1300 }
1301 runtime_->curObjectId_ = 0;
1302 runtime_->properties_.clear();
1303 }
1304
Trim(const std::string & str)1305 std::string DebuggerImpl::Trim(const std::string &str)
1306 {
1307 std::string ret = str;
1308 // If ret has only ' ', remove all charactors.
1309 ret.erase(ret.find_last_not_of(' ') + 1);
1310 // If ret has only ' ', remove all charactors.
1311 ret.erase(0, ret.find_first_not_of(' '));
1312 return ret;
1313 }
1314
GetExtractor(const JSPandaFile * jsPandaFile)1315 DebugInfoExtractor *DebuggerImpl::GetExtractor(const JSPandaFile *jsPandaFile)
1316 {
1317 return JSPandaFileManager::GetInstance()->GetJSPtExtractor(jsPandaFile);
1318 }
1319
1320 // mainly used for breakpoints to match location
GetExtractors(const std::string & url)1321 std::vector<DebugInfoExtractor *> DebuggerImpl::GetExtractors(const std::string &url)
1322 {
1323 std::vector<DebugInfoExtractor *> extractors;
1324 // match patch file first if it contains diff for the url, and currently only support the file
1325 // specified by the url change as a whole
1326 extractors = DebuggerApi::GetPatchExtractors(vm_, url);
1327 if (!extractors.empty()) {
1328 return extractors;
1329 }
1330
1331 std::vector<PtScript *> ptScripts = MatchAllScripts(url);
1332 for (auto ptScript : ptScripts) {
1333 std::string fileName = ptScript->GetFileName();
1334 const JSPandaFile *jsPandaFile = JSPandaFileManager::GetInstance()->FindJSPandaFile(fileName.c_str()).get();
1335 if (jsPandaFile == nullptr) {
1336 continue;
1337 }
1338 DebugInfoExtractor *extractor = JSPandaFileManager::GetInstance()->GetJSPtExtractor(jsPandaFile);
1339 if (extractor == nullptr) {
1340 LOG_DEBUGGER(ERROR) << "GetPossibleBreakpoints: extractor is null";
1341 continue;
1342 }
1343 extractors.emplace_back(extractor);
1344 }
1345 return extractors;
1346 }
1347
GenerateCallFrames(std::vector<std::unique_ptr<CallFrame>> * callFrames,bool getScope)1348 bool DebuggerImpl::GenerateCallFrames(std::vector<std::unique_ptr<CallFrame>> *callFrames, bool getScope)
1349 {
1350 CallFrameId callFrameId = 0;
1351 auto walkerFunc = [this, &callFrameId, &callFrames, &getScope](const FrameHandler *frameHandler) -> StackState {
1352 if (DebuggerApi::IsNativeMethod(frameHandler)) {
1353 LOG_DEBUGGER(INFO) << "GenerateCallFrames: Skip CFrame and Native method";
1354 return StackState::CONTINUE;
1355 }
1356 std::unique_ptr<CallFrame> callFrame = std::make_unique<CallFrame>();
1357 if (!GenerateCallFrame(callFrame.get(), frameHandler, callFrameId, getScope)) {
1358 if (callFrameId == 0) {
1359 return StackState::FAILED;
1360 }
1361 } else {
1362 SaveCallFrameHandler(frameHandler);
1363 callFrames->emplace_back(std::move(callFrame));
1364 callFrameId++;
1365 }
1366 return StackState::CONTINUE;
1367 };
1368 return DebuggerApi::StackWalker(vm_, walkerFunc);
1369 }
1370
SaveCallFrameHandler(const FrameHandler * frameHandler)1371 void DebuggerImpl::SaveCallFrameHandler(const FrameHandler *frameHandler)
1372 {
1373 auto handlerPtr = DebuggerApi::NewFrameHandler(vm_);
1374 *handlerPtr = *frameHandler;
1375 callFrameHandlers_.emplace_back(handlerPtr);
1376 }
1377
GenerateCallFrame(CallFrame * callFrame,const FrameHandler * frameHandler,CallFrameId callFrameId,bool getScope)1378 bool DebuggerImpl::GenerateCallFrame(CallFrame *callFrame, const FrameHandler *frameHandler,
1379 CallFrameId callFrameId, bool getScope)
1380 {
1381 if (!frameHandler->HasFrame()) {
1382 return false;
1383 }
1384 Method *method = DebuggerApi::GetMethod(frameHandler);
1385 auto methodId = method->GetMethodId();
1386 const JSPandaFile *jsPandaFile = method->GetJSPandaFile();
1387 DebugInfoExtractor *extractor = GetExtractor(jsPandaFile);
1388 if (extractor == nullptr) {
1389 LOG_DEBUGGER(ERROR) << "GenerateCallFrame: extractor is null";
1390 return false;
1391 }
1392
1393 // functionName
1394 std::string functionName = method->ParseFunctionName();
1395
1396 // location
1397 std::unique_ptr<Location> location = std::make_unique<Location>();
1398 std::string url = extractor->GetSourceFile(methodId);
1399 auto scriptFunc = [&location](PtScript *script) -> bool {
1400 location->SetScriptId(script->GetScriptId());
1401 return true;
1402 };
1403 if (!MatchScripts(scriptFunc, url, ScriptMatchType::URL)) {
1404 LOG_DEBUGGER(ERROR) << "GenerateCallFrame: Unknown url: " << url;
1405 return false;
1406 }
1407 auto callbackLineFunc = [&location](int32_t line) -> bool {
1408 location->SetLine(line);
1409 return true;
1410 };
1411 auto callbackColumnFunc = [&location](int32_t column) -> bool {
1412 location->SetColumn(column);
1413 return true;
1414 };
1415 if (!extractor->MatchLineWithOffset(callbackLineFunc, methodId, DebuggerApi::GetBytecodeOffset(frameHandler)) ||
1416 !extractor->MatchColumnWithOffset(callbackColumnFunc, methodId, DebuggerApi::GetBytecodeOffset(frameHandler))) {
1417 LOG_DEBUGGER(ERROR) << "GenerateCallFrame: unknown offset: " << DebuggerApi::GetBytecodeOffset(frameHandler);
1418 return false;
1419 }
1420
1421 // scopeChain & this
1422 std::unique_ptr<RemoteObject> thisObj = std::make_unique<RemoteObject>();
1423 thisObj->SetType(ObjectType::Undefined);
1424
1425 JSThread *thread = vm_->GetJSThread();
1426 std::vector<std::unique_ptr<Scope>> scopeChain;
1427 if (getScope) {
1428 scopeChain.emplace_back(GetLocalScopeChain(frameHandler, &thisObj));
1429 // generate closure scopes
1430 auto closureScopeChains = GetClosureScopeChains(frameHandler, &thisObj);
1431 for (auto &scope : closureScopeChains) {
1432 scopeChain.emplace_back(std::move(scope));
1433 }
1434 if (jsPandaFile != nullptr && !jsPandaFile->IsBundlePack() && jsPandaFile->IsNewVersion()) {
1435 JSHandle<JSTaggedValue> currentModule(thread, DebuggerApi::GetCurrentModule(vm_));
1436 if (currentModule->IsSourceTextModule()) { // CJS module is string
1437 scopeChain.emplace_back(GetModuleScopeChain());
1438 }
1439 }
1440 scopeChain.emplace_back(GetGlobalScopeChain());
1441 }
1442
1443 callFrame->SetCallFrameId(callFrameId)
1444 .SetFunctionName(functionName)
1445 .SetLocation(std::move(location))
1446 .SetUrl(url)
1447 .SetScopeChain(std::move(scopeChain))
1448 .SetThis(std::move(thisObj));
1449 return true;
1450 }
1451
GetLocalScopeChain(const FrameHandler * frameHandler,std::unique_ptr<RemoteObject> * thisObj)1452 std::unique_ptr<Scope> DebuggerImpl::GetLocalScopeChain(const FrameHandler *frameHandler,
1453 std::unique_ptr<RemoteObject> *thisObj)
1454 {
1455 auto localScope = std::make_unique<Scope>();
1456
1457 Method *method = DebuggerApi::GetMethod(frameHandler);
1458 auto methodId = method->GetMethodId();
1459 const JSPandaFile *jsPandaFile = method->GetJSPandaFile();
1460 DebugInfoExtractor *extractor = GetExtractor(jsPandaFile);
1461 if (extractor == nullptr) {
1462 LOG_DEBUGGER(ERROR) << "GetScopeChain: extractor is null";
1463 return localScope;
1464 }
1465
1466 std::unique_ptr<RemoteObject> local = std::make_unique<RemoteObject>();
1467 Local<ObjectRef> localObj = ObjectRef::New(vm_);
1468 local->SetType(ObjectType::Object)
1469 .SetObjectId(runtime_->curObjectId_)
1470 .SetClassName(ObjectClassName::Object)
1471 .SetDescription(RemoteObject::ObjectDescription);
1472 auto *sp = DebuggerApi::GetSp(frameHandler);
1473 scopeObjects_[sp] = runtime_->curObjectId_;
1474 DebuggerApi::AddInternalProperties(vm_, localObj, ArkInternalValueType::Scope, runtime_->internalObjects_);
1475 runtime_->properties_[runtime_->curObjectId_++] = Global<JSValueRef>(vm_, localObj);
1476
1477 Local<JSValueRef> thisVal = JSNApiHelper::ToLocal<JSValueRef>(
1478 JSHandle<JSTaggedValue>(vm_->GetJSThread(), JSTaggedValue::Hole()));
1479 GetLocalVariables(frameHandler, methodId, jsPandaFile, thisVal, localObj);
1480 *thisObj = RemoteObject::FromTagged(vm_, thisVal);
1481 runtime_->CacheObjectIfNeeded(thisVal, (*thisObj).get());
1482
1483 const LineNumberTable &lines = extractor->GetLineNumberTable(methodId);
1484 std::unique_ptr<Location> startLoc = std::make_unique<Location>();
1485 std::unique_ptr<Location> endLoc = std::make_unique<Location>();
1486 auto scriptFunc = [&startLoc, &endLoc, lines](PtScript *script) -> bool {
1487 startLoc->SetScriptId(script->GetScriptId())
1488 .SetLine(lines.front().line)
1489 .SetColumn(0);
1490 endLoc->SetScriptId(script->GetScriptId())
1491 .SetLine(lines.back().line + 1)
1492 .SetColumn(0);
1493 return true;
1494 };
1495 if (MatchScripts(scriptFunc, extractor->GetSourceFile(methodId), ScriptMatchType::URL)) {
1496 localScope->SetType(Scope::Type::Local())
1497 .SetObject(std::move(local))
1498 .SetStartLocation(std::move(startLoc))
1499 .SetEndLocation(std::move(endLoc));
1500 }
1501
1502 return localScope;
1503 }
1504
GetClosureScopeChains(const FrameHandler * frameHandler,std::unique_ptr<RemoteObject> * thisObj)1505 std::vector<std::unique_ptr<Scope>> DebuggerImpl::GetClosureScopeChains(const FrameHandler *frameHandler,
1506 std::unique_ptr<RemoteObject> *thisObj)
1507 {
1508 std::vector<std::unique_ptr<Scope>> closureScopes;
1509 Method *method = DebuggerApi::GetMethod(frameHandler);
1510 EntityId methodId = method->GetMethodId();
1511 const JSPandaFile *jsPandaFile = method->GetJSPandaFile();
1512 DebugInfoExtractor *extractor = GetExtractor(jsPandaFile);
1513 JSThread *thread = vm_->GetJSThread();
1514
1515 if (extractor == nullptr) {
1516 LOG_DEBUGGER(ERROR) << "GetClosureScopeChains: extractor is null";
1517 return closureScopes;
1518 }
1519
1520 JSMutableHandle<JSTaggedValue> envHandle = JSMutableHandle<JSTaggedValue>(
1521 thread, DebuggerApi::GetEnv(frameHandler));
1522 JSMutableHandle<JSTaggedValue> valueHandle = JSMutableHandle<JSTaggedValue>(thread, JSTaggedValue::Hole());
1523 JSTaggedValue currentEnv = envHandle.GetTaggedValue();
1524 if (!currentEnv.IsTaggedArray()) {
1525 LOG_DEBUGGER(ERROR) << "GetClosureScopeChains: currentEnv is invalid";
1526 return closureScopes;
1527 }
1528 // check if GetLocalScopeChain has already found and set 'this' value
1529 bool thisFound = (*thisObj)->HasValue();
1530 bool closureVarFound = false;
1531 // currentEnv = currentEnv->parent until currentEnv becomes undefined
1532 for (; currentEnv.IsTaggedArray(); currentEnv = LexicalEnv::Cast(currentEnv.GetTaggedObject())->GetParentEnv()) {
1533 LexicalEnv *lexicalEnv = LexicalEnv::Cast(currentEnv.GetTaggedObject());
1534 envHandle.Update(currentEnv);
1535 if (lexicalEnv->GetScopeInfo().IsHole()) {
1536 continue;
1537 }
1538 auto closureScope = std::make_unique<Scope>();
1539 auto result = JSNativePointer::Cast(lexicalEnv->GetScopeInfo().GetTaggedObject())->GetExternalPointer();
1540 ScopeDebugInfo *scopeDebugInfo = reinterpret_cast<ScopeDebugInfo *>(result);
1541 std::unique_ptr<RemoteObject> closure = std::make_unique<RemoteObject>();
1542 Local<ObjectRef> closureScopeObj = ObjectRef::New(vm_);
1543
1544 for (const auto &[name, slot] : scopeDebugInfo->scopeInfo) {
1545 if (IsVariableSkipped(name.c_str())) {
1546 continue;
1547 }
1548 currentEnv = envHandle.GetTaggedValue();
1549 lexicalEnv = LexicalEnv::Cast(currentEnv.GetTaggedObject());
1550 valueHandle.Update(lexicalEnv->GetProperties(slot));
1551 Local<JSValueRef> value = JSNApiHelper::ToLocal<JSValueRef>(valueHandle);
1552 Local<JSValueRef> varName = StringRef::NewFromUtf8(vm_, name.c_str());
1553 // found 'this' and 'this' is not set in GetLocalScopechain
1554 if (!thisFound && name == "this") {
1555 *thisObj = RemoteObject::FromTagged(vm_, value);
1556 // cache 'this' object
1557 runtime_->CacheObjectIfNeeded(value, (*thisObj).get());
1558 thisFound = true;
1559 continue;
1560 }
1561 // found closure variable in current lexenv
1562 closureVarFound = true;
1563 // if value is hole, should manually set it to undefined
1564 // otherwise after DefineProperty, corresponding varName
1565 // will become undefined
1566 if (value->IsHole()) {
1567 valueHandle.Update(JSTaggedValue::Undefined());
1568 value = JSNApiHelper::ToLocal<JSValueRef>(valueHandle);
1569 }
1570 PropertyAttribute descriptor(value, true, true, true);
1571 closureScopeObj->DefineProperty(vm_, varName, descriptor);
1572 }
1573 // at least one closure variable has been found
1574 if (closureVarFound) {
1575 closure->SetType(ObjectType::Object)
1576 .SetObjectId(runtime_->curObjectId_)
1577 .SetClassName(ObjectClassName::Object)
1578 .SetDescription(RemoteObject::ObjectDescription);
1579
1580 auto scriptFunc = []([[maybe_unused]] PtScript *script) -> bool {
1581 return true;
1582 };
1583 if (MatchScripts(scriptFunc, extractor->GetSourceFile(methodId), ScriptMatchType::URL)) {
1584 closureScope->SetType(Scope::Type::Closure()).SetObject(std::move(closure));
1585 DebuggerApi::AddInternalProperties(
1586 vm_, closureScopeObj, ArkInternalValueType::Scope, runtime_->internalObjects_);
1587 runtime_->properties_[runtime_->curObjectId_++] = Global<JSValueRef>(vm_, closureScopeObj);
1588 closureScopes.emplace_back(std::move(closureScope));
1589 }
1590 }
1591 currentEnv = envHandle.GetTaggedValue();
1592 closureVarFound = false;
1593 }
1594 return closureScopes;
1595 }
1596
GetModuleScopeChain()1597 std::unique_ptr<Scope> DebuggerImpl::GetModuleScopeChain()
1598 {
1599 auto moduleScope = std::make_unique<Scope>();
1600
1601 std::unique_ptr<RemoteObject> module = std::make_unique<RemoteObject>();
1602 Local<ObjectRef> moduleObj = ObjectRef::New(vm_);
1603 module->SetType(ObjectType::Object)
1604 .SetObjectId(runtime_->curObjectId_)
1605 .SetClassName(ObjectClassName::Object)
1606 .SetDescription(RemoteObject::ObjectDescription);
1607 moduleScope->SetType(Scope::Type::Module()).SetObject(std::move(module));
1608 DebuggerApi::AddInternalProperties(vm_, moduleObj, ArkInternalValueType::Scope, runtime_->internalObjects_);
1609 runtime_->properties_[runtime_->curObjectId_++] = Global<JSValueRef>(vm_, moduleObj);
1610 JSThread *thread = vm_->GetJSThread();
1611 JSHandle<JSTaggedValue> currentModule(thread, DebuggerApi::GetCurrentModule(vm_));
1612 DebuggerApi::GetLocalExportVariables(vm_, moduleObj, currentModule, false);
1613 DebuggerApi::GetIndirectExportVariables(vm_, moduleObj, currentModule);
1614 DebuggerApi::GetImportVariables(vm_, moduleObj, currentModule);
1615 return moduleScope;
1616 }
1617
GetLocalVariables(const FrameHandler * frameHandler,panda_file::File::EntityId methodId,const JSPandaFile * jsPandaFile,Local<JSValueRef> & thisVal,Local<ObjectRef> & localObj)1618 void DebuggerImpl::GetLocalVariables(const FrameHandler *frameHandler, panda_file::File::EntityId methodId,
1619 const JSPandaFile *jsPandaFile, Local<JSValueRef> &thisVal, Local<ObjectRef> &localObj)
1620 {
1621 auto *extractor = GetExtractor(jsPandaFile);
1622 Local<JSValueRef> value = JSValueRef::Undefined(vm_);
1623 // in case of arrow function, which doesn't have this in local variable table
1624 for (const auto &localVariableInfo : extractor->GetLocalVariableTable(methodId)) {
1625 std::string varName = localVariableInfo.name;
1626 int32_t regIndex = localVariableInfo.regNumber;
1627 uint32_t bcOffset = DebuggerApi::GetBytecodeOffset(frameHandler);
1628 // if the bytecodeOffset is not in the range of the variable's scope,
1629 // which is indicated as [start_offset, end_offset), ignore it.
1630 if (!IsWithinVariableScope(localVariableInfo, bcOffset)) {
1631 continue;
1632 }
1633
1634 if (IsVariableSkipped(varName)) {
1635 continue;
1636 }
1637
1638 value = DebuggerApi::GetVRegValue(vm_, frameHandler, regIndex);
1639 if (varName == "this") {
1640 LOG_DEBUGGER(INFO) << "find 'this' in local variable table";
1641 thisVal = value;
1642 continue;
1643 }
1644 Local<JSValueRef> name = JSValueRef::Undefined(vm_);
1645 if (varName == "4funcObj") {
1646 if (value->IsFunction()) {
1647 auto funcName = Local<FunctionRef>(value)->GetName(vm_)->ToString();
1648 name = StringRef::NewFromUtf8(vm_, funcName.c_str());
1649 } else {
1650 continue;
1651 }
1652 } else {
1653 name = StringRef::NewFromUtf8(vm_, varName.c_str());
1654 }
1655 PropertyAttribute descriptor(value, true, true, true);
1656 localObj->DefineProperty(vm_, name, descriptor);
1657 }
1658 }
1659
IsWithinVariableScope(const LocalVariableInfo & localVariableInfo,uint32_t bcOffset)1660 bool DebuggerImpl::IsWithinVariableScope(const LocalVariableInfo &localVariableInfo, uint32_t bcOffset)
1661 {
1662 return bcOffset >= localVariableInfo.startOffset && bcOffset < localVariableInfo.endOffset;
1663 }
1664
IsVariableSkipped(const std::string & varName)1665 bool DebuggerImpl::IsVariableSkipped(const std::string &varName)
1666 {
1667 return varName == "4newTarget" || varName == "0this" || varName == "0newTarget" || varName == "0funcObj";
1668 }
1669
GetGlobalScopeChain()1670 std::unique_ptr<Scope> DebuggerImpl::GetGlobalScopeChain()
1671 {
1672 auto globalScope = std::make_unique<Scope>();
1673
1674 std::unique_ptr<RemoteObject> global = std::make_unique<RemoteObject>();
1675 Local<ObjectRef> globalObj = ObjectRef::New(vm_);
1676 global->SetType(ObjectType::Object)
1677 .SetObjectId(runtime_->curObjectId_)
1678 .SetClassName(ObjectClassName::Global)
1679 .SetDescription(RemoteObject::GlobalDescription);
1680 globalScope->SetType(Scope::Type::Global()).SetObject(std::move(global));
1681 globalObj = JSNApi::GetGlobalObject(vm_);
1682 DebuggerApi::AddInternalProperties(vm_, globalObj, ArkInternalValueType::Scope, runtime_->internalObjects_);
1683 runtime_->properties_[runtime_->curObjectId_++] = Global<JSValueRef>(vm_, globalObj);
1684 return globalScope;
1685 }
1686
UpdateScopeObject(const FrameHandler * frameHandler,std::string_view varName,Local<JSValueRef> newVal)1687 void DebuggerImpl::UpdateScopeObject(const FrameHandler *frameHandler,
1688 std::string_view varName, Local<JSValueRef> newVal)
1689 {
1690 auto *sp = DebuggerApi::GetSp(frameHandler);
1691 auto iter = scopeObjects_.find(sp);
1692 if (iter == scopeObjects_.end()) {
1693 LOG_DEBUGGER(ERROR) << "UpdateScopeObject: object not found";
1694 return;
1695 }
1696
1697 auto objectId = iter->second;
1698 Local<ObjectRef> localObj = runtime_->properties_[objectId].ToLocal(vm_);
1699 Local<JSValueRef> name = StringRef::NewFromUtf8(vm_, varName.data());
1700 if (localObj->Has(vm_, name)) {
1701 LOG_DEBUGGER(DEBUG) << "UpdateScopeObject: set new value";
1702 PropertyAttribute descriptor(newVal, true, true, true);
1703 localObj->DefineProperty(vm_, name, descriptor);
1704 } else {
1705 LOG_DEBUGGER(ERROR) << "UpdateScopeObject: not found " << varName;
1706 }
1707 }
1708
ClearSingleStepper()1709 void DebuggerImpl::ClearSingleStepper()
1710 {
1711 // if current depth is 0, then it is safe to reset
1712 if (singleStepper_ != nullptr && DebuggerApi::GetStackDepth(vm_) == 0) {
1713 singleStepper_.reset();
1714 }
1715 }
1716
CmptEvaluateValue(CallFrameId callFrameId,const std::string & expression,std::unique_ptr<RemoteObject> * result)1717 std::optional<std::string> DebuggerImpl::CmptEvaluateValue(CallFrameId callFrameId, const std::string &expression,
1718 std::unique_ptr<RemoteObject> *result)
1719 {
1720 if (DebuggerApi::IsNativeMethod(vm_)) {
1721 *result = RemoteObject::FromTagged(vm_,
1722 Exception::EvalError(vm_, StringRef::NewFromUtf8(vm_, "Native Frame not support.")));
1723 return "Native Frame not support.";
1724 }
1725 DebugInfoExtractor *extractor = GetExtractor(DebuggerApi::GetJSPandaFile(vm_));
1726 if (extractor == nullptr) {
1727 *result = RemoteObject::FromTagged(vm_,
1728 Exception::EvalError(vm_, StringRef::NewFromUtf8(vm_, "Internal error.")));
1729 return "Internal error.";
1730 }
1731 std::string varName = expression;
1732 std::string varValue;
1733 std::string::size_type indexEqual = expression.find_first_of('=', 0);
1734 if (indexEqual != std::string::npos) {
1735 varName = Trim(expression.substr(0, indexEqual));
1736 varValue = Trim(expression.substr(indexEqual + 1, expression.length()));
1737 }
1738
1739 Local<StringRef> name = StringRef::NewFromUtf8(vm_, varName.c_str());
1740 FrameHandler *frameHandler = callFrameHandlers_[callFrameId].get();
1741 if (varValue.empty()) {
1742 Local<JSValueRef> ret = DebuggerExecutor::GetValue(vm_, frameHandler, name);
1743 if (!ret.IsEmpty()) {
1744 *result = RemoteObject::FromTagged(vm_, ret);
1745 runtime_->CacheObjectIfNeeded(ret, (*result).get());
1746 return {};
1747 }
1748 } else {
1749 Local<JSValueRef> value = ConvertToLocal(varValue);
1750 if (value.IsEmpty()) {
1751 return "Unsupported expression.";
1752 }
1753 JsDebuggerManager *mgr = vm_->GetJsDebuggerManager();
1754 mgr->SetEvalFrameHandler(callFrameHandlers_[callFrameId]);
1755 bool ret = DebuggerExecutor::SetValue(vm_, frameHandler, name, value);
1756 mgr->SetEvalFrameHandler(nullptr);
1757 if (ret) {
1758 *result = RemoteObject::FromTagged(vm_, value);
1759 return {};
1760 }
1761 }
1762
1763 *result = RemoteObject::FromTagged(vm_,
1764 Exception::EvalError(vm_, StringRef::NewFromUtf8(vm_, "Unsupported expression.")));
1765 return "Unsupported expression.";
1766 }
1767
ConvertToLocal(const std::string & varValue)1768 Local<JSValueRef> DebuggerImpl::ConvertToLocal(const std::string &varValue)
1769 {
1770 Local<JSValueRef> taggedValue;
1771 if (varValue == "false") {
1772 taggedValue = JSValueRef::False(vm_);
1773 } else if (varValue == "true") {
1774 taggedValue = JSValueRef::True(vm_);
1775 } else if (varValue == "undefined") {
1776 taggedValue = JSValueRef::Undefined(vm_);
1777 } else if (varValue[0] == '\"' && varValue[varValue.length() - 1] == '\"') {
1778 // 2 : 2 means length
1779 taggedValue = StringRef::NewFromUtf8(vm_, varValue.substr(1, varValue.length() - 2).c_str());
1780 } else {
1781 auto begin = reinterpret_cast<const uint8_t *>((varValue.c_str()));
1782 auto end = begin + varValue.length(); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
1783 double d = DebuggerApi::StringToDouble(begin, end, 0);
1784 if (!std::isnan(d)) {
1785 taggedValue = NumberRef::New(vm_, d);
1786 }
1787 }
1788 return taggedValue;
1789 }
1790
DecodeAndCheckBase64(const std::string & src,std::vector<uint8_t> & dest)1791 bool DebuggerImpl::DecodeAndCheckBase64(const std::string &src, std::vector<uint8_t> &dest)
1792 {
1793 dest.resize(PtBase64::DecodedSize(src.size()));
1794 auto [numOctets, done] = PtBase64::Decode(dest.data(), src.data(), src.size());
1795 dest.resize(numOctets);
1796 if ((done && numOctets > panda_file::File::MAGIC_SIZE) &&
1797 memcmp(dest.data(), panda_file::File::MAGIC.data(), panda_file::File::MAGIC_SIZE) == 0) {
1798 return true;
1799 }
1800 return false;
1801 }
1802
CheckAndGenerateCondFunc(const std::optional<std::string> & condition)1803 Local<FunctionRef> DebuggerImpl::CheckAndGenerateCondFunc(const std::optional<std::string> &condition)
1804 {
1805 std::vector<uint8_t> dest;
1806 if (DecodeAndCheckBase64(condition.value(), dest)) {
1807 Local<FunctionRef> funcRef =
1808 DebuggerApi::GenerateFuncFromBuffer(vm_, dest.data(), dest.size(), JSPandaFile::ENTRY_FUNCTION_NAME);
1809 if (!funcRef->IsUndefined()) {
1810 return funcRef;
1811 }
1812 }
1813 return FunctionRef::Undefined(vm_);
1814 }
1815 } // namespace panda::ecmascript::tooling
1816