1/*
2 * Copyright (c) 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 "ecmascript/debugger/dropframe_manager.h"
17
18#include "ecmascript/interpreter/interpreter-inl.h"
19#include "ecmascript/jobs/micro_job_queue.h"
20
21namespace panda::ecmascript::tooling {
22bool DropframeManager::IsNewlexenvOpcode(BytecodeInstruction::Opcode op)
23{
24    switch (op) {
25        case BytecodeInstruction::Opcode::NEWLEXENV_IMM8:
26        case BytecodeInstruction::Opcode::NEWLEXENVWITHNAME_IMM8_ID16:
27        case BytecodeInstruction::Opcode::WIDE_NEWLEXENV_PREF_IMM16:
28        case BytecodeInstruction::Opcode::WIDE_NEWLEXENVWITHNAME_PREF_IMM16_ID16:
29            return true;
30        default:
31            break;
32    }
33    return false;
34}
35
36bool DropframeManager::IsStlexvarOpcode(BytecodeInstruction::Opcode op)
37{
38    switch (op) {
39        case BytecodeInstruction::Opcode::STLEXVAR_IMM4_IMM4:
40        case BytecodeInstruction::Opcode::STLEXVAR_IMM8_IMM8:
41        case BytecodeInstruction::Opcode::WIDE_STLEXVAR_PREF_IMM16_IMM16:
42            return true;
43        default:
44            break;
45    }
46    return false;
47}
48
49std::pair<uint16_t, uint16_t> DropframeManager::ReadStlexvarParams(const uint8_t *pc, BytecodeInstruction::Opcode op)
50{
51    uint16_t level = 0;
52    uint16_t slot = 0;
53    switch (op) {
54        case BytecodeInstruction::Opcode::STLEXVAR_IMM4_IMM4:
55            level = READ_INST_4_0();
56            slot = READ_INST_4_1();
57            break;
58        case BytecodeInstruction::Opcode::STLEXVAR_IMM8_IMM8:
59            level = READ_INST_8_0();
60            slot = READ_INST_8_1();
61            break;
62        case BytecodeInstruction::Opcode::WIDE_STLEXVAR_PREF_IMM16_IMM16:
63            level = READ_INST_16_1();
64            slot = READ_INST_16_3();
65            break;
66        default:
67            break;
68    }
69    return std::make_pair(level, slot);
70}
71
72void DropframeManager::MethodEntry(JSThread *thread, JSHandle<Method> method, JSHandle<JSTaggedValue> envHandle)
73{
74    std::set<std::pair<uint16_t, uint16_t>> modifiedLexVarPos;
75    const JSPandaFile* methodJsPandaFile = method->GetJSPandaFile();
76    panda_file::File::EntityId methodId = method->GetMethodId();
77    PushMethodInfo(std::make_tuple(const_cast<JSPandaFile *>(methodJsPandaFile), methodId));
78    if (method->IsSendableMethod()) {
79        PushMethodType(MethodType::SENDABLE_METHOD);
80        return;
81    }
82    NewLexModifyRecordLevel();
83    PushPromiseQueueSizeRecord(thread);
84    if (!envHandle->IsLexicalEnv()) {
85        PushMethodType(MethodType::OTHER_METHOD);
86        return;
87    }
88    PushMethodType(MethodType::NORMAL_METHOD);
89    uint32_t codeSize = method->GetCodeSize();
90    uint16_t newEnvCount = 0;
91    auto bcIns = BytecodeInstruction(method->GetBytecodeArray());
92    auto bcInsLast = bcIns.JumpTo(codeSize);
93    while (bcIns.GetAddress() != bcInsLast.GetAddress()) {
94        AddLexPropertiesToRecord(thread, bcIns, newEnvCount, modifiedLexVarPos, envHandle);
95        bcIns = bcIns.GetNext();
96    }
97}
98
99void DropframeManager::AddLexPropertiesToRecord(JSThread *thread, BytecodeInstruction &bcIns, uint16_t &newEnvCount,
100    std::set<std::pair<uint16_t, uint16_t>> &modifiedLexVarPos, JSHandle<JSTaggedValue> envHandle)
101{
102    BytecodeInstruction::Opcode op = bcIns.GetOpcode();
103    if (IsNewlexenvOpcode(op)) {
104        newEnvCount++;
105        return;
106    }
107    if (IsStlexvarOpcode(op)) {
108        std::pair<uint16_t, uint16_t> lexVarPos = ReadStlexvarParams(bcIns.GetAddress(), op);
109        uint16_t level;
110        uint16_t slot;
111        std::tie(level, slot) = lexVarPos;
112        JSTaggedValue env = envHandle.GetTaggedValue();
113        for (uint16_t i = 0; ; i++) {
114            if ((level < newEnvCount || i >= level - newEnvCount) &&
115                slot < LexicalEnv::Cast(env.GetTaggedObject())->GetLength() - LexicalEnv::RESERVED_ENV_LENGTH &&
116                !modifiedLexVarPos.count({i, slot})) {
117                JSTaggedValue value = LexicalEnv::Cast(env.GetTaggedObject())->GetProperties(slot);
118                EmplaceLexModifyRecord(thread, env, slot, value);
119                modifiedLexVarPos.insert({i, slot});
120            }
121            if (i >= level) {
122                break;
123            }
124            JSTaggedValue taggedParentEnv = LexicalEnv::Cast(env.GetTaggedObject())->GetParentEnv();
125            if (!taggedParentEnv.IsLexicalEnv()) {
126                break;
127            }
128            env = taggedParentEnv;
129        }
130    }
131}
132
133void DropframeManager::MethodExit(JSThread *thread, [[maybe_unused]] JSHandle<Method> method)
134{
135    const JSPandaFile* methodJsPandaFile = method->GetJSPandaFile();
136    panda_file::File::EntityId methodId = method->GetMethodId();
137    if (!CheckExitMethodInfo(std::make_tuple(const_cast<JSPandaFile *>(methodJsPandaFile), methodId))) {
138        return;
139    }
140    PopMethodInfo();
141    if (CheckIsSendableMethod()) {
142        PopMethodType();
143        return;
144    }
145    PopMethodType();
146    MergeLexModifyRecordOfTopFrame(thread);
147    PopPromiseQueueSizeRecord();
148}
149
150void DropframeManager::DropLastFrame(JSThread *thread)
151{
152    std::vector<std::tuple<JSHandle<JSTaggedValue>, uint16_t, JSHandle<JSTaggedValue>>> lexModifyRecord;
153    lexModifyRecord = GetLexModifyRecordOfTopFrame();
154    for (const auto &item : lexModifyRecord) {
155        JSHandle<JSTaggedValue> envHandle;
156        uint16_t slot;
157        JSHandle<JSTaggedValue> valueHandle;
158        std::tie(envHandle, slot, valueHandle) = item;
159        JSTaggedValue env = envHandle.GetTaggedValue();
160        ASSERT(slot < LexicalEnv::Cast(env.GetTaggedObject())->GetLength() - LexicalEnv::RESERVED_ENV_LENGTH);
161        LexicalEnv::Cast(env.GetTaggedObject())->SetProperties(thread, slot, valueHandle.GetTaggedValue());
162    }
163    PopMethodInfo();
164    PopMethodType();
165    RemoveLexModifyRecordOfTopFrame(thread);
166    PopPromiseQueueSizeRecord();
167
168    FrameHandler frameHandler(thread);
169    bool isEntryFrameDropped = false;
170    while (frameHandler.HasFrame()) {
171        frameHandler.PrevJSFrame();
172        if (frameHandler.IsEntryFrame()) {
173            isEntryFrameDropped = true;
174            continue;
175        }
176        if (frameHandler.IsBuiltinFrame()) {
177            continue;
178        }
179        if (!thread->IsAsmInterpreter()) {
180            JSTaggedType *prevSp = frameHandler.GetSp();
181            thread->SetCurrentFrame(prevSp);
182        }
183        if (isEntryFrameDropped) {
184            thread->SetEntryFrameDroppedState();
185        }
186        thread->SetFrameDroppedState();
187        break;
188    }
189}
190
191void DropframeManager::NewLexModifyRecordLevel()
192{
193    modifiedLexVar_.push(std::vector<std::tuple<JSHandle<JSTaggedValue>, uint16_t, JSHandle<JSTaggedValue>>>());
194}
195
196void DropframeManager::EmplaceLexModifyRecord(JSThread *thread, JSTaggedValue env, uint16_t slot, JSTaggedValue value)
197{
198    GlobalHandleCollection globalHandleCollection(thread);
199    for (const auto &item : modifiedLexVar_.top()) {
200        JSHandle<JSTaggedValue> envHandleRecord = std::get<0>(item);
201        uint16_t slotRecord = std::get<1>(item);
202        if (envHandleRecord.GetTaggedType() == env.GetRawData() && slotRecord == slot) {
203            return;
204        }
205    }
206    JSHandle<JSTaggedValue> envHandle = globalHandleCollection.NewHandle<JSTaggedValue>(env.GetRawData());
207    JSHandle<JSTaggedValue> valueHandle = globalHandleCollection.NewHandle<JSTaggedValue>(value.GetRawData());
208    modifiedLexVar_.top().emplace_back(envHandle, slot, valueHandle);
209}
210
211uint32_t DropframeManager::GetLexModifyRecordLevel()
212{
213    return modifiedLexVar_.size();
214}
215
216std::vector<std::tuple<JSHandle<JSTaggedValue>, uint16_t, JSHandle<JSTaggedValue>>>
217    DropframeManager::GetLexModifyRecordOfTopFrame()
218{
219    if (modifiedLexVar_.empty()) {
220        return std::vector<std::tuple<JSHandle<JSTaggedValue>, uint16_t, JSHandle<JSTaggedValue>>>();
221    }
222    return modifiedLexVar_.top();
223}
224
225void DropframeManager::RemoveLexModifyRecordOfTopFrame(JSThread *thread)
226{
227    if (modifiedLexVar_.empty()) {
228        return;
229    }
230    GlobalHandleCollection globalHandleCollection(thread);
231    for (const auto &item : modifiedLexVar_.top()) {
232        JSHandle<JSTaggedValue> envHandle = std::get<0>(item);
233        JSHandle<JSTaggedValue> valueHandle = std::get<2>(item); // 2:get the third item of the tuple
234        globalHandleCollection.Dispose(envHandle);
235        globalHandleCollection.Dispose(valueHandle);
236    }
237    modifiedLexVar_.pop();
238}
239
240void DropframeManager::MergeLexModifyRecordOfTopFrame(JSThread *thread)
241{
242    if (modifiedLexVar_.empty()) {
243        return;
244    }
245    GlobalHandleCollection globalHandleCollection(thread);
246    std::vector<std::tuple<JSHandle<JSTaggedValue>, uint16_t, JSHandle<JSTaggedValue>>> lexModifyRecord;
247    lexModifyRecord = modifiedLexVar_.top();
248    modifiedLexVar_.pop();
249    if (!modifiedLexVar_.empty() && modifiedLexVar_.top().empty()) {
250        modifiedLexVar_.pop();
251        modifiedLexVar_.push(lexModifyRecord);
252        return;
253    }
254    for (const auto &item : lexModifyRecord) {
255        JSHandle<JSTaggedValue> envHandle;
256        uint16_t slot;
257        JSHandle<JSTaggedValue> valueHandle;
258        std::tie(envHandle, slot, valueHandle) = item;
259        bool existRecord = false;
260        if (!modifiedLexVar_.empty()) {
261            for (const auto &itemLast : modifiedLexVar_.top()) {
262                JSHandle<JSTaggedValue> envHandleRecord = std::get<0>(itemLast);
263                uint16_t slotRecord = std::get<1>(itemLast);
264                if (envHandleRecord.GetTaggedType() == envHandle.GetTaggedType() && slotRecord == slot) {
265                    existRecord = true;
266                    break;
267                }
268            }
269        }
270        if (modifiedLexVar_.empty() || existRecord) {
271            globalHandleCollection.Dispose(envHandle);
272            globalHandleCollection.Dispose(valueHandle);
273        } else {
274            modifiedLexVar_.top().emplace_back(envHandle, slot, valueHandle);
275        }
276    }
277}
278
279void DropframeManager::PushPromiseQueueSizeRecord(JSThread *thread)
280{
281    EcmaContext *context = thread->GetCurrentEcmaContext();
282    uint32_t queueSize = job::MicroJobQueue::GetPromiseQueueSize(thread, context->GetMicroJobQueue());
283    promiseQueueSizeRecord_.push(queueSize);
284}
285
286uint32_t DropframeManager::GetPromiseQueueSizeRecordOfTopFrame()
287{
288    ASSERT(!promiseQueueSizeRecord_.empty());
289    return promiseQueueSizeRecord_.top();
290}
291
292void DropframeManager::PopPromiseQueueSizeRecord()
293{
294    if (!promiseQueueSizeRecord_.empty()) {
295        promiseQueueSizeRecord_.pop();
296    }
297}
298
299void DropframeManager::PushMethodInfo(std::tuple<JSPandaFile*,
300    panda_file::File::EntityId> methodInfo)
301{
302    methodInfo_.push(methodInfo);
303}
304
305bool DropframeManager::CheckExitMethodInfo(std::tuple<JSPandaFile*,
306    panda_file::File::EntityId> methodInfo)
307{
308    if (methodInfo_.empty()) {
309        return false;
310    }
311    if (methodInfo == methodInfo_.top()) {
312        return true;
313    }
314    return false;
315}
316
317void DropframeManager::PopMethodInfo()
318{
319    if (!methodInfo_.empty()) {
320        methodInfo_.pop();
321    }
322}
323
324void DropframeManager::PushMethodType(MethodType methodType)
325{
326    methodType_.push(methodType);
327}
328
329bool DropframeManager::CheckIsSendableMethod()
330{
331    ASSERT(!methodType_.empty());
332    return methodType_.top() == MethodType::SENDABLE_METHOD;
333}
334
335void DropframeManager::PopMethodType()
336{
337    if (!methodType_.empty()) {
338        methodType_.pop();
339    }
340}
341}