1/*
2 * Copyright (c) 2022-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#include "ecmascript/patch/quick_fix_manager.h"
16
17#include "ecmascript/ecma_context.h"
18#include "ecmascript/global_env_constants-inl.h"
19#include "ecmascript/jspandafile/js_pandafile_manager.h"
20#include "ecmascript/js_tagged_value-inl.h"
21#include "ecmascript/object_factory.h"
22#include "ecmascript/module/module_path_helper.h"
23
24namespace panda::ecmascript {
25QuickFixManager::~QuickFixManager()
26{
27    methodInfos_.clear();
28}
29
30void QuickFixManager::RegisterQuickFixQueryFunc(const std::function<bool(std::string baseFileName,
31                        std::string &patchFileName,
32                        uint8_t **patchBuffer,
33                        size_t &patchSize)> callBack)
34{
35    callBack_ = callBack;
36}
37
38void QuickFixManager::LoadPatchIfNeeded(JSThread *thread, const JSPandaFile *baseFile)
39{
40    // callback and load patch.
41    if (!HasQueryQuickFixInfoFunc()) {
42        return;
43    }
44
45    std::string patchFileName;
46    uint8_t *patchBuffer = nullptr;
47    size_t patchSize = 0;
48    CString baseFileName = baseFile->GetJSPandaFileDesc();
49    if (checkedFiles_.find(baseFileName) != checkedFiles_.end()) {
50        LOG_ECMA(DEBUG) << "Do not need check " << baseFileName << " has patch again";
51        return;
52    }
53    checkedFiles_.insert(baseFileName);
54
55    bool needLoadPatch = callBack_(baseFileName.c_str(), patchFileName, &patchBuffer, patchSize);
56    if (!needLoadPatch) {
57        LOG_ECMA(INFO) << "Do not need load patch of: " << baseFileName;
58        return;
59    }
60
61    std::shared_ptr<JSPandaFile> patchFile = JSPandaFileManager::GetInstance()->LoadJSPandaFileSecure(
62        thread, patchFileName.c_str(), "", patchBuffer, patchSize);
63    if (patchFile == nullptr) {
64        LOG_ECMA(ERROR) << "load patch jsPandafile failed of: " << baseFileName;
65        return;
66    }
67
68    PatchInfo patchInfo;
69    patchAndBaseFileNameMap_[patchFileName.c_str()] = baseFileName;
70    if (baseClassInfo_.empty()) {
71        baseClassInfo_ = PatchLoader::CollectClassInfo(baseFile);
72    }
73    auto ret = PatchLoader::LoadPatchInternal(thread, baseFile, patchFile.get(), patchInfo, baseClassInfo_);
74    if (ret != PatchErrorCode::SUCCESS) {
75        LOG_ECMA(ERROR) << "Load patch fail of: " << baseFileName;
76        return;
77    }
78    thread->GetCurrentEcmaContext()->SetStageOfColdReload(StageOfColdReload::IS_COLD_RELOAD);
79    methodInfos_.emplace(baseFileName, patchInfo);
80}
81
82PatchErrorCode QuickFixManager::LoadPatch(JSThread *thread, const std::string &patchFileName,
83                                          const std::string &baseFileName)
84{
85    LOG_ECMA(INFO) << "Load patch, patch: " << patchFileName << ", base:" << baseFileName;
86    if (methodInfos_.find(baseFileName.c_str()) != methodInfos_.end()) {
87        LOG_ECMA(ERROR) << "Cannot repeat load patch!";
88        return PatchErrorCode::PATCH_HAS_LOADED;
89    }
90
91    std::shared_ptr<JSPandaFile> baseFile =
92        JSPandaFileManager::GetInstance()->LoadJSPandaFile(thread, baseFileName.c_str(), "");
93    if (baseFile == nullptr) {
94        LOG_ECMA(ERROR) << "find base jsPandafile failed";
95        return PatchErrorCode::FILE_NOT_FOUND;
96    }
97
98    // The entry point is not work for merge abc.
99    std::shared_ptr<JSPandaFile> patchFile =
100        JSPandaFileManager::GetInstance()->LoadJSPandaFile(thread, patchFileName.c_str(), "");
101    if (patchFile == nullptr) {
102        LOG_ECMA(ERROR) << "load patch jsPandafile failed";
103        return PatchErrorCode::FILE_NOT_FOUND;
104    }
105
106    PatchInfo patchInfo;
107    patchAndBaseFileNameMap_[patchFile->GetJSPandaFileDesc()] = baseFileName.c_str();
108    if (baseClassInfo_.empty()) {
109        baseClassInfo_ = PatchLoader::CollectClassInfo(baseFile.get());
110    }
111    auto ret = PatchLoader::LoadPatchInternal(thread, baseFile.get(), patchFile.get(), patchInfo, baseClassInfo_);
112    if (ret != PatchErrorCode::SUCCESS) {
113        LOG_ECMA(ERROR) << "Load patch fail!";
114        return ret;
115    }
116
117    methodInfos_.emplace(baseFileName.c_str(), patchInfo);
118    LOG_ECMA(INFO) << "Load patch success!";
119    return PatchErrorCode::SUCCESS;
120}
121
122PatchErrorCode QuickFixManager::LoadPatch(JSThread *thread,
123                                          const std::string &patchFileName, uint8_t *patchBuffer, size_t patchSize,
124                                          const std::string &baseFileName, uint8_t *baseBuffer, size_t baseSize)
125{
126    LOG_ECMA(INFO) << "Load patch, patch: " << patchFileName << ", base:" << baseFileName;
127    if (methodInfos_.find(baseFileName.c_str()) != methodInfos_.end()) {
128        LOG_ECMA(ERROR) << "Cannot repeat load patch!";
129        return PatchErrorCode::PATCH_HAS_LOADED;
130    }
131
132    std::shared_ptr<JSPandaFile> baseFile = JSPandaFileManager::GetInstance()->LoadJSPandaFileSecure(
133        thread, baseFileName.c_str(), "", baseBuffer, baseSize);
134    if (baseFile == nullptr) {
135        LOG_ECMA(ERROR) << "find base jsPandafile failed";
136        return PatchErrorCode::FILE_NOT_FOUND;
137    }
138
139    std::shared_ptr<JSPandaFile> patchFile = JSPandaFileManager::GetInstance()->LoadJSPandaFileSecure(
140        thread, patchFileName.c_str(), "", patchBuffer, patchSize);
141    if (patchFile == nullptr) {
142        LOG_ECMA(ERROR) << "load patch jsPandafile failed";
143        return PatchErrorCode::FILE_NOT_FOUND;
144    }
145
146    PatchInfo patchInfo;
147    patchAndBaseFileNameMap_[patchFile->GetJSPandaFileDesc()] = baseFileName.c_str();
148    if (baseClassInfo_.empty()) {
149        baseClassInfo_ = PatchLoader::CollectClassInfo(baseFile.get());
150    }
151    auto ret = PatchLoader::LoadPatchInternal(thread, baseFile.get(), patchFile.get(), patchInfo, baseClassInfo_);
152    if (ret != PatchErrorCode::SUCCESS) {
153        LOG_ECMA(ERROR) << "Load patch fail!";
154        return ret;
155    }
156
157    methodInfos_.emplace(baseFileName.c_str(), patchInfo);
158    LOG_ECMA(INFO) << "Load patch success!";
159    return PatchErrorCode::SUCCESS;
160}
161
162PatchErrorCode QuickFixManager::UnloadPatch(JSThread *thread, const std::string &patchFileName)
163{
164    LOG_ECMA(INFO) << "Unload patch, patch: " << patchFileName;
165    CString baseFileName;
166    for (const auto &item : methodInfos_) {
167        if (item.second.patchFileName == patchFileName.c_str()) {
168            baseFileName = item.first;
169        }
170    }
171    if (baseFileName.empty()) {
172        LOG_ECMA(ERROR) << "patch has not been loaded!";
173        return PatchErrorCode::PATCH_NOT_LOADED;
174    }
175
176    PatchInfo &patchInfo = methodInfos_.find(baseFileName)->second;
177    patchAndBaseFileNameMap_.erase(patchFileName.c_str());
178    auto ret = PatchLoader::UnloadPatchInternal(thread, patchFileName.c_str(), baseFileName.c_str(), patchInfo);
179    if (ret != PatchErrorCode::SUCCESS) {
180        LOG_ECMA(ERROR) << "Unload patch fail!";
181        return ret;
182    }
183
184    methodInfos_.erase(baseFileName.c_str());
185    LOG_ECMA(INFO) << "Unload patch success!";
186    return PatchErrorCode::SUCCESS;
187}
188
189JSTaggedValue QuickFixManager::CheckAndGetPatch(JSThread *thread, const JSPandaFile *baseFile, EntityId baseMethodId)
190{
191    if (methodInfos_.empty()) {
192        return JSTaggedValue::Hole();
193    }
194
195    auto iter = methodInfos_.find(baseFile->GetJSPandaFileDesc());
196    if (iter == methodInfos_.end()) {
197        return JSTaggedValue::Hole();
198    }
199
200    PatchInfo patchInfo = iter->second;
201    MethodLiteral *patchMethodLiteral = PatchLoader::FindSameMethod(patchInfo, baseFile, baseMethodId, baseClassInfo_);
202    if (patchMethodLiteral == nullptr) {
203        return JSTaggedValue::Hole();
204    }
205
206    if (!HasQueryQuickFixInfoFunc()) {
207        return JSTaggedValue::Hole();
208    }
209
210    // Generate patch constpool.
211    CString patchFileName = patchInfo.patchFileName;
212    std::shared_ptr<JSPandaFile> patchFile = JSPandaFileManager::GetInstance()->FindJSPandaFile(patchFileName);
213    ASSERT(patchFile != nullptr);
214
215    EcmaVM *vm = thread->GetEcmaVM();
216    JSHandle<Method> method;
217    method = vm->GetFactory()->NewSMethod(patchMethodLiteral);
218    JSHandle<ConstantPool> newConstpool = thread->GetCurrentEcmaContext()->FindOrCreateConstPool(
219        patchFile.get(), patchMethodLiteral->GetMethodId());
220    method->SetConstantPool(thread, newConstpool);
221
222    CString recordName = MethodLiteral::GetRecordName(baseFile, baseMethodId);
223    EcmaContext *context = thread->GetCurrentEcmaContext();
224    JSHandle<JSTaggedValue> moduleRecord = context->FindPatchModule(recordName);
225    if (moduleRecord->IsHole()) {
226        PatchLoader::ExecuteFuncOrPatchMain(thread, patchFile.get(), patchInfo);
227        moduleRecord = context->FindPatchModule(recordName);
228        if (moduleRecord->IsHole()) {
229            LOG_ECMA(FATAL) << "cold patch: moduleRecord is still hole after regeneration";
230            UNREACHABLE();
231        }
232    }
233    return method.GetTaggedValue();
234}
235
236bool QuickFixManager::IsQuickFixCausedException(JSThread *thread,
237                                                const JSHandle<JSTaggedValue> &exceptionInfo,
238                                                const std::string &patchFileName)
239{
240    JSPandaFileManager *pfManager = JSPandaFileManager::GetInstance();
241    std::shared_ptr<JSPandaFile> patchFile = pfManager->FindJSPandaFile(ConvertToString(patchFileName));
242    if (patchFile == nullptr || ConvertToString(patchFileName) != patchFile->GetJSPandaFileDesc()) {
243        return false;
244    }
245
246    // get and parse stackinfo.
247    JSHandle<JSTaggedValue> stackKey = thread->GlobalConstants()->GetHandledStackString();
248    JSHandle<EcmaString> stack(JSObject::GetProperty(thread, exceptionInfo, stackKey).GetValue());
249    CString stackInfo = ConvertToString(*stack);
250    CUnorderedSet<CString> methodNames = ParseStackInfo(stackInfo);
251
252    // check whether the methodNames contains a patch method name.
253    CUnorderedMap<uint32_t, MethodLiteral *> patchMethodLiterals = patchFile->GetMethodLiteralMap();
254    for (const auto &item : patchMethodLiterals) {
255        MethodLiteral *patch = item.second;
256        auto methodId = patch->GetMethodId();
257        CString patchMethodName(MethodLiteral::GetMethodName(patchFile.get(), methodId));
258        size_t index = patchMethodName.find_last_of('#');          // #...#functionName
259        patchMethodName = patchMethodName.substr(index + 1);
260        if (patchMethodName.find('^') != std::string::npos) {
261            index = patchMethodName.find_last_of('^');
262            patchMethodName = patchMethodName.substr(0, index);    // #...#functionName^1
263        }
264
265        if (std::strcmp(patchMethodName.data(), JSPandaFile::ENTRY_FUNCTION_NAME) != 0 &&
266            methodNames.find(CString(patchMethodName)) != methodNames.end()) {
267            return true;
268        }
269    }
270    return false;
271}
272
273CUnorderedSet<CString> QuickFixManager::ParseStackInfo(const CString &stackInfo)
274{
275    const uint32_t methodNameOffsetToFirstIndex = 5; // offset of the starting position of methodname to firstIndex.
276    size_t lineIndex = 0; // index of "\n".
277    size_t firstIndex = 0; // index of "at".
278    size_t nextIndex = 0; // index of "(".
279
280    CUnorderedSet<CString> methodNames {}; // method names are from exception stack information.
281    while (lineIndex != stackInfo.length() - 1) {
282        firstIndex = stackInfo.find("  at ", lineIndex + 1);
283        nextIndex = stackInfo.find("(", lineIndex + 1);
284        CString methodName = stackInfo.substr(firstIndex + methodNameOffsetToFirstIndex,
285            nextIndex - firstIndex - methodNameOffsetToFirstIndex - 1);
286        methodNames.emplace(methodName);
287        lineIndex = stackInfo.find("\n", lineIndex + 1);
288    }
289    return methodNames;
290}
291
292CString QuickFixManager::GetBaseFileName(const JSHandle<SourceTextModule> &module)
293{
294    CString fileName = module->GetEcmaModuleFilenameString();
295    // Return the baseFileName of the patch module
296    if (fileName.find(ModulePathHelper::EXT_NAME_HQF) != std::string::npos) {
297        auto it = patchAndBaseFileNameMap_.find(fileName);
298        if (it != patchAndBaseFileNameMap_.end()) {
299            return it->second;
300        } else {
301            LOG_ECMA(ERROR) << "The baseFileName corresponding to " << fileName << " cannot be found.";
302        }
303    }
304    return fileName;
305}
306}  // namespace panda::ecmascript