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