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
20namespace panda::ecmascript {
21namespace {
22const CString OHOS_PKG_ABC_PATH_ROOT = "/ets/";  // abc file always under /ets/ dir in HAP/HSP
23}  // namespace
24bool JSPandaFile::loadedFirstPandaFile = false;
25JSPandaFile::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
45void 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
68void 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
91JSPandaFile::~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
111uint32_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
132void 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
174void 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
228MethodLiteral *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
237bool JSPandaFile::IsFirstMergedAbc() const
238{
239    if (isFirstPandafile_ && !IsBundlePack()) {
240        return true;
241    }
242    return false;
243}
244
245bool 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
259const 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
272CString 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
281CString 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
289bool 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
299FunctionKind 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
337FunctionKind 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*/
375CString 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
391CString JSPandaFile::GetNormalizedFileDesc() const
392{
393    return GetNormalizedFileDesc(desc_);
394}
395
396std::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
412std::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
420CString 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
437CString JSPandaFile::GetRecordNameWithBundlePack(EntityId methodIdx)
438{
439    CString recordName = IsBundlePack() ? ENTRY_FUNCTION_NAME : GetRecordName(methodIdx);
440    ASSERT(HasRecord(recordName));
441    return recordName;
442}
443
444
445void JSPandaFile::ClearNameMap()
446{
447    {
448        LockHolder lock(methodNameMapMutex_);
449        methodNameMap_.clear();
450    }
451    {
452        LockHolder lock(recordNameMapMutex_);
453        recordNameMap_.clear();
454    }
455}
456
457size_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
478bool 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
486void 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
496void 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
503void JSPandaFile::IncreaseTaskCount()
504{
505    LockHolder holder(waitTranslateClassFinishedMutex_);
506    runningTaskCount_++;
507}
508
509void JSPandaFile::WaitTranslateClassTaskFinished()
510{
511    LockHolder holder(waitTranslateClassFinishedMutex_);
512    while (runningTaskCount_ > 0) {
513        waitTranslateClassFinishedCV_.Wait(&waitTranslateClassFinishedMutex_);
514    }
515}
516
517void JSPandaFile::ReduceTaskCount()
518{
519    LockHolder holder(waitTranslateClassFinishedMutex_);
520    runningTaskCount_--;
521    if (runningTaskCount_ == 0) {
522        waitTranslateClassFinishedCV_.SignalAll();
523    }
524}
525
526void 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
537void 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