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