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