1 /*
2 * Copyright (c) 2021 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/class_info_extractor.h"
17
18 #include "ecmascript/global_env.h"
19 #include "ecmascript/js_function.h"
20 #include "ecmascript/tagged_dictionary.h"
21
22 namespace panda::ecmascript {
BuildClassInfoExtractorFromLiteral(JSThread * thread,JSHandle<ClassInfoExtractor> & extractor,const JSHandle<TaggedArray> & literal)23 void ClassInfoExtractor::BuildClassInfoExtractorFromLiteral(JSThread *thread, JSHandle<ClassInfoExtractor> &extractor,
24 const JSHandle<TaggedArray> &literal)
25 {
26 [[maybe_unused]] EcmaHandleScope handleScope(thread);
27 const GlobalEnvConstants *globalConst = thread->GlobalConstants();
28 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
29
30 uint32_t literalBufferLength = literal->GetLength();
31 // non static properties number is hidden in the last index of Literal buffer
32 uint32_t nonStaticNum = literal->Get(thread, literalBufferLength - 1).GetInt();
33
34 // Reserve sufficient length to prevent frequent creation.
35 JSHandle<TaggedArray> nonStaticKeys = factory->NewTaggedArray(nonStaticNum + NON_STATIC_RESERVED_LENGTH);
36 JSHandle<TaggedArray> nonStaticProperties = factory->NewTaggedArray(nonStaticNum + NON_STATIC_RESERVED_LENGTH);
37
38 nonStaticKeys->Set(thread, CONSTRUCTOR_INDEX, globalConst->GetConstructorString());
39
40 JSHandle<TaggedArray> nonStaticElements = factory->EmptyArray();
41
42 if (nonStaticNum) {
43 ExtractContentsDetail nonStaticDetail {0, nonStaticNum * 2, NON_STATIC_RESERVED_LENGTH, nullptr};
44
45 if (UNLIKELY(ExtractAndReturnWhetherWithElements(thread, literal, nonStaticDetail, nonStaticKeys,
46 nonStaticProperties, nonStaticElements))) {
47 extractor->SetNonStaticWithElements(true);
48 extractor->SetNonStaticElements(thread, nonStaticElements);
49 }
50 }
51
52 extractor->SetNonStaticKeys(thread, nonStaticKeys);
53 extractor->SetNonStaticProperties(thread, nonStaticProperties);
54
55 JSHandle<JSHClass> prototypeHClass = CreatePrototypeHClass(thread, nonStaticKeys, nonStaticProperties);
56 extractor->SetPrototypeHClass(thread, prototypeHClass);
57
58 uint32_t staticNum = (literalBufferLength - 1) / 2 - nonStaticNum;
59
60 // Reserve sufficient length to prevent frequent creation.
61 JSHandle<TaggedArray> staticKeys = factory->NewTaggedArray(staticNum + STATIC_RESERVED_LENGTH);
62 JSHandle<TaggedArray> staticProperties = factory->NewTaggedArray(staticNum + STATIC_RESERVED_LENGTH);
63
64 staticKeys->Set(thread, LENGTH_INDEX, globalConst->GetLengthString());
65 staticKeys->Set(thread, NAME_INDEX, globalConst->GetNameString());
66 staticKeys->Set(thread, PROTOTYPE_INDEX, globalConst->GetPrototypeString());
67
68 JSHandle<TaggedArray> staticElements = factory->EmptyArray();
69
70 if (staticNum) {
71 ExtractContentsDetail staticDetail {
72 nonStaticNum * 2,
73 literalBufferLength - 1,
74 STATIC_RESERVED_LENGTH,
75 extractor->GetConstructorMethod()
76 };
77
78 if (UNLIKELY(ExtractAndReturnWhetherWithElements(thread, literal, staticDetail, staticKeys,
79 staticProperties, staticElements))) {
80 extractor->SetStaticWithElements(true);
81 extractor->SetStaticElements(thread, staticElements);
82 }
83 } else {
84 // without static properties, set class name
85 std::string clsName = extractor->GetConstructorMethod()->ParseFunctionName();
86 JSHandle<EcmaString> clsNameHandle = factory->NewFromStdString(clsName);
87 staticProperties->Set(thread, NAME_INDEX, clsNameHandle);
88 }
89
90 // set prototype internal accessor
91 JSHandle<JSTaggedValue> prototypeAccessor = globalConst->GetHandledFunctionPrototypeAccessor();
92 staticProperties->Set(thread, PROTOTYPE_INDEX, prototypeAccessor);
93
94 extractor->SetStaticKeys(thread, staticKeys);
95 extractor->SetStaticProperties(thread, staticProperties);
96
97 JSHandle<JSHClass> ctorHClass = CreateConstructorHClass(thread, staticKeys, staticProperties);
98 extractor->SetConstructorHClass(thread, ctorHClass);
99 }
100
ExtractAndReturnWhetherWithElements(JSThread * thread,const JSHandle<TaggedArray> & literal,const ExtractContentsDetail & detail,JSHandle<TaggedArray> & keys,JSHandle<TaggedArray> & properties,JSHandle<TaggedArray> & elements)101 bool ClassInfoExtractor::ExtractAndReturnWhetherWithElements(JSThread *thread, const JSHandle<TaggedArray> &literal,
102 const ExtractContentsDetail &detail,
103 JSHandle<TaggedArray> &keys,
104 JSHandle<TaggedArray> &properties,
105 JSHandle<TaggedArray> &elements)
106 {
107 const GlobalEnvConstants *globalConst = thread->GlobalConstants();
108
109 ASSERT(keys->GetLength() == properties->GetLength() && elements->GetLength() == 0);
110
111 uint32_t pos = detail.fillStartLoc;
112 bool withElemenstFlag = false;
113 bool isStaticFlag = detail.ctorMethod ? true : false;
114 bool keysHasNameFlag = false;
115
116 JSHandle<JSTaggedValue> nameString = globalConst->GetHandledNameString();
117 JSMutableHandle<JSTaggedValue> firstValue(thread, JSTaggedValue::Undefined());
118 JSMutableHandle<JSTaggedValue> secondValue(thread, JSTaggedValue::Undefined());
119 for (uint32_t index = detail.extractBegin; index < detail.extractEnd; index += 2) { // 2: key-value pair
120 firstValue.Update(literal->Get(index));
121 secondValue.Update(literal->Get(index + 1));
122 ASSERT_PRINT(JSTaggedValue::IsPropertyKey(firstValue), "Key is not a property key");
123
124 if (LIKELY(firstValue->IsString())) {
125 if (isStaticFlag && !keysHasNameFlag && JSTaggedValue::SameValue(firstValue, nameString)) {
126 properties->Set(thread, NAME_INDEX, secondValue);
127 keysHasNameFlag = true;
128 continue;
129 }
130
131 // front-end can do better: write index in class literal directly.
132 uint32_t elementIndex = 0;
133 if (JSTaggedValue::StringToElementIndex(firstValue.GetTaggedValue(), &elementIndex)) {
134 ASSERT(elementIndex < JSObject::MAX_ELEMENT_INDEX);
135 uint32_t elementsLength = elements->GetLength();
136 elements = TaggedArray::SetCapacity(thread, elements, elementsLength + 2); // 2: key-value pair
137 elements->Set(thread, elementsLength, firstValue);
138 elements->Set(thread, elementsLength + 1, secondValue);
139 withElemenstFlag = true;
140 continue;
141 }
142 }
143
144 keys->Set(thread, pos, firstValue);
145 properties->Set(thread, pos, secondValue);
146 pos++;
147 }
148
149 if (isStaticFlag) {
150 if (LIKELY(!keysHasNameFlag)) {
151 [[maybe_unused]] EcmaHandleScope handleScope(thread);
152 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
153 std::string clsName = detail.ctorMethod->ParseFunctionName();
154 JSHandle<EcmaString> clsNameHandle = factory->NewFromStdString(clsName);
155 properties->Set(thread, NAME_INDEX, clsNameHandle);
156 } else {
157 // class has static name property, reserved length bigger 1 than actual, need trim
158 uint32_t trimOneLength = keys->GetLength() - 1;
159 keys->Trim(thread, trimOneLength);
160 properties->Trim(thread, trimOneLength);
161 }
162 }
163
164 if (UNLIKELY(withElemenstFlag)) {
165 ASSERT(pos + elements->GetLength() / 2 == properties->GetLength()); // 2: half
166 keys->Trim(thread, pos);
167 properties->Trim(thread, pos);
168 }
169
170 return withElemenstFlag;
171 }
172
CreatePrototypeHClass(JSThread * thread,JSHandle<TaggedArray> & keys,JSHandle<TaggedArray> & properties)173 JSHandle<JSHClass> ClassInfoExtractor::CreatePrototypeHClass(JSThread *thread, JSHandle<TaggedArray> &keys,
174 JSHandle<TaggedArray> &properties)
175 {
176 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
177
178 uint32_t length = keys->GetLength();
179 JSHandle<JSHClass> hclass;
180 if (LIKELY(length <= PropertyAttributes::MAX_CAPACITY_OF_PROPERTIES)) {
181 JSMutableHandle<JSTaggedValue> key(thread, JSTaggedValue::Undefined());
182 JSHandle<LayoutInfo> layout = factory->CreateLayoutInfo(length);
183 for (uint32_t index = 0; index < length; ++index) {
184 key.Update(keys->Get(index));
185 ASSERT_PRINT(JSTaggedValue::IsPropertyKey(key), "Key is not a property key");
186 PropertyAttributes attributes = PropertyAttributes::Default(true, false, true); // non-enumerable
187
188 if (UNLIKELY(properties->Get(index).IsAccessor())) {
189 attributes.SetIsAccessor(true);
190 }
191
192 attributes.SetIsInlinedProps(true);
193 attributes.SetRepresentation(Representation::MIXED);
194 attributes.SetOffset(index);
195 layout->AddKey(thread, index, key.GetTaggedValue(), attributes);
196 }
197
198 hclass = factory->NewEcmaDynClass(JSObject::SIZE, JSType::JS_OBJECT, length);
199 // Not need set proto here
200 hclass->SetLayout(thread, layout);
201 hclass->SetNumberOfProps(length);
202 } else {
203 // dictionary mode
204 hclass = factory->NewEcmaDynClass(JSObject::SIZE, JSType::JS_OBJECT, 0); // without in-obj
205 hclass->SetIsDictionaryMode(true);
206 hclass->SetNumberOfProps(0);
207 }
208
209 hclass->SetClassPrototype(true);
210 hclass->SetIsPrototype(true);
211 return hclass;
212 }
213
CreateConstructorHClass(JSThread * thread,JSHandle<TaggedArray> & keys,JSHandle<TaggedArray> & properties)214 JSHandle<JSHClass> ClassInfoExtractor::CreateConstructorHClass(JSThread *thread, JSHandle<TaggedArray> &keys,
215 JSHandle<TaggedArray> &properties)
216 {
217 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
218
219 uint32_t length = keys->GetLength();
220 JSHandle<JSHClass> hclass;
221 if (LIKELY(length <= PropertyAttributes::MAX_CAPACITY_OF_PROPERTIES)) {
222 JSMutableHandle<JSTaggedValue> key(thread, JSTaggedValue::Undefined());
223 JSHandle<LayoutInfo> layout = factory->CreateLayoutInfo(length);
224 for (uint32_t index = 0; index < length; ++index) {
225 key.Update(keys->Get(index));
226 ASSERT_PRINT(JSTaggedValue::IsPropertyKey(key), "Key is not a property key");
227 PropertyAttributes attributes;
228 switch (index) {
229 case LENGTH_INDEX:
230 attributes = PropertyAttributes::Default(false, false, true);
231 break;
232 case NAME_INDEX:
233 if (LIKELY(properties->Get(NAME_INDEX).IsString())) {
234 attributes = PropertyAttributes::Default(false, false, true);
235 } else {
236 ASSERT(properties->Get(NAME_INDEX).IsJSFunction());
237 attributes = PropertyAttributes::Default(true, false, true);
238 }
239 break;
240 case PROTOTYPE_INDEX:
241 attributes = PropertyAttributes::DefaultAccessor(false, false, false);
242 break;
243 default:
244 attributes = PropertyAttributes::Default(true, false, true);
245 break;
246 }
247
248 if (UNLIKELY(properties->Get(index).IsAccessor())) {
249 attributes.SetIsAccessor(true);
250 }
251
252 attributes.SetIsInlinedProps(true);
253 attributes.SetRepresentation(Representation::MIXED);
254 attributes.SetOffset(index);
255 layout->AddKey(thread, index, key.GetTaggedValue(), attributes);
256 }
257
258 hclass = factory->NewEcmaDynClass(JSFunction::SIZE, JSType::JS_FUNCTION, length);
259 // Not need set proto here
260 hclass->SetLayout(thread, layout);
261 hclass->SetNumberOfProps(length);
262 } else {
263 // dictionary mode
264 hclass = factory->NewEcmaDynClass(JSFunction::SIZE, JSType::JS_FUNCTION, 0); // without in-obj
265 hclass->SetIsDictionaryMode(true);
266 hclass->SetNumberOfProps(0);
267 }
268
269 hclass->SetClassConstructor(true);
270 hclass->SetConstructor(true);
271
272 return hclass;
273 }
274
DefineClassTemplate(JSThread * thread,JSHandle<ClassInfoExtractor> & extractor,const JSHandle<ConstantPool> & constantpool)275 JSHandle<JSFunction> ClassHelper::DefineClassTemplate(JSThread *thread, JSHandle<ClassInfoExtractor> &extractor,
276 const JSHandle<ConstantPool> &constantpool)
277 {
278 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
279
280 JSHandle<JSHClass> prototypeHClass(thread, extractor->GetPrototypeHClass());
281 JSHandle<JSObject> prototype = factory->NewJSObject(prototypeHClass);
282
283 JSHandle<JSHClass> constructorHClass(thread, extractor->GetConstructorHClass());
284 JSHandle<JSFunction> constructor = factory->NewJSFunctionByDynClass(extractor->GetConstructorMethod(),
285 constructorHClass,
286 FunctionKind::CLASS_CONSTRUCTOR);
287
288 // non-static
289 JSHandle<TaggedArray> nonStaticProperties(thread, extractor->GetNonStaticProperties());
290 nonStaticProperties->Set(thread, 0, constructor);
291
292 uint32_t nonStaticLength = nonStaticProperties->GetLength();
293 JSMutableHandle<JSTaggedValue> propValue(thread, JSTaggedValue::Undefined());
294
295 if (LIKELY(!prototypeHClass->IsDictionaryMode())) {
296 for (uint32_t index = 0; index < nonStaticLength; ++index) {
297 propValue.Update(nonStaticProperties->Get(index));
298 if (propValue->IsJSFunction()) {
299 JSHandle<JSFunction> propFunc = JSHandle<JSFunction>::Cast(propValue);
300 propFunc->SetHomeObject(thread, prototype);
301 propFunc->SetConstantPool(thread, constantpool);
302 }
303 prototype->SetPropertyInlinedProps(thread, index, propValue.GetTaggedValue());
304 }
305 } else {
306 JSHandle<TaggedArray> nonStaticKeys(thread, extractor->GetNonStaticKeys());
307 JSHandle<NameDictionary> dict = BuildDictionaryPropeties(thread, prototype, nonStaticKeys, nonStaticProperties,
308 ClassPropertyType::NON_STATIC, constantpool);
309 prototype->SetProperties(thread, dict);
310 }
311
312 // non-static elements
313 if (UNLIKELY(extractor->GetNonStaticWithElements())) {
314 JSHandle<TaggedArray> nonStaticElements(thread, extractor->GetNonStaticElements());
315 ClassHelper::HandleElementsProperties(thread, prototype, nonStaticElements, constantpool);
316 }
317
318 // static
319 JSHandle<TaggedArray> staticProperties(thread, extractor->GetStaticProperties());
320 uint32_t staticLength = staticProperties->GetLength();
321
322 if (LIKELY(!constructorHClass->IsDictionaryMode())) {
323 for (uint32_t index = 0; index < staticLength; ++index) {
324 propValue.Update(staticProperties->Get(index));
325 if (propValue->IsJSFunction()) {
326 JSHandle<JSFunction> propFunc = JSHandle<JSFunction>::Cast(propValue);
327 propFunc->SetHomeObject(thread, constructor);
328 propFunc->SetConstantPool(thread, constantpool);
329 }
330 JSHandle<JSObject>::Cast(constructor)->SetPropertyInlinedProps(thread, index, propValue.GetTaggedValue());
331 }
332 } else {
333 JSHandle<TaggedArray> staticKeys(thread, extractor->GetStaticKeys());
334 JSHandle<NameDictionary> dict = BuildDictionaryPropeties(thread, JSHandle<JSObject>(constructor), staticKeys,
335 staticProperties, ClassPropertyType::STATIC,
336 constantpool);
337 constructor->SetProperties(thread, dict);
338 }
339
340 // static elements
341 if (UNLIKELY(extractor->GetStaticWithElements())) {
342 JSHandle<TaggedArray> staticElements(thread, extractor->GetStaticElements());
343 ClassHelper::HandleElementsProperties(thread, JSHandle<JSObject>(constructor), staticElements, constantpool);
344 }
345
346 constructor->SetProtoOrDynClass(thread, prototype);
347
348 return constructor;
349 }
350
BuildDictionaryPropeties(JSThread * thread,const JSHandle<JSObject> & object,JSHandle<TaggedArray> & keys,JSHandle<TaggedArray> & properties,ClassPropertyType type,const JSHandle<ConstantPool> & constantpool)351 JSHandle<NameDictionary> ClassHelper::BuildDictionaryPropeties(JSThread *thread, const JSHandle<JSObject> &object,
352 JSHandle<TaggedArray> &keys,
353 JSHandle<TaggedArray> &properties,
354 ClassPropertyType type,
355 const JSHandle<ConstantPool> &constantpool)
356 {
357 uint32_t length = keys->GetLength();
358 ASSERT(length > PropertyAttributes::MAX_CAPACITY_OF_PROPERTIES);
359 ASSERT(keys->GetLength() == properties->GetLength());
360
361 JSMutableHandle<NameDictionary> dict(
362 thread, NameDictionary::Create(thread, NameDictionary::ComputeHashTableSize(length)));
363 JSMutableHandle<JSTaggedValue> propKey(thread, JSTaggedValue::Undefined());
364 JSMutableHandle<JSTaggedValue> propValue(thread, JSTaggedValue::Undefined());
365 for (uint32_t index = 0; index < length; index++) {
366 PropertyAttributes attributes;
367 if (type == ClassPropertyType::STATIC) {
368 switch (index) {
369 case ClassInfoExtractor::LENGTH_INDEX:
370 attributes = PropertyAttributes::Default(false, false, true);
371 break;
372 case ClassInfoExtractor::NAME_INDEX:
373 if (LIKELY(properties->Get(ClassInfoExtractor::NAME_INDEX).IsString())) {
374 attributes = PropertyAttributes::Default(false, false, true);
375 } else {
376 ASSERT(properties->Get(ClassInfoExtractor::NAME_INDEX).IsJSFunction());
377 attributes = PropertyAttributes::Default(true, false, true);
378 }
379 break;
380 case ClassInfoExtractor::PROTOTYPE_INDEX:
381 attributes = PropertyAttributes::DefaultAccessor(false, false, false);
382 break;
383 default:
384 attributes = PropertyAttributes::Default(true, false, true);
385 break;
386 }
387 } else {
388 attributes = PropertyAttributes::Default(true, false, true); // non-enumerable
389 }
390 propKey.Update(keys->Get(index));
391 propValue.Update(properties->Get(index));
392 if (propValue->IsJSFunction()) {
393 JSHandle<JSFunction> propFunc = JSHandle<JSFunction>::Cast(propValue);
394 propFunc->SetHomeObject(thread, object);
395 propFunc->SetConstantPool(thread, constantpool);
396 }
397 JSHandle<NameDictionary> newDict = NameDictionary::PutIfAbsent(thread, dict, propKey, propValue, attributes);
398 dict.Update(newDict);
399 }
400
401 return dict;
402 }
403
HandleElementsProperties(JSThread * thread,const JSHandle<JSObject> & object,JSHandle<TaggedArray> & elements,const JSHandle<ConstantPool> & constantpool)404 void ClassHelper::HandleElementsProperties(JSThread *thread, const JSHandle<JSObject> &object,
405 JSHandle<TaggedArray> &elements, const JSHandle<ConstantPool> &constantpool)
406 {
407 JSMutableHandle<JSTaggedValue> elementsKey(thread, JSTaggedValue::Undefined());
408 JSMutableHandle<JSTaggedValue> elementsValue(thread, JSTaggedValue::Undefined());
409 for (uint32_t index = 0; index < elements->GetLength(); index += 2) { // 2: key-value pair
410 elementsKey.Update(elements->Get(index));
411 elementsValue.Update(elements->Get(index + 1));
412 // class property attribute is not default, will transition to dictionary directly.
413 JSObject::DefinePropertyByLiteral(thread, object, elementsKey, elementsValue, true);
414
415 if (elementsValue->IsJSFunction()) {
416 JSHandle<JSFunction> elementsFunc = JSHandle<JSFunction>::Cast(elementsValue);
417 elementsFunc->SetHomeObject(thread, object);
418 elementsFunc->SetConstantPool(thread, constantpool);
419 }
420 }
421 }
422 } // namespace panda::ecmascript