1 /*
2 * Copyright (c) 2025 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/global_env.h"
17 #include "ecmascript/js_function.h"
18 #include "ecmascript/js_object-inl.h"
19 #include "ecmascript/layout_info.h"
20 #include "ecmascript/napi/include/jsnapi_expo.h"
21 #include "ecmascript/napi/jsnapi_class_creation_helper.h"
22 #include "ecmascript/napi/jsnapi_helper.h"
23 #include "ecmascript/object_factory.h"
24
25 namespace panda::ecmascript {
ConstructDescByAttr(const JSThread * thread,const PropertyAttribute & attr,PropertyDescriptor * desc)26 void JSNApiClassCreationHelper::ConstructDescByAttr(const JSThread *thread, const PropertyAttribute &attr,
27 PropertyDescriptor *desc)
28 {
29 new (desc) PropertyDescriptor(thread, JSNApiHelper::ToJSHandle(attr.GetValue(thread->GetEcmaVM())),
30 attr.IsWritable(), attr.IsEnumerable(), attr.IsConfigurable());
31 }
32
DestructDesc(PropertyDescriptor * desc)33 void JSNApiClassCreationHelper::DestructDesc(PropertyDescriptor *desc)
34 {
35 desc->~PropertyDescriptor();
36 }
37
DestructAttr(PropertyAttribute * attr)38 void JSNApiClassCreationHelper::DestructAttr(PropertyAttribute *attr)
39 {
40 // Call ~PropertyAttribute directly for attrs from napi_define_class, which were construct by using placement new.
41 attr->~PropertyAttribute();
42 }
43
TryAddOriKeyAndOriAttrToHClass(const JSThread * thread,const Local<JSValueRef> & oriKey,const PropertyAttribute & oriAttr,PropertyDescriptor & desc,size_t & attrOffset,JSHandle<JSHClass> & hClass)44 bool JSNApiClassCreationHelper::TryAddOriKeyAndOriAttrToHClass(const JSThread *thread, const Local<JSValueRef> &oriKey,
45 const PropertyAttribute &oriAttr,
46 PropertyDescriptor &desc, size_t &attrOffset,
47 JSHandle<JSHClass> &hClass)
48 {
49 // If return false, it means that property must be add by slow path(DefinePropertyOrThrow).
50 JSMutableHandle<JSTaggedValue> key(JSNApiHelper::ToJSMutableHandle(oriKey));
51 JSTaggedValue keyValue = key.GetTaggedValue();
52 EcmaVM *vm = thread->GetEcmaVM();
53 if (key->IsString() && !EcmaStringAccessor(keyValue).IsInternString()) {
54 // update string stable
55 key.Update(JSTaggedValue(vm->GetFactory()->InternString(key)));
56 }
57 ConstructDescByAttr(thread, oriAttr, &desc);
58 // If property key is element index, property must be add by slow path(DefinePropertyOrThrow).
59 if (UNLIKELY(!JSTaggedValue::IsPureString(const_cast<JSThread *>(thread), keyValue))) {
60 return false;
61 }
62 // If property key is repeat key, property must be add by slow path.
63 JSHandle<ecmascript::LayoutInfo> layoutInfoHandle(thread, hClass->GetLayout(thread));
64 if (UNLIKELY(layoutInfoHandle->CheckIsDuplicateKey(thread, attrOffset, key->GetKeyHashCode(thread), keyValue))) {
65 return false;
66 }
67 ASSERT(static_cast<int32_t>(attrOffset) == hClass->GetNextInlinedPropsIndex());
68 JSHClass::AddInlinedPropToHClass(thread, desc, attrOffset++, key, hClass);
69 return true;
70 }
71
NewClassFuncProtoWithProperties(JSThread * thread,const JSHandle<JSFunction> & func,size_t propertyCount,size_t nonStaticPropCount,const Local<JSValueRef> * keys,PropertyAttribute * attrs,PropertyDescriptor * descs)72 JSHandle<JSObject> JSNApiClassCreationHelper::NewClassFuncProtoWithProperties(
73 JSThread *thread, const JSHandle<JSFunction> &func, size_t propertyCount, size_t nonStaticPropCount,
74 const Local<JSValueRef> *keys, PropertyAttribute *attrs, PropertyDescriptor *descs)
75 {
76 ASSERT(propertyCount >= nonStaticPropCount);
77 JSHandle<GlobalEnv> env = thread->GetGlobalEnv();
78 size_t inlNonStaticPropCount =
79 std::min(nonStaticPropCount, static_cast<size_t>(ecmascript::PropertyAttributes::MAX_FAST_PROPS_CAPACITY));
80 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
81 JSHandle<JSHClass> classPrototypeHClass = factory->CreateClassFuncProtoHClass(thread, inlNonStaticPropCount);
82 // Step1: create hClass of protoOrHClass in class function
83 // Set "constructor" in prototype
84 PropertyDescriptor ctorDesc(thread, JSHandle<JSTaggedValue>::Cast(func), true, false, true);
85 ASSERT(classPrototypeHClass->GetNextInlinedPropsIndex() != -1);
86 const size_t startInlPropIdx = static_cast<size_t>(classPrototypeHClass->GetNextInlinedPropsIndex());
87 JSHandle<JSTaggedValue> ctorKey = thread->GlobalConstants()->GetHandledConstructorString();
88 JSHClass::AddInlinedPropToHClass(thread, ctorDesc, startInlPropIdx, ctorKey, classPrototypeHClass);
89 // Set inlined property slot of non-static properties
90 const size_t nonStaticPropStartIdx = propertyCount - 1; // 1: array length of keys and attrs is propertyCount
91 std::bitset<ecmascript::PropertyAttributes::MAX_FAST_PROPS_CAPACITY> propTags;
92 if (inlNonStaticPropCount > 0) {
93 ASSERT(propertyCount >= inlNonStaticPropCount);
94 size_t nextInlinedPropIdx = startInlPropIdx + 1; // 1: constructor attr set first
95 // Properties in keys and attrs are stored in the following way:
96 // +------------------+ ... + -------------------------------------+ ... +---------------------+
97 // | first staticProp | ... | last staticProp | last nonStaticProp | ... | first nonStaticProp |
98 // +------------------+ ... + -------------------------------------+ ... +---------------------+
99 for (size_t i = 0; i < inlNonStaticPropCount; ++i) {
100 size_t curNonStaticPropIdx = nonStaticPropStartIdx - i;
101 propTags[curNonStaticPropIdx] =
102 TryAddOriKeyAndOriAttrToHClass(thread, keys[curNonStaticPropIdx], attrs[curNonStaticPropIdx],
103 descs[curNonStaticPropIdx], nextInlinedPropIdx, classPrototypeHClass);
104 }
105 }
106 // Step2: create protoOrHClass object in terms of the created hclass
107 JSHandle<JSObject> classPrototype = factory->NewJSObjectWithInit(classPrototypeHClass);
108 // Step3: set values of inlined properties
109 size_t curInlPropIdx = startInlPropIdx;
110 classPrototype->SetPropertyInlinedProps<true>(thread, curInlPropIdx++, ctorDesc.GetValue().GetTaggedValue());
111 if (inlNonStaticPropCount > 0) {
112 JSHandle<JSTaggedValue> classPrototypeHandle(classPrototype);
113 for (size_t i = 0; i < inlNonStaticPropCount; ++i) {
114 size_t curNonStaticPropIdx = nonStaticPropStartIdx - i;
115 if (propTags[curNonStaticPropIdx]) {
116 classPrototype->SetPropertyInlinedProps<true>(thread, curInlPropIdx++,
117 descs[curNonStaticPropIdx].GetValue().GetTaggedValue());
118 } else {
119 JSTaggedValue::DefinePropertyOrThrow(thread, classPrototypeHandle,
120 JSNApiHelper::ToJSMutableHandle(keys[curNonStaticPropIdx]),
121 descs[curNonStaticPropIdx]);
122 }
123 // Corresponds to ConstructDescByAttr in TryAddOriKeyAndOriAttrToHClass
124 DestructDesc(&descs[curNonStaticPropIdx]);
125 DestructAttr(&attrs[curNonStaticPropIdx]);
126 }
127 }
128 // Add non-static properties that out bound of MAX_FAST_PROPS_CAPACITY
129 if (nonStaticPropCount > ecmascript::PropertyAttributes::MAX_FAST_PROPS_CAPACITY) {
130 JSHandle<JSTaggedValue> classPrototypeHandle(classPrototype);
131 for (size_t i = inlNonStaticPropCount; i < nonStaticPropCount; ++i) {
132 size_t curNonStaticPropIdx = nonStaticPropStartIdx - i;
133 ConstructDescByAttr(thread, attrs[curNonStaticPropIdx], &descs[curNonStaticPropIdx]);
134 JSTaggedValue::DefinePropertyOrThrow(thread, classPrototypeHandle,
135 JSNApiHelper::ToJSMutableHandle(keys[curNonStaticPropIdx]),
136 descs[curNonStaticPropIdx]);
137 DestructDesc(&descs[curNonStaticPropIdx]);
138 DestructAttr(&attrs[curNonStaticPropIdx]);
139 }
140 }
141 return classPrototype;
142 }
143
CreateClassFuncWithProperties(JSThread * thread,const char * name,InternalFunctionCallback nativeFunc,bool callNapi,size_t propertyCount,size_t staticPropCount,const Local<JSValueRef> * keys,PropertyAttribute * attrs,PropertyDescriptor * descs)144 JSHandle<JSFunction> JSNApiClassCreationHelper::CreateClassFuncWithProperties(
145 JSThread *thread, const char *name, InternalFunctionCallback nativeFunc, bool callNapi, size_t propertyCount,
146 size_t staticPropCount, const Local<JSValueRef> *keys, PropertyAttribute *attrs, PropertyDescriptor *descs)
147 {
148 ASSERT(propertyCount >= staticPropCount);
149 size_t inlinedStaticPropCount =
150 std::min(staticPropCount, static_cast<size_t>(ecmascript::PropertyAttributes::MAX_FAST_PROPS_CAPACITY));
151 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
152 JSHandle<JSHClass> functionClass = factory->CreateClassFuncHClass(thread, inlinedStaticPropCount);
153 // Step1: create hClass of class function
154 // Set name of class function
155 JSHandle<JSTaggedValue> functionName(factory->NewFromUtf8(name));
156 PropertyDescriptor nameDesc(thread, functionName, false, false, true);
157 ASSERT(functionClass->GetNextInlinedPropsIndex() != -1);
158 const size_t startInlPropIdx = static_cast<size_t>(functionClass->GetNextInlinedPropsIndex());
159 JSHandle<JSTaggedValue> nameKey = thread->GlobalConstants()->GetHandledNameString();
160 JSHClass::AddInlinedPropToHClass(thread, nameDesc, startInlPropIdx, nameKey, functionClass);
161 // Set static properties of class function
162 std::bitset<ecmascript::PropertyAttributes::MAX_FAST_PROPS_CAPACITY> propTags;
163 size_t nextInlinedPropIdx = startInlPropIdx + 1; // 1: first static property was set after class function name
164 // Properties in keys and attrs are stored in the following way:
165 // +------------------+ ... + -------------------------------------+ ... +---------------------+
166 // | first staticProp | ... | last staticProp | last nonStaticProp | ... | first nonStaticProp |
167 // +------------------+ ... + -------------------------------------+ ... +---------------------+
168 for (size_t i = 0; i < inlinedStaticPropCount; ++i) {
169 propTags[i] =
170 TryAddOriKeyAndOriAttrToHClass(thread, keys[i], attrs[i], descs[i], nextInlinedPropIdx, functionClass);
171 }
172 // Step2: create object of class function
173 JSHandle<JSFunction> classFunc = factory->NewJSFunctionByHClass(reinterpret_cast<void *>(nativeFunc), functionClass,
174 ecmascript::FunctionKind::CLASS_CONSTRUCTOR);
175 // Step3: set values of inlined properties
176 size_t curInlPropIdx = startInlPropIdx;
177 classFunc->SetPropertyInlinedProps<true>(thread, curInlPropIdx++, nameDesc.GetValue().GetTaggedValue());
178 JSHandle<JSTaggedValue> classFuncHandle(classFunc);
179 for (size_t i = 0; i < inlinedStaticPropCount; ++i) {
180 if (propTags[i]) {
181 classFunc->SetPropertyInlinedProps<true>(thread, curInlPropIdx++,
182 descs[i].GetValue().GetTaggedValue());
183 } else {
184 JSTaggedValue::DefinePropertyOrThrow(thread, classFuncHandle, JSNApiHelper::ToJSMutableHandle(keys[i]),
185 descs[i]);
186 }
187 DestructDesc(&descs[i]); // Corresponds to ConstructDescByAttr in TryAddOriKeyAndOriAttrToHClass
188 DestructAttr(&attrs[i]);
189 }
190 // Add static properties that out bound of MAX_FAST_PROPS_CAPACITY
191 for (size_t i = inlinedStaticPropCount; i < staticPropCount; ++i) {
192 ConstructDescByAttr(thread, attrs[i], &descs[i]);
193 JSTaggedValue::DefinePropertyOrThrow(thread, classFuncHandle, JSNApiHelper::ToJSMutableHandle(keys[i]),
194 descs[i]);
195 DestructDesc(&descs[i]);
196 DestructAttr(&attrs[i]);
197 }
198
199 JSHandle<JSObject> clsPrototype = NewClassFuncProtoWithProperties(
200 thread, classFunc, propertyCount, propertyCount - staticPropCount, keys, attrs, descs);
201 JSFunction::InitClassFunctionWithClsPrototype(thread, classFunc, callNapi, clsPrototype);
202
203 return classFunc;
204 }
205 } // namespace panda::ecmascript
206