• 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/patch_loader.h"
16 
17 #include "ecmascript/global_handle_collection.h"
18 #include "ecmascript/interpreter/interpreter-inl.h"
19 #include "ecmascript/jspandafile/js_pandafile_manager.h"
20 #include "ecmascript/mem/c_string.h"
21 #include "ecmascript/napi/include/jsnapi.h"
22 
23 namespace panda::ecmascript {
LoadPatchInternal(JSThread * thread,const JSPandaFile * baseFile,const JSPandaFile * patchFile,PatchInfo & patchInfo)24 PatchErrorCode PatchLoader::LoadPatchInternal(JSThread *thread, const JSPandaFile *baseFile,
25                                               const JSPandaFile *patchFile, PatchInfo &patchInfo)
26 {
27     DISALLOW_GARBAGE_COLLECTION;
28     EcmaVM *vm = thread->GetEcmaVM();
29 
30     // hot reload and hot patch only support merge-abc file.
31     if (baseFile->IsBundlePack() || patchFile->IsBundlePack()) {
32         LOG_ECMA(ERROR) << "base or patch is not merge abc!";
33         return PatchErrorCode::PACKAGE_NOT_ESMODULE;
34     }
35 
36     // Generate patchInfo for hot reload, hot patch and cold patch.
37     patchInfo = PatchLoader::GeneratePatchInfo(patchFile);
38 
39     if (!thread->GetCurrentEcmaContext()->HasCachedConstpool(baseFile)) {
40         LOG_ECMA(INFO) << "cold patch!";
41         return PatchErrorCode::SUCCESS;
42     }
43 
44     [[maybe_unused]] EcmaHandleScope handleScope(thread);
45 
46     // store base constpool in global object for avoid gc.
47     uint32_t num = baseFile->GetConstpoolNum();
48     GlobalHandleCollection gloalHandleCollection(thread);
49     for (uint32_t idx = 0; idx < num; idx++) {
50         JSTaggedValue constpool = thread->GetCurrentEcmaContext()->FindConstpool(baseFile, idx);
51         if (!constpool.IsHole()) {
52             JSHandle<JSTaggedValue> constpoolHandle =
53                 gloalHandleCollection.NewHandle<JSTaggedValue>(constpool.GetRawData());
54             patchInfo.baseConstpools.emplace_back(constpoolHandle);
55         }
56     }
57 
58     // create empty patch constpool for replace method constpool.
59     thread->GetCurrentEcmaContext()->CreateAllConstpool(patchFile);
60     FindAndReplaceSameMethod(thread, baseFile, patchFile, patchInfo);
61 
62     if (!ExecutePatchMain(thread, patchFile, baseFile, patchInfo)) {
63         LOG_ECMA(ERROR) << "Execute patch main failed";
64         return PatchErrorCode::INTERNAL_ERROR;
65     }
66 
67     vm->GetJsDebuggerManager()->GetHotReloadManager()->NotifyPatchLoaded(baseFile, patchFile);
68     return PatchErrorCode::SUCCESS;
69 }
70 
ExecutePatchMain(JSThread * thread,const JSPandaFile * patchFile,const JSPandaFile * baseFile,PatchInfo & patchInfo)71 bool PatchLoader::ExecutePatchMain(JSThread *thread, const JSPandaFile *patchFile,
72                                    const JSPandaFile *baseFile, PatchInfo &patchInfo)
73 {
74     EcmaVM *vm = thread->GetEcmaVM();
75 
76     const auto &recordInfos = patchFile->GetJSRecordInfo();
77     bool isHotPatch = false;
78     bool isNewVersion = patchFile->IsNewVersion();
79     for (const auto &item : recordInfos) {
80         const CString &recordName = item.first;
81         uint32_t mainMethodIndex = patchFile->GetMainMethodIndex(recordName);
82         ASSERT(mainMethodIndex != 0);
83         EntityId mainMethodId(mainMethodIndex);
84 
85         // For HotPatch, generate program and execute for every record.
86         if (!isHotPatch) {
87             CString mainMethodName = MethodLiteral::GetMethodName(patchFile, mainMethodId);
88             if (mainMethodName != JSPandaFile::PATCH_FUNCTION_NAME_0) {
89                 LOG_ECMA(INFO) << "HotReload no need to execute patch main";
90                 return true;
91             }
92             isHotPatch = true;
93         }
94 
95         JSTaggedValue constpoolVal = JSTaggedValue::Hole();
96         if (isNewVersion) {
97             constpoolVal = thread->GetCurrentEcmaContext()->FindConstpool(patchFile, mainMethodId);
98         } else {
99             constpoolVal = thread->GetCurrentEcmaContext()->FindConstpool(patchFile, 0);
100         }
101         ASSERT(!constpoolVal.IsHole());
102         JSHandle<ConstantPool> constpool(thread, constpoolVal);
103         MethodLiteral *mainMethodLiteral = patchFile->FindMethodLiteral(mainMethodIndex);
104         JSHandle<Program> program = PandaFileTranslator::GenerateProgramInternal(vm, mainMethodLiteral, constpool);
105 
106         if (program->GetMainFunction().IsUndefined()) {
107             continue;
108         }
109 
110         // For add a new function, Call patch_main_0.
111         JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
112         JSHandle<JSFunction> func(thread, program->GetMainFunction());
113         JSHandle<JSTaggedValue> module =
114             thread->GetCurrentEcmaContext()->GetModuleManager()->HostResolveImportedModuleWithMerge(
115                 patchFile->GetJSPandaFileDesc(), recordName);
116         func->SetModule(thread, module);
117         EcmaRuntimeCallInfo *info =
118             EcmaInterpreter::NewRuntimeCallInfo(thread, JSHandle<JSTaggedValue>(func), undefined, undefined, 0);
119         EcmaInterpreter::Execute(info);
120 
121         if (thread->HasPendingException()) {
122             // clear exception and rollback.
123             thread->ClearException();
124             UnloadPatchInternal(thread, patchFile->GetJSPandaFileDesc(), baseFile->GetJSPandaFileDesc(), patchInfo);
125             LOG_ECMA(ERROR) << "execute patch main has exception";
126             return false;
127         }
128     }
129     return true;
130 }
131 
UnloadPatchInternal(JSThread * thread,const CString & patchFileName,const CString & baseFileName,PatchInfo & patchInfo)132 PatchErrorCode PatchLoader::UnloadPatchInternal(JSThread *thread, const CString &patchFileName,
133                                                 const CString &baseFileName, PatchInfo &patchInfo)
134 {
135     std::shared_ptr<JSPandaFile> baseFile = JSPandaFileManager::GetInstance()->FindJSPandaFile(baseFileName);
136     if (baseFile == nullptr) {
137         LOG_ECMA(ERROR) << "find base jsPandafile failed";
138         return PatchErrorCode::FILE_NOT_EXECUTED;
139     }
140 
141     std::shared_ptr<JSPandaFile> patchFile = JSPandaFileManager::GetInstance()->FindJSPandaFile(patchFileName);
142     if (patchFile == nullptr) {
143         LOG_ECMA(ERROR) << "find patch jsPandafile failed";
144         return PatchErrorCode::FILE_NOT_FOUND;
145     }
146 
147     const auto &baseMethodInfo = patchInfo.baseMethodInfo;
148     if (baseMethodInfo.empty()) {
149         LOG_ECMA(INFO) << "no method need to unload";
150         return PatchErrorCode::SUCCESS;
151     }
152 
153     EcmaVM *vm = thread->GetEcmaVM();
154     auto baseConstpoolValues = thread->GetCurrentEcmaContext()->FindConstpools(baseFile.get());
155     if (!baseConstpoolValues.has_value()) {
156         LOG_ECMA(ERROR) << "base constpool is empty";
157         return PatchErrorCode::INTERNAL_ERROR;
158     }
159 
160     for (const auto &item : baseMethodInfo) {
161         const auto &methodIndex = item.first;
162         ConstantPool *baseConstpool = ConstantPool::Cast(
163             (baseConstpoolValues.value().get()[methodIndex.constpoolNum]).GetTaggedObject());
164 
165         uint32_t constpoolIndex = methodIndex.constpoolIndex;
166         uint32_t literalIndex = methodIndex.literalIndex;
167         Method *patchMethod = nullptr;
168         if (literalIndex == UINT32_MAX) {
169             JSTaggedValue value = baseConstpool->GetObjectFromCache(constpoolIndex);
170             ASSERT(value.IsMethod());
171             patchMethod = Method::Cast(value.GetTaggedObject());
172         } else {
173             ClassLiteral *classLiteral = ClassLiteral::Cast(baseConstpool->GetObjectFromCache(constpoolIndex));
174             TaggedArray *literalArray = TaggedArray::Cast(classLiteral->GetArray());
175             JSTaggedValue value = literalArray->Get(thread, literalIndex);
176             ASSERT(value.IsJSFunctionBase());
177             JSFunctionBase *func = JSFunctionBase::Cast(value.GetTaggedObject());
178             patchMethod = Method::Cast(func->GetMethod().GetTaggedObject());
179         }
180 
181         MethodLiteral *baseMethodLiteral = item.second;
182         JSTaggedValue baseConstpoolValue = thread->GetCurrentEcmaContext()->FindConstpool(
183             baseFile.get(), baseMethodLiteral->GetMethodId());
184         ReplaceMethod(thread, patchMethod, baseMethodLiteral, baseConstpoolValue);
185         LOG_ECMA(INFO) << "Replace base method: "
186                        << patchMethod->GetRecordName()
187                        << ":" << patchMethod->GetMethodName();
188     }
189 
190     vm->GetJsDebuggerManager()->GetHotReloadManager()->NotifyPatchUnloaded(patchFile.get());
191 
192     // release base constpool.
193     CVector<JSHandle<JSTaggedValue>> &baseConstpools = patchInfo.baseConstpools;
194     GlobalHandleCollection gloalHandleCollection(thread);
195     for (auto &item : baseConstpools) {
196         gloalHandleCollection.Dispose(item);
197     }
198 
199     ClearPatchInfo(thread, patchFileName);
200     return PatchErrorCode::SUCCESS;
201 }
202 
ClearPatchInfo(JSThread * thread,const CString & patchFileName)203 void PatchLoader::ClearPatchInfo(JSThread *thread, const CString &patchFileName)
204 {
205     EcmaVM *vm = thread->GetEcmaVM();
206 
207     vm->GetGlobalEnv()->SetGlobalPatch(thread, vm->GetFactory()->EmptyArray());
208 
209     // For release patch constpool and JSPandaFile.
210     vm->CollectGarbage(TriggerGCType::FULL_GC);
211 
212     std::shared_ptr<JSPandaFile> patchFile = JSPandaFileManager::GetInstance()->FindJSPandaFile(patchFileName);
213     if (patchFile != nullptr) {
214         LOG_ECMA(INFO) << "patch jsPandaFile is not nullptr";
215     }
216 }
217 
ReplaceMethod(JSThread * thread,Method * destMethod,MethodLiteral * srcMethodLiteral,JSTaggedValue srcConstpool)218 void PatchLoader::ReplaceMethod(JSThread *thread,
219                                 Method *destMethod,
220                                 MethodLiteral *srcMethodLiteral,
221                                 JSTaggedValue srcConstpool)
222 {
223     // Update destmethod exclude ExtraLiteralInfo(FunctionKind). Method FunctionKind will be set after
224     // building class inheritance relationship or defining gettersetter by value.
225     //
226     // HotReload of class inheritance will be affected.
227     destMethod->SetCallField(srcMethodLiteral->GetCallField());
228     destMethod->SetLiteralInfo(srcMethodLiteral->GetLiteralInfo());
229     destMethod->SetCodeEntryOrLiteral(reinterpret_cast<uintptr_t>(srcMethodLiteral));
230     destMethod->SetNativePointerOrBytecodeArray(const_cast<void *>(srcMethodLiteral->GetNativePointer()));
231     destMethod->SetConstantPool(thread, srcConstpool);
232     destMethod->SetProfileTypeInfo(thread, JSTaggedValue::Undefined());
233     destMethod->SetAotCodeBit(false);
234 }
235 
FindAndReplaceSameMethod(JSThread * thread,const JSPandaFile * baseFile,const JSPandaFile * patchFile,PatchInfo & patchInfo)236 void PatchLoader::FindAndReplaceSameMethod(JSThread *thread, const JSPandaFile *baseFile,
237                                            const JSPandaFile *patchFile, PatchInfo &patchInfo)
238 {
239     auto context = thread->GetCurrentEcmaContext();
240     const CMap<int32_t, JSTaggedValue> &baseConstpoolValues = context->FindConstpools(baseFile).value();
241     for (const auto &item : baseConstpoolValues) {
242         if (item.second.IsHole()) {
243             continue;
244         }
245 
246         ConstantPool *baseConstpool = ConstantPool::Cast(item.second.GetTaggedObject());
247         uint32_t constpoolNum = item.first;
248         uint32_t baseConstpoolSize = baseConstpool->GetCacheLength();
249         for (uint32_t constpoolIndex = 0; constpoolIndex < baseConstpoolSize; constpoolIndex++) {
250             JSTaggedValue constpoolValue = baseConstpool->GetObjectFromCache(constpoolIndex);
251             if (!constpoolValue.IsMethod() && !constpoolValue.IsClassLiteral()) {
252                 continue;
253             }
254 
255             // For normal function and class constructor.
256             if (constpoolValue.IsMethod()) {
257                 Method *baseMethod = Method::Cast(constpoolValue.GetTaggedObject());
258                 EntityId baseMethodId = baseMethod->GetMethodId();
259                 MethodLiteral *patchMethodLiteral = FindSameMethod(patchInfo, baseFile, baseMethodId);
260                 if (patchMethodLiteral == nullptr) {
261                     continue;
262                 }
263 
264                 JSTaggedValue patchConstpoolValue = context->FindConstpool(patchFile,
265                     patchMethodLiteral->GetMethodId());
266                 ReplaceMethod(thread, baseMethod, patchMethodLiteral, patchConstpoolValue);
267 
268                 BaseMethodIndex indexs = {constpoolNum, constpoolIndex};
269                 SaveBaseMethodInfo(patchInfo, baseFile, baseMethodId, indexs);
270             } else if (constpoolValue.IsClassLiteral()) {
271                 // For class literal.
272                 ClassLiteral *classLiteral = ClassLiteral::Cast(constpoolValue);
273                 TaggedArray *literalArray = TaggedArray::Cast(classLiteral->GetArray());
274                 uint32_t literalLength = literalArray->GetLength();
275                 for (uint32_t literalIndex = 0; literalIndex < literalLength; literalIndex++) {
276                     JSTaggedValue literalItem = literalArray->Get(thread, literalIndex);
277                     if (!literalItem.IsJSFunctionBase()) {
278                         continue;
279                     }
280 
281                     // Every record is the same in current class literal.
282                     JSFunctionBase *func = JSFunctionBase::Cast(literalItem.GetTaggedObject());
283                     Method *baseMethod = Method::Cast(func->GetMethod().GetTaggedObject());
284                     EntityId baseMethodId = baseMethod->GetMethodId();
285                     MethodLiteral *patchMethodLiteral = FindSameMethod(patchInfo, baseFile, baseMethodId);
286                     if (patchMethodLiteral == nullptr) {
287                         continue;
288                     }
289 
290                     JSTaggedValue patchConstpoolValue = context->FindConstpool(patchFile,
291                         patchMethodLiteral->GetMethodId());
292                     ReplaceMethod(thread, baseMethod, patchMethodLiteral, patchConstpoolValue);
293 
294                     BaseMethodIndex indexs = {constpoolNum, constpoolIndex, literalIndex};
295                     SaveBaseMethodInfo(patchInfo, baseFile, baseMethodId, indexs);
296                 }
297             }
298         }
299     }
300 }
301 
FindSameMethod(PatchInfo & patchInfo,const JSPandaFile * baseFile,EntityId baseMethodId)302 MethodLiteral* PatchLoader::FindSameMethod(PatchInfo &patchInfo, const JSPandaFile *baseFile, EntityId baseMethodId)
303 {
304     const CMap<CString, CMap<CString, MethodLiteral*>> &patchMethodLiterals = patchInfo.patchMethodLiterals;
305     CString baseRecordName = MethodLiteral::GetRecordName(baseFile, baseMethodId);
306     auto recordIter = patchMethodLiterals.find(baseRecordName);
307     if (recordIter == patchMethodLiterals.end()) {
308         return nullptr;
309     }
310 
311     CString baseMethodName = MethodLiteral::GetMethodName(baseFile, baseMethodId);
312     auto methodIter = recordIter->second.find(baseMethodName);
313     if (methodIter == recordIter->second.end()) {
314         return nullptr;
315     }
316 
317     LOG_ECMA(INFO) << "Replace base method: " << baseRecordName << ":" << baseMethodName;
318     return methodIter->second;
319 }
320 
SaveBaseMethodInfo(PatchInfo & patchInfo,const JSPandaFile * baseFile,EntityId baseMethodId,const BaseMethodIndex & indexs)321 void PatchLoader::SaveBaseMethodInfo(PatchInfo &patchInfo, const JSPandaFile *baseFile,
322                                      EntityId baseMethodId, const BaseMethodIndex &indexs)
323 {
324     CMap<BaseMethodIndex, MethodLiteral *> &baseMethodInfo = patchInfo.baseMethodInfo;
325     MethodLiteral *baseMethodLiteral = baseFile->FindMethodLiteral(baseMethodId.GetOffset());
326     ASSERT(baseMethodLiteral != nullptr);
327     baseMethodInfo.emplace(indexs, baseMethodLiteral);
328 }
329 
GeneratePatchInfo(const JSPandaFile * patchFile)330 PatchInfo PatchLoader::GeneratePatchInfo(const JSPandaFile *patchFile)
331 {
332     const auto &map = patchFile->GetMethodLiteralMap();
333     CMap<CString, CMap<CString, MethodLiteral*>> methodLiterals;
334     for (const auto &item : map) {
335         MethodLiteral *methodLiteral = item.second;
336         EntityId methodId = EntityId(item.first);
337         CString methodName = MethodLiteral::GetMethodName(patchFile, methodId);
338         if (methodName == JSPandaFile::ENTRY_FUNCTION_NAME ||
339             methodName == JSPandaFile::PATCH_FUNCTION_NAME_0 ||
340             methodName == JSPandaFile::PATCH_FUNCTION_NAME_1) {
341             continue;
342         }
343 
344         CString recordName = MethodLiteral::GetRecordName(patchFile, methodId);
345         auto iter = methodLiterals.find(recordName);
346         if (iter != methodLiterals.end()) {
347             iter->second.emplace(methodName, methodLiteral);
348         } else {
349             CMap<CString, MethodLiteral*> methodNameInfo = {{methodName, methodLiteral}};
350             methodLiterals.emplace(recordName, std::move(methodNameInfo));
351         }
352     }
353 
354     PatchInfo patchInfo;
355     patchInfo.patchFileName = patchFile->GetJSPandaFileDesc();
356     patchInfo.patchMethodLiterals = std::move(methodLiterals);
357     return patchInfo;
358 }
359 }  // namespace panda::ecmascript