1/*
2 * Copyright (c) 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 "evaluate/debugInfoStorage.h"
17#include "assembler/assembly-type.h"
18#include "generated/signatures.h"
19#include "evaluate/helpers.h"
20
21#include "libpandafile/class_data_accessor-inl.h"
22#include "libpandafile/file-inl.h"
23
24namespace ark::es2panda::evaluate {
25
26namespace {
27
28std::string GetFullRecordName(const panda_file::File &pf, const panda_file::File::EntityId &classId)
29{
30    std::string name = utf::Mutf8AsCString(pf.GetStringData(classId).data);
31
32    auto type = pandasm::Type::FromDescriptor(name);
33    type = pandasm::Type(type.GetComponentName(), type.GetRank());
34
35    return type.GetPandasmName();
36}
37
38bool EndsWith(std::string_view str, std::string_view suffix)
39{
40    auto pos = str.rfind(suffix);
41    return pos != std::string::npos && (str.size() - pos) == suffix.size();
42}
43
44}  // namespace
45
46ImportExportTable::ImportExportTable(ArenaAllocator *allocator)
47    : imports_(allocator->Adapter()), exports_(allocator->Adapter())
48{
49}
50
51DebugInfoStorage::DebugInfoStorage(const CompilerOptions &options, ArenaAllocator *allocator)
52    : allocator_(allocator), sourceFileToDebugInfo_(allocator->Adapter()), moduleNameToDebugInfo_(allocator->Adapter())
53{
54    for (const auto &pfPath : options.debuggerEvalPandaFiles) {
55        LoadFileDebugInfo(pfPath);
56    }
57}
58
59void DebugInfoStorage::LoadFileDebugInfo(std::string_view pfPath)
60{
61    auto pf = panda_file::OpenPandaFile(pfPath);
62    if (!pf) {
63        LOG(FATAL, ES2PANDA) << "Failed to load a provided abc file: " << pfPath;
64    }
65
66    for (auto id : pf->GetClasses()) {
67        panda_file::File::EntityId classId(id);
68        if (pf->IsExternal(classId)) {
69            continue;
70        }
71
72        auto recordName = GetFullRecordName(*pf, classId);
73        if (!EndsWith(recordName, compiler::Signatures::ETS_GLOBAL)) {
74            continue;
75        }
76
77        std::string_view moduleName = helpers::SplitRecordName(recordName).first;
78        auto *debugInfo = allocator_->New<FileDebugInfo>(std::move(pf), classId, moduleName);
79        auto sourceFileId = debugInfo->globalClassAcc.GetSourceFileId();
80        ASSERT(sourceFileId.has_value());
81        std::string_view sourceFileName = utf::Mutf8AsCString(debugInfo->pf->GetStringData(*sourceFileId).data);
82        debugInfo->sourceFilePath = sourceFileName;
83
84        sourceFileToDebugInfo_.emplace(sourceFileName, debugInfo);
85        moduleNameToDebugInfo_.emplace(moduleName, debugInfo);
86        return;
87    }
88
89    LOG(FATAL, ES2PANDA) << "ETSGLOBAL not found in provided file: " << pfPath;
90}
91
92const panda_file::File *DebugInfoStorage::GetPandaFile(std::string_view filePath)
93{
94    auto iter = sourceFileToDebugInfo_.find(filePath);
95    if (iter == sourceFileToDebugInfo_.end()) {
96        return nullptr;
97    }
98    return iter->second->pf.get();
99}
100
101const ImportExportTable *DebugInfoStorage::GetImportExportTable(std::string_view filePath)
102{
103    auto iter = sourceFileToDebugInfo_.find(filePath);
104    if (iter == sourceFileToDebugInfo_.end()) {
105        return nullptr;
106    }
107    return &LazyLoadImportExportTable(iter->second);
108}
109
110panda_file::ClassDataAccessor *DebugInfoStorage::GetGlobalClassAccessor(std::string_view filePath)
111{
112    auto iter = sourceFileToDebugInfo_.find(filePath);
113    if (iter == sourceFileToDebugInfo_.end()) {
114        return nullptr;
115    }
116    return &iter->second->globalClassAcc;
117}
118
119std::string_view DebugInfoStorage::GetModuleName(std::string_view filePath)
120{
121    auto iter = sourceFileToDebugInfo_.find(filePath);
122    if (iter == sourceFileToDebugInfo_.end()) {
123        return {};
124    }
125    return iter->second->moduleName;
126}
127
128panda_file::File::EntityId DebugInfoStorage::FindClass(std::string_view filePath, std::string_view className)
129{
130    auto iter = sourceFileToDebugInfo_.find(filePath);
131    if (iter == sourceFileToDebugInfo_.end()) {
132        return panda_file::File::EntityId();
133    }
134
135    const auto &records = LazyLoadRecords(iter->second);
136
137    auto classIter = records.find(className);
138    return classIter == records.end() ? panda_file::File::EntityId() : classIter->second;
139}
140
141bool DebugInfoStorage::FillEvaluateContext(EvaluateContext &context)
142{
143    const auto *contextPandaFile = GetPandaFile(context.sourceFilePath.Utf8());
144    if (contextPandaFile == nullptr) {
145        LOG(WARNING, ES2PANDA) << "Could not find context file: " << context.sourceFilePath << std::endl;
146        return false;
147    }
148
149    context.file = contextPandaFile;
150    context.extractor = std::make_unique<panda_file::DebugInfoExtractor>(contextPandaFile);
151
152    for (auto methodId : context.extractor->GetMethodIdList()) {
153        for (const auto &entry : context.extractor->GetLineNumberTable(methodId)) {
154            if (context.lineNumber == entry.line) {
155                context.methodId = methodId;
156                context.bytecodeOffset = entry.offset;
157                util::UString sourceFilePath(std::string_view(context.extractor->GetSourceFile(methodId)), allocator_);
158                context.sourceFilePath = sourceFilePath.View();
159                return true;
160            }
161        }
162    }
163    LOG(WARNING, ES2PANDA) << "Could not find code at line: " << context.lineNumber << std::endl;
164    return false;
165}
166
167const ImportExportTable &DebugInfoStorage::LazyLoadImportExportTable(FileDebugInfo *info)
168{
169    ASSERT(info);
170
171    if (info->importExportTable.has_value()) {
172        return *info->importExportTable;
173    }
174
175    // NOTE: load table after it is implemented in compiler.
176    info->importExportTable.emplace(allocator_);
177    return info->importExportTable.value();
178}
179
180const FileDebugInfo::RecordsMap &DebugInfoStorage::LazyLoadRecords(FileDebugInfo *info)
181{
182    ASSERT(info);
183
184    if (info->records.has_value()) {
185        return *info->records;
186    }
187
188    info->records.emplace(allocator_->Adapter());
189    auto &records = *info->records;
190
191    const auto *pf = info->pf.get();
192    for (auto id : pf->GetClasses()) {
193        panda_file::File::EntityId classId(id);
194        if (pf->IsExternal(classId)) {
195            // Сlass that marked in currect .abc file as <external> should be define in some other .abc file.
196            // Thus we will not lose information about this class.
197            continue;
198        }
199
200        auto recordName = helpers::SplitRecordName(GetFullRecordName(*pf, classId)).second;
201        auto recordNameView = util::UString(recordName, allocator_).View();
202        if (!records.emplace(recordNameView, classId).second) {
203            LOG(FATAL, ES2PANDA) << "Failed to emplace class '" << recordNameView << "' in records."
204                                 << "There should be only one declaration of the same class.";
205        }
206    }
207
208    return records;
209}
210
211FileDebugInfo *DebugInfoStorage::GetDebugInfoByModuleName(std::string_view moduleName) const
212{
213    auto find = moduleNameToDebugInfo_.find(moduleName);
214    if (find != moduleNameToDebugInfo_.end()) {
215        return find->second;
216    }
217    return nullptr;
218}
219
220}  // namespace ark::es2panda::evaluate
221