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
16 #include "ecmascript/jspandafile/js_pandafile.h"
17 #include "ecmascript/jspandafile/program_object.h"
18
19 namespace panda::ecmascript {
20 namespace {
21 const CString OHOS_PKG_ABC_PATH_ROOT = "/ets/"; // abc file always under /ets/ dir in HAP/HSP
22 } // namespace
23 bool JSPandaFile::loadedFirstPandaFile = false;
JSPandaFile(const panda_file::File * pf,const CString & descriptor,CreateMode mode)24 JSPandaFile::JSPandaFile(const panda_file::File *pf, const CString &descriptor, CreateMode mode)
25 : pf_(pf), desc_(descriptor), mode_(mode)
26 {
27 ASSERT(pf_ != nullptr);
28 CheckIsBundlePack();
29 if (isBundlePack_) {
30 InitializeUnMergedPF();
31 } else {
32 InitializeMergedPF();
33 }
34 checksum_ = pf->GetHeader()->checksum;
35 isNewVersion_ = pf_->GetHeader()->version > OLD_VERSION;
36 if (!loadedFirstPandaFile && !isBundlePack_) {
37 // Tag the first merged abc to use constant string. The lifetime of this first panda file is the same
38 // as the vm. And make sure the first pandafile is the same at the compile time and runtime.
39 isFirstPandafile_ = false;
40 loadedFirstPandaFile = true;
41 }
42 }
43
CheckIsBundlePack()44 void JSPandaFile::CheckIsBundlePack()
45 {
46 Span<const uint32_t> classIndexes = pf_->GetClasses();
47 for (const uint32_t index : classIndexes) {
48 panda_file::File::EntityId classId(index);
49 if (pf_->IsExternal(classId)) {
50 continue;
51 }
52 panda_file::ClassDataAccessor cda(*pf_, classId);
53 cda.EnumerateFields([&](panda_file::FieldDataAccessor &fieldAccessor) -> void {
54 panda_file::File::EntityId fieldNameId = fieldAccessor.GetNameId();
55 panda_file::File::StringData sd = GetStringData(fieldNameId);
56 const char *fieldName = utf::Mutf8AsCString(sd.data);
57 if (std::strcmp(IS_COMMON_JS, fieldName) == 0 || std::strcmp(MODULE_RECORD_IDX, fieldName) == 0) {
58 isBundlePack_ = false;
59 }
60 });
61 if (!isBundlePack_) {
62 return;
63 }
64 }
65 }
66
CheckIsRecordWithBundleName(const CString & entry)67 void JSPandaFile::CheckIsRecordWithBundleName(const CString &entry)
68 {
69 size_t pos = entry.find('/');
70 if (pos == CString::npos) {
71 LOG_FULL(FATAL) << "CheckIsRecordWithBundleName Invalid parameter entry";
72 }
73
74 CString bundleName = entry.substr(0, pos);
75 size_t bundleNameLen = bundleName.length();
76 for (auto &[recordName, _] : jsRecordInfo_) {
77 if (recordName.find(PACKAGE_PATH_SEGMENT) != CString::npos ||
78 recordName.find(NPM_PATH_SEGMENT) != CString::npos) {
79 continue;
80 }
81 // Confirm whether the current record is new or old by judging whether the recordName has a bundleName
82 if (!(recordName.length() > bundleNameLen && (recordName.compare(0, bundleNameLen, bundleName) == 0))) {
83 isRecordWithBundleName_ = false;
84 }
85 return;
86 }
87 }
88
~JSPandaFile()89 JSPandaFile::~JSPandaFile()
90 {
91 if (pf_ != nullptr) {
92 delete pf_;
93 pf_ = nullptr;
94 }
95
96 constpoolMap_.clear();
97 for (auto& each : jsRecordInfo_) {
98 delete each.second;
99 }
100 jsRecordInfo_.clear();
101 methodLiteralMap_.clear();
102 ClearNameMap();
103 if (methodLiterals_ != nullptr) {
104 JSPandaFileManager::FreeBuffer(methodLiterals_, sizeof(MethodLiteral) * numMethods_, isBundlePack_, mode_);
105 methodLiterals_ = nullptr;
106 }
107 }
108
GetOrInsertConstantPool(ConstPoolType type,uint32_t offset,const CUnorderedMap<uint32_t,uint64_t> * constpoolMap)109 uint32_t JSPandaFile::GetOrInsertConstantPool(ConstPoolType type, uint32_t offset,
110 const CUnorderedMap<uint32_t, uint64_t> *constpoolMap)
111 {
112 CUnorderedMap<uint32_t, uint64_t> *map = nullptr;
113 if (constpoolMap != nullptr && !IsBundlePack()) {
114 map = const_cast<CUnorderedMap<uint32_t, uint64_t> *>(constpoolMap);
115 } else {
116 map = &constpoolMap_;
117 }
118 auto it = map->find(offset);
119 if (it != map->cend()) {
120 ConstPoolValue value(it->second);
121 return value.GetConstpoolIndex();
122 }
123 ASSERT(constpoolIndex_ != UINT32_MAX);
124 uint32_t index = constpoolIndex_++;
125 ConstPoolValue value(type, index);
126 map->emplace(offset, value.GetValue());
127 return index;
128 }
129
InitializeUnMergedPF()130 void JSPandaFile::InitializeUnMergedPF()
131 {
132 Span<const uint32_t> classIndexes = pf_->GetClasses();
133 numClasses_ = classIndexes.size();
134 JSRecordInfo* info = new JSRecordInfo();
135 for (const uint32_t index : classIndexes) {
136 panda_file::File::EntityId classId(index);
137 if (pf_->IsExternal(classId)) {
138 continue;
139 }
140 panda_file::ClassDataAccessor cda(*pf_, classId);
141 numMethods_ += cda.GetMethodsNumber();
142 const char *desc = utf::Mutf8AsCString(cda.GetDescriptor());
143 if (info->moduleRecordIdx == -1 && std::strcmp(MODULE_CLASS, desc) == 0) {
144 cda.EnumerateFields([&](panda_file::FieldDataAccessor &fieldAccessor) -> void {
145 panda_file::File::EntityId fieldNameId = fieldAccessor.GetNameId();
146 panda_file::File::StringData sd = GetStringData(fieldNameId);
147 CString fieldName = utf::Mutf8AsCString(sd.data);
148 if (fieldName != desc_) {
149 info->moduleRecordIdx = fieldAccessor.GetValue<int32_t>().value();
150 info->classId = index;
151 return;
152 }
153 });
154 }
155 if (!info->isCjs && std::strcmp(COMMONJS_CLASS, desc) == 0) {
156 info->classId = index;
157 info->isCjs = true;
158 }
159 if (!info->isSharedModule && std::strcmp(IS_SHARED_MODULE, desc) == 0) {
160 info->isSharedModule = true;
161 }
162 if (!info->hasTopLevelAwait && std::strcmp(HASTLA_CLASS, desc) == 0) {
163 info->hasTopLevelAwait = true;
164 }
165 }
166 jsRecordInfo_.insert({JSPandaFile::ENTRY_FUNCTION_NAME, info});
167 methodLiterals_ = static_cast<MethodLiteral *>(
168 JSPandaFileManager::AllocateBuffer(sizeof(MethodLiteral) * numMethods_, isBundlePack_, mode_));
169 methodLiteralMap_.reserve(numMethods_);
170 }
171
InitializeMergedPF()172 void JSPandaFile::InitializeMergedPF()
173 {
174 Span<const uint32_t> classIndexes = pf_->GetClasses();
175 numClasses_ = classIndexes.size();
176 jsRecordInfo_.reserve(numClasses_);
177 for (const uint32_t index : classIndexes) {
178 panda_file::File::EntityId classId(index);
179 if (pf_->IsExternal(classId)) {
180 continue;
181 }
182 panda_file::ClassDataAccessor cda(*pf_, classId);
183 numMethods_ += cda.GetMethodsNumber();
184 JSRecordInfo* info = new JSRecordInfo();
185 info->classId = index;
186 bool hasCjsFiled = false;
187 bool hasJsonFiled = false;
188 CString desc = utf::Mutf8AsCString(cda.GetDescriptor());
189 CString recordName = ParseEntryPoint(desc);
190 cda.EnumerateFields([&](panda_file::FieldDataAccessor &fieldAccessor) -> void {
191 panda_file::File::EntityId fieldNameId = fieldAccessor.GetNameId();
192 panda_file::File::StringData sd = GetStringData(fieldNameId);
193 const char *fieldName = utf::Mutf8AsCString(sd.data);
194 if (std::strcmp(IS_COMMON_JS, fieldName) == 0) {
195 hasCjsFiled = true;
196 info->isCjs = fieldAccessor.GetValue<bool>().value();
197 } else if (std::strcmp(IS_JSON_CONTENT, fieldName) == 0) {
198 hasJsonFiled = true;
199 info->isJson = true;
200 info->jsonStringId = fieldAccessor.GetValue<uint32_t>().value();
201 } else if (std::strcmp(MODULE_RECORD_IDX, fieldName) == 0) {
202 info->moduleRecordIdx = fieldAccessor.GetValue<int32_t>().value();
203 } else if (std::strcmp(IS_SHARED_MODULE, fieldName) == 0) {
204 info->isSharedModule = fieldAccessor.GetValue<bool>().value();
205 } else if (std::strcmp(HAS_TOP_LEVEL_AWAIT, fieldName) == 0) {
206 info->hasTopLevelAwait = fieldAccessor.GetValue<bool>().value();
207 } else if (std::strcmp(LAZY_IMPORT, fieldName) == 0) {
208 info->lazyImportIdx = fieldAccessor.GetValue<uint32_t>().value();
209 } else if (std::strlen(fieldName) > PACKAGE_NAME_LEN &&
210 std::strncmp(fieldName, PACKAGE_NAME, PACKAGE_NAME_LEN) == 0) {
211 info->npmPackageName = fieldName + PACKAGE_NAME_LEN;
212 } else {
213 npmEntries_.emplace(recordName, fieldName);
214 }
215 });
216 if (hasCjsFiled || hasJsonFiled) {
217 jsRecordInfo_.emplace(recordName, info);
218 } else {
219 delete info;
220 }
221 }
222 methodLiterals_ = static_cast<MethodLiteral *>(
223 JSPandaFileManager::AllocateBuffer(sizeof(MethodLiteral) * numMethods_, isBundlePack_, mode_));
224 methodLiteralMap_.reserve(numMethods_);
225 }
226
IsFirstMergedAbc() const227 bool JSPandaFile::IsFirstMergedAbc() const
228 {
229 if (isFirstPandafile_ && !IsBundlePack()) {
230 return true;
231 }
232 return false;
233 }
234
GetEntryPoint(const CString & recordName) const235 CString JSPandaFile::GetEntryPoint(const CString &recordName) const
236 {
237 CString entryPoint;
238 if (FindOhmUrlInPF(recordName, entryPoint) && HasRecord(entryPoint)) {
239 return entryPoint;
240 }
241 return CString();
242 }
243
GetRecordName(const CString & entryPoint) const244 CString JSPandaFile::GetRecordName(const CString &entryPoint) const
245 {
246 if (entryPoint.empty() || entryPoint == JSPandaFile::ENTRY_FUNCTION_NAME) {
247 return GetJSPandaFileDesc();
248 }
249 return entryPoint;
250 }
251
FindOhmUrlInPF(const CString & recordName,CString & entryPoint) const252 bool JSPandaFile::FindOhmUrlInPF(const CString &recordName, CString &entryPoint) const
253 {
254 auto info = npmEntries_.find(recordName);
255 if (info != npmEntries_.end()) {
256 entryPoint = info->second;
257 return true;
258 }
259 return false;
260 }
261
GetFunctionKind(panda_file::FunctionKind funcKind)262 FunctionKind JSPandaFile::GetFunctionKind(panda_file::FunctionKind funcKind)
263 {
264 FunctionKind kind;
265 if ((static_cast<uint32_t>(funcKind) & SENDABLE_FUNCTION_MASK) != 0) {
266 funcKind = static_cast<panda_file::FunctionKind>(static_cast<uint32_t>(funcKind) & (~SENDABLE_FUNCTION_MASK));
267 }
268 switch (funcKind) {
269 case panda_file::FunctionKind::NONE:
270 kind = FunctionKind::NONE_FUNCTION;
271 break;
272 case panda_file::FunctionKind::FUNCTION:
273 kind = FunctionKind::BASE_CONSTRUCTOR;
274 break;
275 case panda_file::FunctionKind::NC_FUNCTION:
276 kind = FunctionKind::ARROW_FUNCTION;
277 break;
278 case panda_file::FunctionKind::GENERATOR_FUNCTION:
279 kind = FunctionKind::GENERATOR_FUNCTION;
280 break;
281 case panda_file::FunctionKind::ASYNC_FUNCTION:
282 kind = FunctionKind::ASYNC_FUNCTION;
283 break;
284 case panda_file::FunctionKind::ASYNC_GENERATOR_FUNCTION:
285 kind = FunctionKind::ASYNC_GENERATOR_FUNCTION;
286 break;
287 case panda_file::FunctionKind::ASYNC_NC_FUNCTION:
288 kind = FunctionKind::ASYNC_ARROW_FUNCTION;
289 break;
290 case panda_file::FunctionKind::CONCURRENT_FUNCTION:
291 kind = FunctionKind::CONCURRENT_FUNCTION;
292 break;
293 default:
294 LOG_ECMA(FATAL) << "this branch is unreachable";
295 UNREACHABLE();
296 }
297 return kind;
298 }
299
GetFunctionKind(ConstPoolType type)300 FunctionKind JSPandaFile::GetFunctionKind(ConstPoolType type)
301 {
302 FunctionKind kind;
303 switch (type) {
304 case ConstPoolType::BASE_FUNCTION:
305 kind = FunctionKind::BASE_CONSTRUCTOR;
306 break;
307 case ConstPoolType::NC_FUNCTION:
308 kind = FunctionKind::ARROW_FUNCTION;
309 break;
310 case ConstPoolType::GENERATOR_FUNCTION:
311 kind = FunctionKind::GENERATOR_FUNCTION;
312 break;
313 case ConstPoolType::ASYNC_FUNCTION:
314 kind = FunctionKind::ASYNC_FUNCTION;
315 break;
316 case ConstPoolType::CLASS_FUNCTION:
317 kind = FunctionKind::CLASS_CONSTRUCTOR;
318 break;
319 case ConstPoolType::METHOD:
320 kind = FunctionKind::NORMAL_FUNCTION;
321 break;
322 case ConstPoolType::ASYNC_GENERATOR_FUNCTION:
323 kind = FunctionKind::ASYNC_GENERATOR_FUNCTION;
324 break;
325 default:
326 LOG_ECMA(FATAL) << "this branch is unreachable";
327 UNREACHABLE();
328 }
329 return kind;
330 }
331
332 /*
333 handle desc like: case1: /data/storage/el1/bundle/entry/ets/modules.abc -> entry/ets/modules.abc
334 case2: /data/storage/el1/bundle/entry/ets/widgets.abc -> entry/ets/widgets.abc
335 case3: /data/app/el1/bundle/public/com.xx.xx/entry/ets/modules.abc -> entry/ets/modules.abc
336 case4: /data/app/el1/bundle/public/com.xx.xx/entry/ets/widgets.abc -> entry/ets/widgets.abc
337 */
GetNormalizedFileDesc(const CString & desc)338 CString JSPandaFile::GetNormalizedFileDesc(const CString &desc)
339 {
340 auto etsTokenPos = desc.rfind(OHOS_PKG_ABC_PATH_ROOT);
341 if (etsTokenPos == std::string::npos) {
342 // file not in OHOS package.
343 return desc;
344 }
345 auto ohosModulePos = desc.rfind('/', etsTokenPos - 1);
346 if (ohosModulePos == std::string::npos) {
347 LOG_ECMA(ERROR) << "Get abcPath from desc failed. desc: " << desc;
348 return desc;
349 }
350 // substring likes {ohosModuleName}/ets/modules.abc or {ohosModuleName}/ets/widgets.abc
351 return desc.substr(ohosModulePos + 1);
352 }
353
GetNormalizedFileDesc() const354 CString JSPandaFile::GetNormalizedFileDesc() const
355 {
356 return GetNormalizedFileDesc(desc_);
357 }
358
GetMethodName(EntityId methodId)359 std::pair<std::string_view, bool> JSPandaFile::GetMethodName(EntityId methodId)
360 {
361 LockHolder lock(methodNameMapMutex_);
362 uint32_t id = methodId.GetOffset();
363 auto iter = methodNameMap_.find(id);
364 if (iter != methodNameMap_.end()) {
365 panda_file::StringData sd = iter->second;
366 return {std::string_view(utf::Mutf8AsCString(sd.data), sd.utf16_length), sd.is_ascii};
367 }
368
369 panda_file::MethodDataAccessor mda(*pf_, methodId);
370 auto sd = GetStringData(mda.GetNameId());
371 methodNameMap_.emplace(id, sd);
372 return {std::string_view(utf::Mutf8AsCString(sd.data), sd.utf16_length), sd.is_ascii};
373 }
374
GetCpuProfilerMethodName(EntityId methodId) const375 std::pair<std::string_view, bool> JSPandaFile::GetCpuProfilerMethodName(EntityId methodId) const
376 {
377 panda_file::MethodDataAccessor mda(*pf_, methodId);
378 auto sd = GetStringData(mda.GetNameId());
379 std::string_view strView(utf::Mutf8AsCString(sd.data), sd.utf16_length);
380 return {strView, sd.is_ascii};
381 }
382
GetRecordName(EntityId methodId)383 CString JSPandaFile::GetRecordName(EntityId methodId)
384 {
385 LockHolder lock(recordNameMapMutex_);
386 uint32_t id = methodId.GetOffset();
387 auto iter = recordNameMap_.find(id);
388 if (iter != recordNameMap_.end()) {
389 return iter->second;
390 }
391
392 panda_file::MethodDataAccessor mda(*pf_, methodId);
393 panda_file::ClassDataAccessor cda(*pf_, mda.GetClassId());
394 CString desc = utf::Mutf8AsCString(cda.GetDescriptor());
395 auto name = JSPandaFile::ParseEntryPoint(desc);
396 recordNameMap_.emplace(id, name);
397 return name;
398 }
399
GetRecordNameWithBundlePack(EntityId methodIdx)400 CString JSPandaFile::GetRecordNameWithBundlePack(EntityId methodIdx)
401 {
402 CString recordName = IsBundlePack() ? ENTRY_FUNCTION_NAME : GetRecordName(methodIdx);
403 ASSERT(HasRecord(recordName));
404 return recordName;
405 }
406
407
ClearNameMap()408 void JSPandaFile::ClearNameMap()
409 {
410 {
411 LockHolder lock(methodNameMapMutex_);
412 methodNameMap_.clear();
413 }
414 {
415 LockHolder lock(recordNameMapMutex_);
416 recordNameMap_.clear();
417 }
418 }
419
GetClassAndMethodIndexes(std::vector<std::pair<uint32_t,uint32_t>> & indexes)420 void JSPandaFile::GetClassAndMethodIndexes(std::vector<std::pair<uint32_t, uint32_t>> &indexes)
421 {
422 // Each thread gets 128 classes each time. If less than 128, it gets 2 classes.
423 indexes.clear();
424 LockHolder lock(classIndexMutex_);
425 if (classIndex_ >= numClasses_) {
426 return;
427 }
428 uint32_t cnts = ASYN_TRANSLATE_CLSSS_COUNT;
429 uint32_t minCount = (Taskpool::GetCurrentTaskpool()->GetTotalThreadNum() + 1) * ASYN_TRANSLATE_CLSSS_COUNT;
430 if (numClasses_ - classIndex_ < minCount) {
431 cnts = ASYN_TRANSLATE_CLSSS_MIN_COUNT;
432 }
433 for (uint32_t i = 0; i < cnts; i++) {
434 uint32_t classIdx = 0;
435 uint32_t methodIdx = 0;
436 Span<const uint32_t> classIndexes = GetClasses();
437 uint32_t index = 0;
438 do {
439 classIdx = classIndex_++;
440 if (classIdx >= numClasses_) {
441 return;
442 }
443 index = classIndexes[classIdx];
444 } while (IsExternal(panda_file::File::EntityId(index)));
445
446 methodIdx = methodIndex_;
447 panda_file::File::EntityId classId(classIndexes[classIdx]);
448 panda_file::ClassDataAccessor cda(*pf_, classId);
449 methodIndex_ += cda.GetMethodsNumber();
450 indexes.emplace_back(methodIdx, classIdx);
451 }
452 }
453
Run(uint32_t threadIndex)454 bool JSPandaFile::TranslateClassesTask::Run([[maybe_unused]] uint32_t threadIndex)
455 {
456 ECMA_BYTRACE_NAME(HITRACE_TAG_ARK, "TranslateClassesTask::Run");
457 jsPandaFile_->TranslateClass(thread_, *methodNamePtr_);
458 jsPandaFile_->ReduceTaskCount();
459 return true;
460 }
461
TranslateClass(JSThread * thread,const CString & methodName)462 void JSPandaFile::TranslateClass(JSThread *thread, const CString &methodName)
463 {
464 std::vector<std::pair<uint32_t, uint32_t>> indexes;
465 indexes.reserve(ASYN_TRANSLATE_CLSSS_COUNT);
466 do {
467 GetClassAndMethodIndexes(indexes);
468 uint32_t size = indexes.size();
469 for (uint32_t i = 0; i < size; i++) {
470 PandaFileTranslator::TranslateClass(thread, this, methodName, indexes[i].first, indexes[i].second);
471 }
472 } while (!indexes.empty());
473 }
474
PostInitializeMethodTask(JSThread * thread,const std::shared_ptr<CString> & methodNamePtr)475 void JSPandaFile::PostInitializeMethodTask(JSThread *thread, const std::shared_ptr<CString> &methodNamePtr)
476 {
477 IncreaseTaskCount();
478 Taskpool::GetCurrentTaskpool()->PostTask(
479 std::make_unique<TranslateClassesTask>(thread->GetThreadId(), thread, this, methodNamePtr));
480 }
481
IncreaseTaskCount()482 void JSPandaFile::IncreaseTaskCount()
483 {
484 LockHolder holder(waitTranslateClassFinishedMutex_);
485 runningTaskCount_++;
486 }
487
WaitTranslateClassTaskFinished()488 void JSPandaFile::WaitTranslateClassTaskFinished()
489 {
490 LockHolder holder(waitTranslateClassFinishedMutex_);
491 while (runningTaskCount_ > 0) {
492 waitTranslateClassFinishedCV_.Wait(&waitTranslateClassFinishedMutex_);
493 }
494 }
495
ReduceTaskCount()496 void JSPandaFile::ReduceTaskCount()
497 {
498 LockHolder holder(waitTranslateClassFinishedMutex_);
499 runningTaskCount_--;
500 if (runningTaskCount_ == 0) {
501 waitTranslateClassFinishedCV_.SignalAll();
502 }
503 }
504
SetAllMethodLiteralToMap()505 void JSPandaFile::SetAllMethodLiteralToMap()
506 {
507 // async to optimize SetAllMethodLiteralToMap later
508 MethodLiteral *methodLiterals = GetMethodLiterals();
509 size_t methodIdx = 0;
510 while (methodIdx < numMethods_) {
511 MethodLiteral *methodLiteral = methodLiterals + (methodIdx++);
512 SetMethodLiteralToMap(methodLiteral);
513 }
514 }
515
TranslateClasses(JSThread * thread,const CString & methodName)516 void JSPandaFile::TranslateClasses(JSThread *thread, const CString &methodName)
517 {
518 const std::shared_ptr<CString> methodNamePtr = std::make_shared<CString>(methodName);
519 for (uint32_t i = 0; i < Taskpool::GetCurrentTaskpool()->GetTotalThreadNum(); i++) {
520 PostInitializeMethodTask(thread, methodNamePtr);
521 }
522 TranslateClass(thread, methodName);
523 WaitTranslateClassTaskFinished();
524 SetAllMethodLiteralToMap();
525 }
526 } // namespace panda::ecmascript
527