1/*
2 * Copyright (c) 2023-2024 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/dfx/stackinfo/js_stackgetter.h"
17
18#include "ecmascript/compiler/aot_file/aot_file_manager.h"
19#include "ecmascript/global_env_constants-inl.h"
20#include "ecmascript/jspandafile/js_pandafile_manager.h"
21
22namespace panda::ecmascript {
23class CpuProfiler;
24class SamplesRecord;
25
26bool JsStackGetter::CheckFrameType(JSThread *thread, JSTaggedType *sp)
27{
28    FrameType type = FrameHandler::GetFrameType(sp);
29    if (type > FrameType::FRAME_TYPE_LAST || type < FrameType::FRAME_TYPE_FIRST) {
30        return false;
31    }
32
33    FrameIterator iterator(sp, thread);
34    iterator.Advance();
35    JSTaggedType *preSp = iterator.GetSp();
36    if (preSp == nullptr) {
37        return true;
38    }
39#if defined(PANDA_TARGET_64)
40    if (thread->IsAsmInterpreter() && !thread->IsLegalSp(reinterpret_cast<uintptr_t>(preSp))) {
41        return false;
42    }
43#endif
44    type = FrameHandler::GetFrameType(preSp);
45    if (type > FrameType::FRAME_TYPE_LAST || type < FrameType::FRAME_TYPE_FIRST) {
46        return false;
47    }
48    return true;
49}
50
51bool JsStackGetter::ParseMethodInfo(struct MethodKey &methodKey,
52                                    const FrameIterator &it,
53                                    const EcmaVM *vm,
54                                    FrameInfoTemp &codeEntry,
55                                    bool isCpuProfiler)
56{
57    auto method = it.CheckAndGetMethod();
58    if (method == nullptr) {
59        return false;
60    }
61    codeEntry.methodKey = methodKey;
62    if (method->IsNativeWithCallField()) {
63        FrameIterator itNext(it.GetSp(), it.GetThread());
64        itNext.Advance<GCVisitedFlag::IGNORED>();
65        GetNativeMethodCallPos(itNext, codeEntry);
66        GetNativeStack(vm, it, codeEntry.functionName, sizeof(codeEntry.functionName), isCpuProfiler);
67    } else {
68        const JSPandaFile *jsPandaFile = method->GetJSPandaFile();
69        if (jsPandaFile == nullptr) {
70            return false;
71        }
72        EntityId methodId = reinterpret_cast<MethodLiteral *>(methodKey.methodIdentifier)->GetMethodId();
73        // function name
74        const char *functionName = MethodLiteral::GetMethodName(jsPandaFile, methodId, true);
75        uint8_t length = strlen(functionName);
76        if (length != 0 && functionName[0] == '#') {
77            uint8_t index = length - 1;
78            while (functionName[index] != '#') {
79                index--;
80            }
81            functionName += (index + 1);  // #...#functionName
82        }
83        if (strlen(functionName) == 0) {
84            functionName = "anonymous";
85        }
86        if (!CheckAndCopy(codeEntry.functionName, sizeof(codeEntry.functionName), functionName)) {
87            return false;
88        }
89        uint8_t specialIndex = strlen(codeEntry.functionName) - 1;
90        while (specialIndex > 0 && codeEntry.functionName[specialIndex] != '^') {
91            specialIndex--;
92        }
93        if (codeEntry.functionName[specialIndex] == '^') {
94            codeEntry.functionName[specialIndex] = '\0';  // #...#functionName^1
95        }
96        // record name
97        const char *recordName = MethodLiteral::GetRecordNameWithSymbol(jsPandaFile, methodId);
98        if (strlen(recordName) != 0) {
99            if (!CheckAndCopy(codeEntry.recordName, sizeof(codeEntry.recordName), recordName)) {
100                return false;
101            }
102        }
103
104        DebugInfoExtractor *debugExtractor =
105            JSPandaFileManager::GetInstance()->GetJSPtExtractor(jsPandaFile);
106        if (debugExtractor == nullptr) {
107            return false;
108        }
109        // source file
110        const std::string &sourceFile = debugExtractor->GetSourceFile(methodId);
111        if (!sourceFile.empty()) {
112            if (!CheckAndCopy(codeEntry.url, sizeof(codeEntry.url), sourceFile.c_str())) {
113                return false;
114            }
115        }
116        // line number and clomn number
117        codeEntry.lineNumber = debugExtractor->GetFristLine(methodId);
118        codeEntry.columnNumber = debugExtractor->GetFristColumn(methodId);
119    }
120    return true;
121}
122
123bool JsStackGetter::CheckAndCopy(char *dest, size_t length, const char *src)
124{
125    int srcLength = strlen(src);
126    if (length <= static_cast<size_t>(srcLength) || strcpy_s(dest, srcLength + 1, src) != EOK) {
127        LOG_ECMA(ERROR) << "JsStackGetter strcpy_s failed, maybe srcLength more than destLength";
128        return false;
129    }
130    dest[srcLength] = '\0';
131    return true;
132}
133
134void JsStackGetter::GetNativeStack(const EcmaVM *vm, const FrameIterator &it, char *functionName, size_t size,
135                                   bool isCpuProfiler)
136{
137    std::stringstream stream;
138    JSFunction* function = JSFunction::Cast(it.GetFunction().GetTaggedObject());
139    JSTaggedValue extraInfoValue = function->GetNativeFunctionExtraInfo();
140    // it not allow thread check here, if enable thread check, it maybe deadlock in IsInThreadPool
141    JSThread *thread = vm->GetJSThreadNoCheck();
142    const GlobalEnvConstants *globalConst = thread->GlobalConstants();
143    JSHandle<JSTaggedValue> nameKey = globalConst->GetHandledNameString();
144    JSHandle<JSTaggedValue> func(thread, function);
145    JSHandle<JSTaggedValue> funcNameValue = JSObject::GetProperty(thread, func, nameKey).GetValue();
146    std::string methodNameStr;
147    if (funcNameValue->IsString()) {
148        JSHandle<EcmaString> methodName(funcNameValue);
149        methodNameStr = EcmaStringAccessor(methodName).ToStdString();
150    }
151    // napi method
152    if (isCpuProfiler && function->IsCallNapi() && extraInfoValue.CheckIsJSNativePointer()) {
153        JSNativePointer *extraInfo = JSNativePointer::Cast(extraInfoValue.GetTaggedObject());
154        auto cb = vm->GetNativePtrGetter();
155        if (cb != nullptr  && extraInfo != nullptr) {
156            if (!vm->GetJSThreadNoCheck()->CpuProfilerCheckJSTaggedType(extraInfoValue.GetRawData())) {
157                return;
158            }
159            auto addr = cb(reinterpret_cast<void *>(extraInfo->GetData()));
160            stream << addr;
161            if (!CheckAndCopy(functionName, size, methodNameStr.c_str())) {
162                return;
163            }
164            const uint8_t methodNameStrLength = methodNameStr.size();
165            if (!CheckAndCopy(functionName + methodNameStrLength, size - methodNameStrLength, "(")) {
166                return;
167            }
168            const uint8_t napiBeginLength = 1; // 1:the length of "("
169            if (!CheckAndCopy(functionName + methodNameStrLength + napiBeginLength,
170                size - methodNameStrLength - napiBeginLength, stream.str().c_str())) {
171                return;
172            }
173            uint8_t srcLength = stream.str().size();
174            CheckAndCopy(functionName + methodNameStrLength + napiBeginLength + srcLength,
175                size - methodNameStrLength - napiBeginLength - srcLength, ")");
176            return;
177        }
178    }
179    CheckAndCopy(functionName, size, methodNameStr.c_str());
180}
181
182RunningState JsStackGetter::GetRunningState(const FrameIterator &it, const EcmaVM *vm,
183                                            bool isNative, bool topFrame,
184                                            bool enableVMTag)
185{
186    JSThread *thread = vm->GetAssociatedJSThread();
187    JSFunction* function = JSFunction::Cast(it.GetFunction().GetTaggedObject());
188
189    if (enableVMTag) {
190        if (topFrame) {
191            if (thread->GetGcState()) {
192                return RunningState::GC;
193            }
194            if (isNative && (it.IsLeaveFrame() || thread->GetRuntimeState())) {
195                return RunningState::RUNTIME;
196            }
197        }
198
199        if (function->IsCallNapi()) {
200            return RunningState::NAPI;
201        }
202        if (isNative) {
203            return function->GetNativeFunctionExtraInfo().CheckIsJSNativePointer() ? RunningState::ARKUI_ENGINE :
204                                                                                     RunningState::BUILTIN;
205        }
206        if (it.IsFastJitFunctionFrame()) {
207            return RunningState::JIT;
208        }
209        if (it.IsOptimizedJSFunctionFrame()) {
210            return RunningState::AOT;
211        }
212        if (thread->IsAsmInterpreter()) {
213            // For Methods that is compiled in AOT but deoptimized at runtime, we mark it as AINT-D
214            Method *method = Method::Cast(function->GetMethod());
215            MethodLiteral *methodLiteral = method->GetMethodLiteral();
216            if (methodLiteral != nullptr && MethodLiteral::IsAotWithCallField(methodLiteral->GetCallField())) {
217                return RunningState::AINT_D;
218            }
219            return RunningState::AINT;
220        }
221        return RunningState::CINT;
222    }
223
224    if (topFrame) {
225        if (function->IsCallNapi()) {
226            return RunningState::NAPI;
227        }
228        if (thread->GetGcState()) {
229            return RunningState::GC;
230        }
231    }
232
233    if (function->IsCallNapi()) {
234        return RunningState::NAPI;
235    }
236    if (isNative) {
237        return function->GetNativeFunctionExtraInfo().CheckIsJSNativePointer() ? RunningState::ARKUI_ENGINE :
238                                                                                 RunningState::BUILTIN;
239    }
240
241    return RunningState::OTHER;
242}
243
244void JsStackGetter::GetNativeMethodCallPos(FrameIterator &it, FrameInfoTemp &codeEntry)
245{
246    auto nextMethod = it.CheckAndGetMethod();
247    if (nextMethod == nullptr) {
248        return ;
249    }
250    JSFunction* function = JSFunction::Cast(it.GetFunction().GetTaggedObject());
251    JSTaggedValue extraInfoValue = function->GetNativeFunctionExtraInfo();
252    if (!extraInfoValue.CheckIsJSNativePointer() && nextMethod->GetJSPandaFile() != nullptr) {
253        DebugInfoExtractor *debugExtractor =
254            JSPandaFileManager::GetInstance()->GetJSPtExtractor(nextMethod->GetJSPandaFile());
255        if (debugExtractor == nullptr) {
256            return;
257        }
258        MethodLiteral *methodLiteral = nextMethod->GetMethodLiteral();
259        if (methodLiteral == nullptr) {
260            return;
261        }
262        panda_file::File::EntityId methodId = methodLiteral->GetMethodId();
263        const std::string &sourceFile = debugExtractor->GetSourceFile(methodId);
264        const char *tempVariable;
265        if (sourceFile.empty()) {
266            tempVariable = "";
267        } else {
268            tempVariable = sourceFile.c_str();
269        }
270        if (!CheckAndCopy(codeEntry.url, sizeof(codeEntry.url), tempVariable)) {
271            return;
272        }
273        int lineNumber = 0;
274        auto callbackLineFunc = [&lineNumber](int32_t line) -> bool {
275            lineNumber = line + 1;
276            return true;
277        };
278        int columnNumber = 0;
279        auto callbackColumnFunc = [&columnNumber](int32_t column) -> bool {
280            columnNumber += column + 1;
281            return true;
282        };
283        uint32_t offset = it.GetBytecodeOffset();
284        if (!debugExtractor->MatchLineWithOffset(callbackLineFunc, methodId, offset)) {
285            lineNumber = 0;
286        }
287        if (!debugExtractor->MatchColumnWithOffset(callbackColumnFunc, methodId, offset)) {
288            columnNumber = 0;
289        }
290        codeEntry.lineNumber = lineNumber;
291        codeEntry.columnNumber = columnNumber;
292    }
293}
294
295void *JsStackGetter::GetMethodIdentifier(Method *method, const FrameIterator &it)
296{
297    JSFunction* function = JSFunction::Cast(it.GetFunction().GetTaggedObject());
298    JSTaggedValue extraInfoValue = function->GetNativeFunctionExtraInfo();
299    if (method->IsNativeWithCallField()) {
300        if (extraInfoValue.CheckIsJSNativePointer()) {
301            JSNativePointer *extraInfo = JSNativePointer::Cast(extraInfoValue.GetTaggedObject());
302            return reinterpret_cast<void *>(extraInfo->GetData());
303        }
304        return const_cast<void *>(method->GetNativePointer());
305    }
306
307    MethodLiteral *methodLiteral = method->GetMethodLiteral();
308    return reinterpret_cast<void *>(methodLiteral);
309}
310void JsStackGetter::GetCallLineNumber(const FrameIterator &it, int &LineNumber)
311{
312    FrameIterator itNext(it.GetSp(), it.GetThread());
313    itNext.Advance<GCVisitedFlag::IGNORED>();
314    auto nextMethod = itNext.CheckAndGetMethod();
315    if (nextMethod == nullptr) {
316        return ;
317    }
318    JSFunction* function = JSFunction::Cast(itNext.GetFunction().GetTaggedObject());
319    JSTaggedValue extraInfoValue = function->GetNativeFunctionExtraInfo();
320    if (!extraInfoValue.CheckIsJSNativePointer() && nextMethod->GetJSPandaFile() != nullptr) {
321        DebugInfoExtractor *debugExtractor =
322            JSPandaFileManager::GetInstance()->GetJSPtExtractor(nextMethod->GetJSPandaFile());
323        if (debugExtractor == nullptr) {
324            return;
325        }
326        MethodLiteral *methodLiteral = nextMethod->GetMethodLiteral();
327        if (methodLiteral == nullptr) {
328            return;
329        }
330        panda_file::File::EntityId methodId = methodLiteral->GetMethodId();
331        int lineNum = 0;
332        auto callbackLineFunc = [&lineNum](int32_t line) -> bool {
333            lineNum = line + 1;
334            return true;
335        };
336        uint32_t offset = itNext.GetBytecodeOffset();
337        if (!debugExtractor->MatchLineWithOffset(callbackLineFunc, methodId, offset)) {
338            lineNum = 0;
339        }
340        LineNumber = lineNum;
341    }
342}
343} // namespace panda::ecmascript
344