• 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/global_env_constants-inl.h"
18 #include "ecmascript/jspandafile/js_pandafile_manager.h"
19 #include "ecmascript/js_tagged_value-inl.h"
20 #include "ecmascript/module/module_path_helper.h"
21 
22 namespace panda::ecmascript {
~QuickFixManager()23 QuickFixManager::~QuickFixManager()
24 {
25     methodInfos_.clear();
26 }
27 
RegisterQuickFixQueryFunc(const std::function<bool (std::string baseFileName,std::string & patchFileName,uint8_t ** patchBuffer,size_t & patchSize)> callBack)28 void QuickFixManager::RegisterQuickFixQueryFunc(const std::function<bool(std::string baseFileName,
29                         std::string &patchFileName,
30                         uint8_t **patchBuffer,
31                         size_t &patchSize)> callBack)
32 {
33     callBack_ = callBack;
34 }
35 
LoadPatchIfNeeded(JSThread * thread,const JSPandaFile * baseFile)36 void QuickFixManager::LoadPatchIfNeeded(JSThread *thread, const JSPandaFile *baseFile)
37 {
38     // callback and load patch.
39     if (!HasQueryQuickFixInfoFunc()) {
40         return;
41     }
42 
43     std::string patchFileName;
44     uint8_t *patchBuffer = nullptr;
45     size_t patchSize = 0;
46     CString baseFileName = baseFile->GetJSPandaFileDesc();
47     if (checkedFiles_.find(baseFileName) != checkedFiles_.end()) {
48         LOG_ECMA(DEBUG) << "Do not need check " << baseFileName << " has patch again";
49         return;
50     }
51     checkedFiles_.insert(baseFileName);
52 
53     bool needLoadPatch = callBack_(baseFileName.c_str(), patchFileName, &patchBuffer, patchSize);
54     if (!needLoadPatch) {
55         LOG_ECMA(INFO) << "Do not need load patch of: " << baseFileName;
56         return;
57     }
58 
59     std::shared_ptr<JSPandaFile> patchFile = JSPandaFileManager::GetInstance()->LoadJSPandaFileSecure(
60         thread, patchFileName.c_str(), "", patchBuffer, patchSize);
61     if (patchFile == nullptr) {
62         LOG_ECMA(ERROR) << "load patch jsPandafile failed of: " << baseFileName;
63         return;
64     }
65 
66     PatchInfo patchInfo;
67     patchAndBaseFileNameMap_[patchFileName.c_str()] = baseFileName;
68     if (baseClassInfo_.empty()) {
69         baseClassInfo_ = PatchLoader::CollectClassInfo(baseFile);
70     }
71     auto ret = PatchLoader::LoadPatchInternal(thread, baseFile, patchFile.get(), patchInfo, baseClassInfo_);
72     if (ret != PatchErrorCode::SUCCESS) {
73         LOG_ECMA(ERROR) << "Load patch fail of: " << baseFileName;
74         return;
75     }
76     thread->GetEcmaVM()->SetStageOfColdReload(StageOfColdReload::IS_COLD_RELOAD);
77     methodInfos_.emplace(baseFileName, patchInfo);
78 }
79 
LoadPatch(JSThread * thread,const std::string & patchFileName,const std::string & baseFileName)80 PatchErrorCode QuickFixManager::LoadPatch(JSThread *thread, const std::string &patchFileName,
81                                           const std::string &baseFileName)
82 {
83     LOG_ECMA(INFO) << "Load patch, patch: " << patchFileName << ", base:" << baseFileName;
84     if (methodInfos_.find(baseFileName.c_str()) != methodInfos_.end()) {
85         LOG_ECMA(ERROR) << "Cannot repeat load patch!";
86         return PatchErrorCode::PATCH_HAS_LOADED;
87     }
88 
89     std::shared_ptr<JSPandaFile> baseFile = JSPandaFileManager::GetInstance()->LoadJSPandaFile(
90         thread, baseFileName.c_str(), "", false, ExecuteTypes::STATIC);
91     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, PatchErrorCode::FILE_NOT_FOUND);
92     if (baseFile == nullptr) {
93         LOG_ECMA(ERROR) << "find base jsPandafile failed";
94         return PatchErrorCode::FILE_NOT_FOUND;
95     }
96 
97     // The entry point is not work for merge abc.
98     std::shared_ptr<JSPandaFile> patchFile = JSPandaFileManager::GetInstance()->LoadJSPandaFile(
99         thread, patchFileName.c_str(), "", false, ExecuteTypes::STATIC);
100     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, PatchErrorCode::FILE_NOT_FOUND);
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 
LoadPatch(JSThread * thread,const std::string & patchFileName,uint8_t * patchBuffer,size_t patchSize,const std::string & baseFileName,uint8_t * baseBuffer,size_t baseSize)122 PatchErrorCode 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 
UnloadPatch(JSThread * thread,const std::string & patchFileName)162 PatchErrorCode 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 
CheckAndGetPatch(JSThread * thread,const JSPandaFile * baseFile,EntityId baseMethodId)189 JSTaggedValue 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 = vm->FindOrCreateConstPool(
219         patchFile.get(), patchMethodLiteral->GetMethodId());
220     method->SetConstantPool(thread, newConstpool);
221 
222     CString recordName = MethodLiteral::GetRecordName(baseFile, baseMethodId);
223     JSHandle<JSTaggedValue> moduleRecord = vm->FindPatchModule(recordName);
224     if (moduleRecord->IsHole()) {
225         PatchLoader::ExecuteFuncOrPatchMain(thread, patchFile.get(), patchInfo);
226         moduleRecord = vm->FindPatchModule(recordName);
227         if (moduleRecord->IsHole()) {
228             LOG_ECMA(FATAL) << "cold patch: moduleRecord is still hole after regeneration";
229             UNREACHABLE();
230         }
231     }
232     return method.GetTaggedValue();
233 }
234 
IsQuickFixCausedException(JSThread * thread,const JSHandle<JSTaggedValue> & exceptionInfo,const std::string & patchFileName)235 bool QuickFixManager::IsQuickFixCausedException(JSThread *thread,
236                                                 const JSHandle<JSTaggedValue> &exceptionInfo,
237                                                 const std::string &patchFileName)
238 {
239     JSPandaFileManager *pfManager = JSPandaFileManager::GetInstance();
240     std::shared_ptr<JSPandaFile> patchFile = pfManager->FindJSPandaFile(ConvertToString(patchFileName));
241     if (patchFile == nullptr || ConvertToString(patchFileName) != patchFile->GetJSPandaFileDesc()) {
242         return false;
243     }
244 
245     // get and parse stackinfo.
246     JSHandle<JSTaggedValue> stackKey = thread->GlobalConstants()->GetHandledStackString();
247     JSHandle<EcmaString> stack(JSObject::GetProperty(thread, exceptionInfo, stackKey).GetValue());
248     CString stackInfo = ConvertToString(thread, *stack);
249     CUnorderedSet<CString> methodNames = ParseStackInfo(stackInfo);
250 
251     // check whether the methodNames contains a patch method name.
252     std::unordered_map<uint32_t, MethodLiteral *> patchMethodLiterals = patchFile->GetMethodLiteralMap();
253     for (const auto &item : patchMethodLiterals) {
254         MethodLiteral *patch = item.second;
255         auto methodId = patch->GetMethodId();
256         CString patchMethodName(MethodLiteral::GetMethodName(patchFile.get(), methodId));
257         size_t index = patchMethodName.find_last_of('#');          // #...#functionName
258         patchMethodName = patchMethodName.substr(index + 1);
259         if (patchMethodName.find('^') != std::string::npos) {
260             index = patchMethodName.find_last_of('^');
261             patchMethodName = patchMethodName.substr(0, index);    // #...#functionName^1
262         }
263 
264         if (std::strcmp(patchMethodName.data(), JSPandaFile::ENTRY_FUNCTION_NAME) != 0 &&
265             methodNames.find(CString(patchMethodName)) != methodNames.end()) {
266             return true;
267         }
268     }
269     return false;
270 }
271 
ParseStackInfo(const CString & stackInfo)272 CUnorderedSet<CString> QuickFixManager::ParseStackInfo(const CString &stackInfo)
273 {
274     const uint32_t methodNameOffsetToFirstIndex = 5; // offset of the starting position of methodname to firstIndex.
275     size_t lineIndex = 0; // index of "\n".
276     size_t firstIndex = 0; // index of "at".
277     size_t nextIndex = 0; // index of "(".
278 
279     CUnorderedSet<CString> methodNames {}; // method names are from exception stack information.
280     while (lineIndex != stackInfo.length() - 1) {
281         firstIndex = stackInfo.find("  at ", lineIndex + 1);
282         nextIndex = stackInfo.find("(", lineIndex + 1);
283         CString methodName = stackInfo.substr(firstIndex + methodNameOffsetToFirstIndex,
284             nextIndex - firstIndex - methodNameOffsetToFirstIndex - 1);
285         methodNames.emplace(methodName);
286         lineIndex = stackInfo.find("\n", lineIndex + 1);
287     }
288     return methodNames;
289 }
290 
GetBaseFileName(const JSHandle<SourceTextModule> & module)291 CString QuickFixManager::GetBaseFileName(const JSHandle<SourceTextModule> &module)
292 {
293     CString fileName = module->GetEcmaModuleFilenameString();
294     // Return the baseFileName of the patch module
295     if (fileName.find(ModulePathHelper::EXT_NAME_HQF) != std::string::npos) {
296         auto it = patchAndBaseFileNameMap_.find(fileName);
297         if (it != patchAndBaseFileNameMap_.end()) {
298             return it->second;
299         } else {
300             LOG_ECMA(ERROR) << "The baseFileName corresponding to " << fileName << " cannot be found.";
301         }
302     }
303     return fileName;
304 }
305 }  // namespace panda::ecmascript