• 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 
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