• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2022 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/mem/barriers-inl.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/mem/c_string.h"
22 #include "ecmascript/napi/include/jsnapi.h"
23 #include "ecmascript/object_factory.h"
24 
25 namespace panda::ecmascript {
~QuickFixManager()26 QuickFixManager::~QuickFixManager()
27 {
28     methodInfos_.clear();
29 }
30 
RegisterQuickFixQueryFunc(const std::function<bool (std::string baseFileName,std::string & patchFileName,void ** patchBuffer,size_t & patchSize)> callBack)31 void QuickFixManager::RegisterQuickFixQueryFunc(const std::function<bool(std::string baseFileName,
32                         std::string &patchFileName,
33                         void **patchBuffer,
34                         size_t &patchSize)> callBack)
35 {
36     callBack_ = callBack;
37 }
38 
LoadPatchIfNeeded(JSThread * thread,const JSPandaFile * baseFile)39 void QuickFixManager::LoadPatchIfNeeded(JSThread *thread, const JSPandaFile *baseFile)
40 {
41     // callback and load patch.
42     if (!HasQueryQuickFixInfoFunc()) {
43         return;
44     }
45 
46     std::string patchFileName;
47     void *patchBuffer = nullptr;
48     size_t patchSize = 0;
49     CString baseFileName = baseFile->GetJSPandaFileDesc();
50     bool needLoadPatch = callBack_(baseFileName.c_str(), patchFileName, &patchBuffer, patchSize);
51     if (!needLoadPatch) {
52         LOG_ECMA(INFO) << "Do not need load patch of: " << baseFileName;
53         return;
54     }
55 
56     if (methodInfos_.find(baseFileName) != methodInfos_.end()) {
57         LOG_ECMA(DEBUG) << "Cannot repeat load patch of: " << baseFileName;
58         return;
59     }
60 
61     std::shared_ptr<JSPandaFile> patchFile = JSPandaFileManager::GetInstance()->LoadJSPandaFile(
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     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 
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 =
91         JSPandaFileManager::GetInstance()->LoadJSPandaFile(thread, baseFileName.c_str(), "");
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 =
99         JSPandaFileManager::GetInstance()->LoadJSPandaFile(thread, patchFileName.c_str(), "");
100     if (patchFile == nullptr) {
101         LOG_ECMA(ERROR) << "load patch jsPandafile failed";
102         return PatchErrorCode::FILE_NOT_FOUND;
103     }
104 
105     PatchInfo patchInfo;
106     if (baseClassInfo_.empty()) {
107         baseClassInfo_ = PatchLoader::CollectClassInfo(baseFile.get());
108     }
109     auto ret = PatchLoader::LoadPatchInternal(thread, baseFile.get(), patchFile.get(), patchInfo, baseClassInfo_);
110     if (ret != PatchErrorCode::SUCCESS) {
111         LOG_ECMA(ERROR) << "Load patch fail!";
112         return ret;
113     }
114 
115     methodInfos_.emplace(baseFileName.c_str(), patchInfo);
116     LOG_ECMA(INFO) << "Load patch success!";
117     return PatchErrorCode::SUCCESS;
118 }
119 
LoadPatch(JSThread * thread,const std::string & patchFileName,const void * patchBuffer,size_t patchSize,const std::string & baseFileName,const void * baseBuffer,size_t baseSize)120 PatchErrorCode QuickFixManager::LoadPatch(JSThread *thread,
121                                           const std::string &patchFileName, const void *patchBuffer, size_t patchSize,
122                                           const std::string &baseFileName, const void *baseBuffer, size_t baseSize)
123 {
124     LOG_ECMA(INFO) << "Load patch, patch: " << patchFileName << ", base:" << baseFileName;
125     if (methodInfos_.find(baseFileName.c_str()) != methodInfos_.end()) {
126         LOG_ECMA(ERROR) << "Cannot repeat load patch!";
127         return PatchErrorCode::PATCH_HAS_LOADED;
128     }
129 
130     std::shared_ptr<JSPandaFile> baseFile = JSPandaFileManager::GetInstance()->LoadJSPandaFile(
131         thread, baseFileName.c_str(), "", baseBuffer, baseSize);
132     if (baseFile == nullptr) {
133         LOG_ECMA(ERROR) << "find base jsPandafile failed";
134         return PatchErrorCode::FILE_NOT_FOUND;
135     }
136 
137     std::shared_ptr<JSPandaFile> patchFile = JSPandaFileManager::GetInstance()->LoadJSPandaFile(
138         thread, patchFileName.c_str(), "", patchBuffer, patchSize);
139     if (patchFile == nullptr) {
140         LOG_ECMA(ERROR) << "load patch jsPandafile failed";
141         return PatchErrorCode::FILE_NOT_FOUND;
142     }
143 
144     PatchInfo patchInfo;
145     if (baseClassInfo_.empty()) {
146         baseClassInfo_ = PatchLoader::CollectClassInfo(baseFile.get());
147     }
148     auto ret = PatchLoader::LoadPatchInternal(thread, baseFile.get(), patchFile.get(), patchInfo, baseClassInfo_);
149     if (ret != PatchErrorCode::SUCCESS) {
150         LOG_ECMA(ERROR) << "Load patch fail!";
151         return ret;
152     }
153 
154     methodInfos_.emplace(baseFileName.c_str(), patchInfo);
155     LOG_ECMA(INFO) << "Load patch success!";
156     return PatchErrorCode::SUCCESS;
157 }
158 
UnloadPatch(JSThread * thread,const std::string & patchFileName)159 PatchErrorCode QuickFixManager::UnloadPatch(JSThread *thread, const std::string &patchFileName)
160 {
161     LOG_ECMA(INFO) << "Unload patch, patch: " << patchFileName;
162     CString baseFileName;
163     for (const auto &item : methodInfos_) {
164         if (item.second.patchFileName == patchFileName.c_str()) {
165             baseFileName = item.first;
166         }
167     }
168     if (baseFileName.empty()) {
169         LOG_ECMA(ERROR) << "patch has not been loaded!";
170         return PatchErrorCode::PATCH_NOT_LOADED;
171     }
172 
173     PatchInfo &patchInfo = methodInfos_.find(baseFileName)->second;
174     auto ret = PatchLoader::UnloadPatchInternal(thread, patchFileName.c_str(), baseFileName.c_str(), patchInfo);
175     if (ret != PatchErrorCode::SUCCESS) {
176         LOG_ECMA(ERROR) << "Unload patch fail!";
177         return ret;
178     }
179 
180     methodInfos_.erase(baseFileName.c_str());
181     LOG_ECMA(INFO) << "Unload patch success!";
182     return PatchErrorCode::SUCCESS;
183 }
184 
CheckAndGetPatch(JSThread * thread,const JSPandaFile * baseFile,EntityId baseMethodId)185 JSTaggedValue QuickFixManager::CheckAndGetPatch(JSThread *thread, const JSPandaFile *baseFile, EntityId baseMethodId)
186 {
187     if (methodInfos_.empty()) {
188         return JSTaggedValue::Hole();
189     }
190 
191     auto iter = methodInfos_.find(baseFile->GetJSPandaFileDesc());
192     if (iter == methodInfos_.end()) {
193         return JSTaggedValue::Hole();
194     }
195 
196     PatchInfo patchInfo = iter->second;
197     MethodLiteral *patchMethodLiteral = PatchLoader::FindSameMethod(patchInfo, baseFile, baseMethodId, baseClassInfo_);
198     if (patchMethodLiteral == nullptr) {
199         return JSTaggedValue::Hole();
200     }
201 
202     // Generate patch constpool.
203     CString patchFileName = patchInfo.patchFileName;
204     std::shared_ptr<JSPandaFile> patchFile = JSPandaFileManager::GetInstance()->FindJSPandaFile(patchFileName);
205     ASSERT(patchFile != nullptr);
206 
207     EcmaVM *vm = thread->GetEcmaVM();
208     JSHandle<Method> method = vm->GetFactory()->NewMethod(patchMethodLiteral);
209     JSHandle<ConstantPool> newConstpool = thread->GetCurrentEcmaContext()->FindOrCreateConstPool(
210         patchFile.get(), patchMethodLiteral->GetMethodId());
211     method->SetConstantPool(thread, newConstpool);
212 
213     CString recordName = MethodLiteral::GetRecordName(baseFile, baseMethodId);
214     EcmaContext *context = thread->GetCurrentEcmaContext();
215     JSHandle<JSTaggedValue> moduleRecord = context->FindPatchModule(recordName);
216     if (moduleRecord->IsHole()) {
217         PatchLoader::ExecuteFuncOrPatchMain(thread, patchFile.get(), patchInfo);
218         moduleRecord = context->FindPatchModule(recordName);
219         if (moduleRecord->IsHole()) {
220             LOG_ECMA(ERROR) << "cold patch: moduleRecord is still hole after regeneration";
221             method->SetModule(thread, JSTaggedValue::Undefined());
222             return method.GetTaggedValue();
223         }
224     }
225     method->SetModule(thread, moduleRecord.GetTaggedValue());
226     return method.GetTaggedValue();
227 }
228 
IsQuickFixCausedException(JSThread * thread,const JSHandle<JSTaggedValue> & exceptionInfo,const std::string & patchFileName)229 bool QuickFixManager::IsQuickFixCausedException(JSThread *thread,
230                                                 const JSHandle<JSTaggedValue> &exceptionInfo,
231                                                 const std::string &patchFileName)
232 {
233     JSPandaFileManager *pfManager = JSPandaFileManager::GetInstance();
234     std::shared_ptr<JSPandaFile> patchFile = pfManager->FindJSPandaFile(ConvertToString(patchFileName));
235     if (patchFile == nullptr || ConvertToString(patchFileName) != patchFile->GetJSPandaFileDesc()) {
236         return false;
237     }
238 
239     // get and parse stackinfo.
240     JSHandle<JSTaggedValue> stackKey = thread->GlobalConstants()->GetHandledStackString();
241     JSHandle<EcmaString> stack(JSObject::GetProperty(thread, exceptionInfo, stackKey).GetValue());
242     CString stackInfo = ConvertToString(*stack);
243     CUnorderedSet<CString> methodNames = ParseStackInfo(stackInfo);
244 
245     // check whether the methodNames contains a patch method name.
246     CUnorderedMap<uint32_t, MethodLiteral *> patchMethodLiterals = patchFile->GetMethodLiteralMap();
247     for (const auto &item : patchMethodLiterals) {
248         MethodLiteral *patch = item.second;
249         auto methodId = patch->GetMethodId();
250         const char *patchMethodName = MethodLiteral::GetMethodName(patchFile.get(), methodId);
251         if (std::strcmp(patchMethodName, JSPandaFile::ENTRY_FUNCTION_NAME) != 0 &&
252             methodNames.find(CString(patchMethodName)) != methodNames.end()) {
253             return true;
254         }
255     }
256     return false;
257 }
258 
ParseStackInfo(const CString & stackInfo)259 CUnorderedSet<CString> QuickFixManager::ParseStackInfo(const CString &stackInfo)
260 {
261     const uint32_t methodNameOffsetToFirstIndex = 5; // offset of the starting position of methodname to firstIndex.
262     size_t lineIndex = 0; // index of "\n".
263     size_t firstIndex = 0; // index of "at".
264     size_t nextIndex = 0; // index of "(".
265 
266     CUnorderedSet<CString> methodNames {}; // method names are from exception stack information.
267     while (lineIndex != stackInfo.length() - 1) {
268         firstIndex = stackInfo.find("  at ", lineIndex + 1);
269         nextIndex = stackInfo.find("(", lineIndex + 1);
270         CString methodName = stackInfo.substr(firstIndex + methodNameOffsetToFirstIndex,
271             nextIndex - firstIndex - methodNameOffsetToFirstIndex - 1);
272         methodNames.emplace(methodName);
273         lineIndex = stackInfo.find("\n", lineIndex + 1);
274     }
275     return methodNames;
276 }
277 }  // namespace panda::ecmascript