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 "ecmascript/debugger/js_debugger.h"
17#include <memory>
18
19#include "ecmascript/interpreter/interpreter-inl.h"
20
21namespace panda::ecmascript::tooling {
22using panda::ecmascript::base::BuiltinsBase;
23
24bool JSDebugger::SetBreakpoint(const JSPtLocation &location, Local<FunctionRef> condFuncRef)
25{
26    std::unique_ptr<PtMethod> ptMethod = FindMethod(location);
27    if (ptMethod == nullptr) {
28        LOG_DEBUGGER(ERROR) << "SetBreakpoint: Cannot find MethodLiteral";
29        return false;
30    }
31
32    if (location.GetBytecodeOffset() >= ptMethod->GetCodeSize()) {
33        LOG_DEBUGGER(ERROR) << "SetBreakpoint: Invalid breakpoint location";
34        return false;
35    }
36
37    auto [_, success] = breakpoints_.emplace(location.GetSourceFile(), ptMethod.release(),
38        location.GetBytecodeOffset(), Global<FunctionRef>(ecmaVm_, condFuncRef));
39    if (!success) {
40        // also return true
41        LOG_DEBUGGER(WARN) << "SetBreakpoint: Breakpoint already exists";
42    }
43
44    DumpBreakpoints();
45    return true;
46}
47
48bool JSDebugger::SetSmartBreakpoint(const JSPtLocation &location)
49{
50    std::unique_ptr<PtMethod> ptMethod = FindMethod(location);
51    if (ptMethod == nullptr) {
52        LOG_DEBUGGER(ERROR) << "SetSmartBreakpoint: Cannot find MethodLiteral";
53        return false;
54    }
55
56    if (location.GetBytecodeOffset() >= ptMethod->GetCodeSize()) {
57        LOG_DEBUGGER(ERROR) << "SetSmartBreakpoint: Invalid breakpoint location";
58        return false;
59    }
60
61    auto [_, success] = smartBreakpoints_.emplace(location.GetSourceFile(), ptMethod.release(),
62        location.GetBytecodeOffset(), Global<FunctionRef>(ecmaVm_, FunctionRef::Undefined(ecmaVm_)));
63    if (!success) {
64        // also return true
65        LOG_DEBUGGER(WARN) << "SetSmartBreakpoint: Breakpoint already exists";
66    }
67
68    DumpBreakpoints();
69    return true;
70}
71
72bool JSDebugger::RemoveBreakpoint(const JSPtLocation &location)
73{
74    std::unique_ptr<PtMethod> ptMethod = FindMethod(location);
75    if (ptMethod == nullptr) {
76        LOG_DEBUGGER(ERROR) << "RemoveBreakpoint: Cannot find MethodLiteral";
77        return false;
78    }
79
80    if (!RemoveBreakpoint(ptMethod, location.GetBytecodeOffset())) {
81        LOG_DEBUGGER(ERROR) << "RemoveBreakpoint: Breakpoint not found";
82        return false;
83    }
84
85    DumpBreakpoints();
86    return true;
87}
88
89void JSDebugger::RemoveAllBreakpoints()
90{
91    breakpoints_.clear();
92}
93
94bool JSDebugger::RemoveBreakpointsByUrl(const std::string &url)
95{
96    for (auto it = breakpoints_.begin(); it != breakpoints_.end();) {
97        const auto &bp = *it;
98        if (bp.GetSourceFile() == url) {
99            it = breakpoints_.erase(it);
100        } else {
101            it++;
102        }
103    }
104
105    DumpBreakpoints();
106    return true;
107}
108
109void JSDebugger::BytecodePcChanged(JSThread *thread, JSHandle<Method> method, uint32_t bcOffset)
110{
111    ASSERT(bcOffset < method->GetCodeSize() && "code size of current Method less then bcOffset");
112    HandleExceptionThrowEvent(thread, method, bcOffset);
113    // clear singlestep flag
114    singleStepOnDebuggerStmt_ = false;
115    if (ecmaVm_->GetJsDebuggerManager()->IsMixedStackEnabled()) {
116        if (!HandleBreakpoint(method, bcOffset)) {
117            HandleNativeOut();
118            HandleStep(method, bcOffset);
119        }
120    } else  {
121        if (!HandleStep(method, bcOffset)) {
122            HandleBreakpoint(method, bcOffset);
123        }
124    }
125}
126
127bool JSDebugger::HandleNativeOut()
128{
129    if (hooks_ == nullptr) {
130        return false;
131    }
132
133    return hooks_->NativeOut();
134}
135
136bool JSDebugger::HandleBreakpoint(JSHandle<Method> method, uint32_t bcOffset)
137{
138    if (hooks_ == nullptr) {
139        return false;
140    }
141
142    auto smartBreakpoint = FindSmartBreakpoint(method, bcOffset);
143    if (smartBreakpoint.has_value()) {
144        JSPtLocation smartLocation {method->GetJSPandaFile(), method->GetMethodId(), bcOffset,
145            smartBreakpoint.value().GetSourceFile()};
146        std::unique_ptr<PtMethod> ptMethod = FindMethod(smartLocation);
147        RemoveSmartBreakpoint(ptMethod, bcOffset);
148        hooks_->Breakpoint(smartLocation);
149        return true;
150    }
151
152    auto breakpoint = FindBreakpoint(method, bcOffset);
153    if (!breakpoint.has_value() || !IsBreakpointCondSatisfied(breakpoint)) {
154        return false;
155    }
156    JSPtLocation location {method->GetJSPandaFile(), method->GetMethodId(), bcOffset,
157        breakpoint.value().GetSourceFile()};
158
159    hooks_->Breakpoint(location);
160    return true;
161}
162
163bool JSDebugger::HandleDebuggerStmt(JSHandle<Method> method, uint32_t bcOffset)
164{
165    if (hooks_ == nullptr || !ecmaVm_->GetJsDebuggerManager()->IsDebugMode()) {
166        return false;
167    }
168    // if debugger stmt is met by single stepping, disable debugger
169    // stmt to prevent pausing on this line twice
170    if (singleStepOnDebuggerStmt_) {
171        return false;
172    }
173    auto breakpointAtDebugger = FindBreakpoint(method, bcOffset);
174    // if a breakpoint is set on the same line as debugger stmt,
175    // the debugger stmt is ineffective
176    if (breakpointAtDebugger.has_value()) {
177        return false;
178    }
179    JSPtLocation location {method->GetJSPandaFile(), method->GetMethodId(), bcOffset};
180    hooks_->DebuggerStmt(location);
181
182    return true;
183}
184
185void JSDebugger::HandleExceptionThrowEvent(const JSThread *thread, JSHandle<Method> method, uint32_t bcOffset)
186{
187    if (hooks_ == nullptr || !thread->HasPendingException()) {
188        return;
189    }
190
191    JSPtLocation throwLocation {method->GetJSPandaFile(), method->GetMethodId(), bcOffset};
192
193    hooks_->Exception(throwLocation);
194}
195
196bool JSDebugger::HandleStep(JSHandle<Method> method, uint32_t bcOffset)
197{
198    if (hooks_ == nullptr) {
199        return false;
200    }
201
202    JSPtLocation location {method->GetJSPandaFile(), method->GetMethodId(), bcOffset};
203
204    return hooks_->SingleStep(location);
205}
206
207std::optional<JSBreakpoint> JSDebugger::FindBreakpoint(JSHandle<Method> method, uint32_t bcOffset) const
208{
209    for (const auto &bp : breakpoints_) {
210        if ((bp.GetBytecodeOffset() == bcOffset) &&
211            (bp.GetPtMethod()->GetJSPandaFile() == method->GetJSPandaFile()) &&
212            (bp.GetPtMethod()->GetMethodId() == method->GetMethodId())) {
213            return bp;
214        }
215    }
216    return {};
217}
218
219std::optional<JSBreakpoint> JSDebugger::FindSmartBreakpoint(JSHandle<Method> method, uint32_t bcOffset) const
220{
221    for (const auto &bp : smartBreakpoints_) {
222        if ((bp.GetBytecodeOffset() == bcOffset) &&
223            (bp.GetPtMethod()->GetJSPandaFile() == method->GetJSPandaFile()) &&
224            (bp.GetPtMethod()->GetMethodId() == method->GetMethodId())) {
225            return bp;
226        }
227    }
228    return {};
229}
230
231bool JSDebugger::RemoveBreakpoint(const std::unique_ptr<PtMethod> &ptMethod, uint32_t bcOffset)
232{
233    for (auto it = breakpoints_.begin(); it != breakpoints_.end(); ++it) {
234        const auto &bp = *it;
235        if ((bp.GetBytecodeOffset() == bcOffset) &&
236            (bp.GetPtMethod()->GetJSPandaFile() == ptMethod->GetJSPandaFile()) &&
237            (bp.GetPtMethod()->GetMethodId() == ptMethod->GetMethodId())) {
238            it = breakpoints_.erase(it);
239            return true;
240        }
241    }
242
243    return false;
244}
245
246bool JSDebugger::RemoveSmartBreakpoint(const std::unique_ptr<PtMethod> &ptMethod, uint32_t bcOffset)
247{
248    for (auto it = smartBreakpoints_.begin(); it != smartBreakpoints_.end(); ++it) {
249        const auto &bp = *it;
250        if ((bp.GetBytecodeOffset() == bcOffset) &&
251            (bp.GetPtMethod()->GetJSPandaFile() == ptMethod->GetJSPandaFile()) &&
252            (bp.GetPtMethod()->GetMethodId() == ptMethod->GetMethodId())) {
253            it = smartBreakpoints_.erase(it);
254            return true;
255        }
256    }
257
258    return false;
259}
260
261std::unique_ptr<PtMethod> JSDebugger::FindMethod(const JSPtLocation &location) const
262{
263    std::unique_ptr<PtMethod> ptMethod {nullptr};
264    ::panda::ecmascript::JSPandaFileManager::GetInstance()->EnumerateJSPandaFiles([&ptMethod, location](
265        const std::shared_ptr<JSPandaFile> &file) {
266        if (file->GetJSPandaFileDesc() == location.GetJsPandaFile()->GetJSPandaFileDesc()) {
267            MethodLiteral *methodsData = file->GetMethodLiterals();
268            uint32_t numberMethods = file->GetNumMethods();
269            for (uint32_t i = 0; i < numberMethods; ++i) {
270                if (methodsData[i].GetMethodId() == location.GetMethodId()) {
271                    MethodLiteral *methodLiteral = methodsData + i;
272                    ptMethod = std::make_unique<PtMethod>(file.get(),
273                        methodLiteral->GetMethodId(), methodLiteral->IsNativeWithCallField());
274                    return false;
275                }
276            }
277        }
278        return true;
279    });
280    return ptMethod;
281}
282
283void JSDebugger::DumpBreakpoints()
284{
285    LOG_DEBUGGER(INFO) << "dump breakpoints with size " << breakpoints_.size();
286    for (const auto &bp : breakpoints_) {
287        LOG_DEBUGGER(DEBUG) << bp.ToString();
288    }
289}
290
291bool JSDebugger::IsBreakpointCondSatisfied(std::optional<JSBreakpoint> breakpoint) const
292{
293    if (!breakpoint.has_value()) {
294        return false;
295    }
296    JSThread *thread = ecmaVm_->GetJSThread();
297    auto condFuncRef = breakpoint.value().GetConditionFunction();
298    if (condFuncRef->IsFunction(ecmaVm_)) {
299        LOG_DEBUGGER(INFO) << "BreakpointCondition: evaluating condition";
300        auto handlerPtr = std::make_shared<FrameHandler>(ecmaVm_->GetJSThread());
301        auto evalResult = DebuggerApi::EvaluateViaFuncCall(const_cast<EcmaVM *>(ecmaVm_),
302            condFuncRef.ToLocal(ecmaVm_), handlerPtr);
303        if (thread->HasPendingException()) {
304            LOG_DEBUGGER(ERROR) << "BreakpointCondition: has pending exception";
305            thread->ClearException();
306            return false;
307        }
308        bool satisfied = evalResult->ToBoolean(ecmaVm_)->Value();
309        if (!satisfied) {
310            LOG_DEBUGGER(INFO) << "BreakpointCondition: condition not meet";
311            return false;
312        }
313    }
314    return true;
315}
316
317void JSDebugger::MethodEntry(JSHandle<Method> method, JSHandle<JSTaggedValue> envHandle)
318{
319    if (hooks_ == nullptr || !ecmaVm_->GetJsDebuggerManager()->IsDebugMode()) {
320        return;
321    }
322    FrameHandler frameHandler(ecmaVm_->GetJSThread());
323    if (frameHandler.IsEntryFrame() || frameHandler.IsBuiltinFrame()) {
324        return;
325    }
326    auto *debuggerMgr = ecmaVm_->GetJsDebuggerManager();
327    debuggerMgr->MethodEntry(method, envHandle);
328
329    // scriptParsed for sendable object
330    if (method->IsSendableMethod()) {
331        hooks_->SendableMethodEntry(method);
332    }
333}
334
335void JSDebugger::MethodExit([[maybe_unused]] JSHandle<Method> method)
336{
337    if (hooks_ == nullptr || !ecmaVm_->GetJsDebuggerManager()->IsDebugMode()) {
338        return;
339    }
340    FrameHandler frameHandler(ecmaVm_->GetJSThread());
341    if (frameHandler.IsEntryFrame() || frameHandler.IsBuiltinFrame()) {
342        return;
343    }
344    auto *debuggerMgr = ecmaVm_->GetJsDebuggerManager();
345    debuggerMgr->MethodExit(method);
346}
347}  // namespace panda::tooling::ecmascript
348