* Copyright (c) 2021-2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ECMASCRIPT_DEBUGGER_JS_DEBUGGER_H
#define ECMASCRIPT_DEBUGGER_JS_DEBUGGER_H
#include "ecmascript/debugger/debugger_api.h"
#include "ecmascript/debugger/js_debugger_manager.h"
#include "ecmascript/debugger/js_pt_method.h"
#include "ecmascript/ecma_vm.h"
#include "ecmascript/jspandafile/method_literal.h"
namespace panda::ecmascript::tooling {
class JSBreakpoint {
public:
JSBreakpoint(const std::string &sourceFile, std::shared_ptr<PtMethod> ptMethod, uint32_t bcOffset,
const Global<FunctionRef> &condFuncRef) : sourceFile_(sourceFile), ptMethod_(std::move(ptMethod)),
bcOffset_(bcOffset), condFuncRef_(condFuncRef) {}
~JSBreakpoint() = default;
const std::string &GetSourceFile() const
{
return sourceFile_;
}
PtMethod *GetPtMethod() const
{
return ptMethod_.get();
}
uint32_t GetBytecodeOffset() const
{
return bcOffset_;
}
bool operator==(const JSBreakpoint &bpoint) const
{
return bcOffset_ == bpoint.GetBytecodeOffset() &&
ptMethod_->GetMethodId() == bpoint.GetPtMethod()->GetMethodId() &&
sourceFile_ == bpoint.GetSourceFile() &&
ptMethod_->GetJSPandaFile() == bpoint.GetPtMethod()->GetJSPandaFile();
}
std::string ToString() const
{
std::stringstream breakpoint;
breakpoint << "[";
breakpoint << "methodId:" << ptMethod_->GetMethodId() << ", ";
breakpoint << "bytecodeOffset:" << bcOffset_ << ", ";
breakpoint << "sourceFile:" << "\""<< sourceFile_ << "\""<< ", ";
breakpoint << "jsPandaFile:" << "\"" << ptMethod_->GetJSPandaFile()->GetJSPandaFileDesc() << "\"";
breakpoint << "]";
return breakpoint.str();
}
const Global<FunctionRef> &GetConditionFunction() const
{
return condFuncRef_;
}
DEFAULT_COPY_SEMANTIC(JSBreakpoint);
DEFAULT_MOVE_SEMANTIC(JSBreakpoint);
private:
std::string sourceFile_;
std::shared_ptr<PtMethod> ptMethod_;
uint32_t bcOffset_;
Global<FunctionRef> condFuncRef_;
};
class HashJSBreakpoint {
public:
size_t operator()(const JSBreakpoint &bpoint) const
{
return (std::hash<std::string>()(bpoint.GetSourceFile())) ^
(std::hash<uint32_t>()(bpoint.GetPtMethod()->GetMethodId().GetOffset())) ^
(std::hash<uint32_t>()(bpoint.GetBytecodeOffset()));
}
};
class PUBLIC_API JSDebugger : public JSDebugInterface, RuntimeListener {
public:
explicit JSDebugger(const EcmaVM *vm) : ecmaVm_(vm)
{
notificationMgr_ = ecmaVm_->GetJsDebuggerManager()->GetNotificationManager();
notificationMgr_->AddListener(this);
}
~JSDebugger() override
{
notificationMgr_->RemoveListener(this);
}
void RegisterHooks(PtHooks *hooks) override
{
hooks_ = hooks;
notificationMgr_->VmStartEvent();
}
void UnregisterHooks() override
{
notificationMgr_->VmDeathEvent();
hooks_ = nullptr;
}
bool HandleDebuggerStmt(JSHandle<Method> method, uint32_t bcOffset) override;
bool SetBreakpoint(const JSPtLocation &location, Local<FunctionRef> condFuncRef) override;
bool SetSmartBreakpoint(const JSPtLocation &location);
bool RemoveBreakpoint(const JSPtLocation &location) override;
void RemoveAllBreakpoints() override;
bool RemoveBreakpointsByUrl(const std::string &url) override;
void BytecodePcChanged(JSThread *thread, JSHandle<Method> method, uint32_t bcOffset) override;
void GenerateAsyncFrames(std::shared_ptr<AsyncStack> asyncStack, bool skipTopFrame) override
{
if (hooks_ == nullptr) {
return;
}
hooks_->GenerateAsyncFrames(asyncStack, skipTopFrame);
}
void LoadModule(std::string_view filename, std::string_view entryPoint) override
{
if (hooks_ == nullptr) {
return;
}
hooks_->LoadModule(filename, entryPoint);
}
void VmStart() override
{
if (hooks_ == nullptr) {
return;
}
hooks_->VmStart();
}
void VmDeath() override
{
if (hooks_ == nullptr) {
return;
}
hooks_->VmDeath();
}
void NativeCalling(const void *nativeAddress) override
{
if (hooks_ == nullptr) {
return;
}
hooks_->NativeCalling(nativeAddress);
}
void NativeReturn(const void *nativeAddress) override
{
if (hooks_ == nullptr) {
return;
}
hooks_->NativeReturn(nativeAddress);
}
void MethodEntry(JSHandle<Method> method, JSHandle<JSTaggedValue> envHandle) override;
void MethodExit(JSHandle<Method> method) override;
bool GetSingleStepStatus() const
{
return singleStepOnDebuggerStmt_;
}
void SetSingleStepStatus(bool status)
{
singleStepOnDebuggerStmt_ = status;
}
void DisableFirstTimeFlag()
{
if (hooks_ == nullptr) {
return;
}
hooks_->DisableFirstTimeFlag();
}
void SetSymbolicBreakpoint(const std::unordered_set<std::string> &functionNamesSet)
{
symbolicBreakpoints_.insert(functionNamesSet.begin(), functionNamesSet.end());
}
void RemoveSymbolicBreakpoint(const std::string &functionName)
{
auto symbolicBreakpoint = symbolicBreakpoints_.find(functionName);
if (symbolicBreakpoint != symbolicBreakpoints_.end()) {
symbolicBreakpoints_.erase(symbolicBreakpoint);
} else {
LOG_DEBUGGER(ERROR) << "RemoveSymbolicBreakpoint: invalid symbol breakpoint " << functionName;
}
}
void SetDebuggerAccessor(const JSHandle<GlobalEnv> &globalEnv) override
{
if (hooks_ == nullptr) {
return;
}
hooks_->SetDebuggerAccessor(globalEnv);
}
private:
std::unique_ptr<PtMethod> FindMethod(const JSPtLocation &location) const;
std::optional<JSBreakpoint> FindBreakpoint(JSHandle<Method> method, uint32_t bcOffset) const;
std::optional<JSBreakpoint> FindSmartBreakpoint(JSHandle<Method> method, uint32_t bcOffset) const;
bool RemoveBreakpoint(const std::unique_ptr<PtMethod> &ptMethod, uint32_t bcOffset);
bool RemoveSmartBreakpoint(const std::unique_ptr<PtMethod> &ptMethod, uint32_t bcOffset);
void HandleExceptionThrowEvent(const JSThread *thread, JSHandle<Method> method, uint32_t bcOffset);
bool HandleStep(JSHandle<Method> method, uint32_t bcOffset);
bool HandleNativeOut();
bool HandleBreakpoint(JSHandle<Method> method, uint32_t bcOffset);
void DumpBreakpoints();
bool IsBreakpointCondSatisfied(std::optional<JSBreakpoint> breakpoint) const;
void HandleSymbolicBreakpoint(const JSHandle<Method> &method);
const EcmaVM *ecmaVm_;
PtHooks *hooks_ {nullptr};
NotificationManager *notificationMgr_ {nullptr};
bool singleStepOnDebuggerStmt_ {false};
CUnorderedSet<JSBreakpoint, HashJSBreakpoint> breakpoints_ {};
CUnorderedSet<JSBreakpoint, HashJSBreakpoint> smartBreakpoints_ {};
std::unordered_set<std::string> symbolicBreakpoints_ {};
friend class JsDebuggerFriendTest;
};
}
#endif