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