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