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/builtins/builtins_function.h"
17
18 #include "ecmascript/ecma_vm.h"
19 #include "ecmascript/global_env.h"
20 #include "ecmascript/interpreter/interpreter.h"
21 #include "ecmascript/jspandafile/js_pandafile_manager.h"
22 #include "ecmascript/js_arguments.h"
23 #include "ecmascript/js_stable_array.h"
24 #include "ecmascript/tagged_array-inl.h"
25
26 namespace panda::ecmascript::builtins {
27 // ecma 19.2.1 Function (p1, p2, ... , pn, body)
FunctionConstructor(EcmaRuntimeCallInfo * argv)28 JSTaggedValue BuiltinsFunction::FunctionConstructor(EcmaRuntimeCallInfo *argv)
29 {
30 // not support
31 JSThread *thread = argv->GetThread();
32 [[maybe_unused]] EcmaHandleScope handleScope(thread);
33 THROW_TYPE_ERROR_AND_RETURN(thread, "Not support eval. Forbidden using new Function()/Function().",
34 JSTaggedValue::Exception());
35 }
36
37 // ecma 19.2.3 The Function prototype object is itself a built-in function object.
38 // When invoked, it accepts any arguments and returns undefined.
FunctionPrototypeInvokeSelf(EcmaRuntimeCallInfo * argv)39 JSTaggedValue BuiltinsFunction::FunctionPrototypeInvokeSelf([[maybe_unused]] EcmaRuntimeCallInfo *argv)
40 {
41 return JSTaggedValue::Undefined();
42 }
43 namespace {
MakeArgListWithHole(JSThread * thread,TaggedArray * argv,size_t length)44 static size_t MakeArgListWithHole(JSThread *thread, TaggedArray *argv, size_t length)
45 {
46 if (length > argv->GetLength()) {
47 length = argv->GetLength();
48 }
49 for (size_t index = 0; index < length; ++index) {
50 JSTaggedValue value = argv->Get(thread, index);
51 if (value.IsHole()) {
52 argv->Set(thread, index, JSTaggedValue::Undefined());
53 }
54 }
55 return length;
56 }
57
BuildArgumentsListFast(JSThread * thread,const JSHandle<JSTaggedValue> & arrayObj)58 static std::pair<TaggedArray*, size_t> BuildArgumentsListFast(JSThread *thread,
59 const JSHandle<JSTaggedValue> &arrayObj)
60 {
61 if (!arrayObj->HasStableElements(thread)) {
62 return std::make_pair(nullptr, 0);
63 }
64 if (arrayObj->IsStableJSArguments(thread)) {
65 JSHandle<JSArguments> argList = JSHandle<JSArguments>::Cast(arrayObj);
66 TaggedArray *elements = TaggedArray::Cast(argList->GetElements().GetTaggedObject());
67 auto env = thread->GetEcmaVM()->GetGlobalEnv();
68 if (argList->GetClass() != env->GetArgumentsClass().GetObject<JSHClass>()) {
69 return std::make_pair(nullptr, 0);
70 }
71 auto result = argList->GetPropertyInlinedProps(JSArguments::LENGTH_INLINE_PROPERTY_INDEX);
72 if (!result.IsInt()) {
73 return std::make_pair(nullptr, 0);
74 }
75 size_t length = static_cast<size_t>(result.GetInt());
76 size_t res = MakeArgListWithHole(thread, elements, length);
77 return std::make_pair(elements, res);
78 } else if (arrayObj->IsStableJSArray(thread)) {
79 JSHandle<JSArray> argList = JSHandle<JSArray>::Cast(arrayObj);
80 TaggedArray *elements = TaggedArray::Cast(argList->GetElements().GetTaggedObject());
81 size_t length = argList->GetArrayLength();
82 if (elements->GetLength() == 0 && length != 0) {
83 JSHandle<TaggedArray> array =
84 thread->GetEcmaVM()->GetFactory()->NewTaggedArray(length, JSTaggedValue::Undefined());
85 return std::make_pair(*array, length);
86 }
87 size_t res = MakeArgListWithHole(thread, elements, length);
88 return std::make_pair(elements, res);
89 } else {
90 UNREACHABLE();
91 }
92 }
93 } // anonymous namespace
94
95 // ecma 19.2.3.1 Function.prototype.apply (thisArg, argArray)
FunctionPrototypeApply(EcmaRuntimeCallInfo * argv)96 JSTaggedValue BuiltinsFunction::FunctionPrototypeApply(EcmaRuntimeCallInfo *argv)
97 {
98 ASSERT(argv);
99 BUILTINS_API_TRACE(argv->GetThread(), Function, PrototypeApply);
100 JSThread *thread = argv->GetThread();
101 [[maybe_unused]] EcmaHandleScope handleScope(thread);
102
103 // 1. If IsCallable(func) is false, throw a TypeError exception.
104 if (!GetThis(argv)->IsCallable()) {
105 THROW_TYPE_ERROR_AND_RETURN(thread, "apply target is not callable", JSTaggedValue::Exception());
106 }
107
108 JSHandle<JSTaggedValue> func = GetThis(argv);
109 JSHandle<JSTaggedValue> thisArg = GetCallArg(argv, 0);
110 JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
111 // 2. If argArray is null or undefined, then
112 if (GetCallArg(argv, 1)->IsUndefined()) { // null will also get undefined
113 // a. Return Call(func, thisArg).
114 EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, func, thisArg, undefined, 0);
115 return JSFunction::Call(info);
116 }
117 // 3. Let argList be CreateListFromArrayLike(argArray).
118 JSHandle<JSTaggedValue> arrayObj = GetCallArg(argv, 1);
119 std::pair<TaggedArray*, size_t> argumentsList = BuildArgumentsListFast(thread, arrayObj);
120 if (!argumentsList.first) {
121 JSHandle<TaggedArray> argList = JSHandle<TaggedArray>::Cast(
122 JSObject::CreateListFromArrayLike(thread, arrayObj));
123 // 4. ReturnIfAbrupt(argList).
124 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
125 const int32_t argsLength = static_cast<int32_t>(argList->GetLength());
126 EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, func, thisArg, undefined, argsLength);
127 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
128 info->SetCallArg(argsLength, argList);
129 return JSFunction::Call(info);
130 }
131 // 6. Return Call(func, thisArg, argList).
132 const int32_t argsLength = static_cast<int32_t>(argumentsList.second);
133 EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, func, thisArg, undefined, argsLength);
134 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
135 info->SetCallArg(argsLength, argumentsList.first);
136 return JSFunction::Call(info);
137 }
138
139 // ecma 19.2.3.2 Function.prototype.bind (thisArg , ...args)
FunctionPrototypeBind(EcmaRuntimeCallInfo * argv)140 JSTaggedValue BuiltinsFunction::FunctionPrototypeBind(EcmaRuntimeCallInfo *argv)
141 {
142 ASSERT(argv);
143 BUILTINS_API_TRACE(argv->GetThread(), Function, PrototypeBind);
144 JSThread *thread = argv->GetThread();
145 [[maybe_unused]] EcmaHandleScope handleScope(thread);
146
147 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
148 // 1. Let Target be the this value.
149 JSHandle<JSTaggedValue> target = GetThis(argv);
150 // 2. If IsCallable(Target) is false, throw a TypeError exception.
151 if (!target->IsCallable()) {
152 THROW_TYPE_ERROR_AND_RETURN(thread, "bind target is not callable", JSTaggedValue::Exception());
153 }
154
155 JSHandle<JSTaggedValue> thisArg = GetCallArg(argv, 0);
156 uint32_t argsLength = 0;
157 if (argv->GetArgsNumber() > 1) {
158 argsLength = argv->GetArgsNumber() - 1;
159 }
160
161 // 3. Let args be a new (possibly empty) List consisting of all of the argument
162 // values provided after thisArg in order.
163 JSHandle<TaggedArray> argsArray = factory->NewTaggedArray(argsLength);
164 for (uint32_t index = 0; index < argsLength; ++index) {
165 argsArray->Set(thread, index, GetCallArg(argv, index + 1));
166 }
167 // 4. Let F be BoundFunctionCreate(Target, thisArg, args).
168 JSHandle<JSFunctionBase> targetFunction = JSHandle<JSFunctionBase>::Cast(target);
169 JSHandle<JSBoundFunction> boundFunction = factory->NewJSBoundFunction(targetFunction, thisArg, argsArray);
170 // 5. ReturnIfAbrupt(F)
171 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
172
173 // 6. Let targetHasLength be HasOwnProperty(Target, "length").
174 auto globalConst = thread->GlobalConstants();
175 JSHandle<JSTaggedValue> lengthKey = globalConst->GetHandledLengthString();
176 bool targetHasLength =
177 JSTaggedValue::HasOwnProperty(thread, JSHandle<JSTaggedValue>::Cast(targetFunction), lengthKey);
178 // 7. ReturnIfAbrupt(targetHasLength).
179 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
180
181 double lengthValue = 0.0;
182 // 8. If targetHasLength is true, then
183 if (targetHasLength) {
184 // a. Let targetLen be Get(Target, "length").
185 JSHandle<JSTaggedValue> targetLen = JSObject::GetProperty(thread, target, lengthKey).GetValue();
186 // b. ReturnIfAbrupt(targetLen).
187 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
188
189 // c. If Type(targetLen) is not Number, let L be 0.
190 // d. Else,
191 // i. Let targetLen be ToInteger(targetLen).
192 // ii. Let L be the larger of 0 and the result of targetLen minus the number of elements of args.
193 if (targetLen->IsNumber()) {
194 // argv include thisArg
195 lengthValue =
196 std::max(0.0, JSTaggedValue::ToNumber(thread, targetLen).GetNumber() - static_cast<double>(argsLength));
197 }
198 }
199 // 9. Else let L be 0.
200
201 // 10. Let status be DefinePropertyOrThrow(F, "length", PropertyDescriptor {[[Value]]: L,
202 // [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true}).
203 PropertyDescriptor desc(thread, JSHandle<JSTaggedValue>(thread, JSTaggedValue(lengthValue)), false, false, true);
204 [[maybe_unused]] bool status =
205 JSTaggedValue::DefinePropertyOrThrow(thread, JSHandle<JSTaggedValue>(boundFunction), lengthKey, desc);
206 // 11. Assert: status is not an abrupt completion.
207 ASSERT_PRINT(status, "DefinePropertyOrThrow failed");
208
209 // 12. Let targetName be Get(Target, "name").
210 JSHandle<JSTaggedValue> nameKey = globalConst->GetHandledNameString();
211 JSHandle<JSTaggedValue> targetName = JSObject::GetProperty(thread, target, nameKey).GetValue();
212 // 13. ReturnIfAbrupt(targetName).
213 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
214
215 JSHandle<JSTaggedValue> boundName = thread->GlobalConstants()->GetHandledBoundString();
216 // 14. If Type(targetName) is not String, let targetName be the empty string.
217 // 15. Perform SetFunctionName(F, targetName, "bound").
218 if (!targetName->IsString()) {
219 JSHandle<JSTaggedValue> emptyString(factory->GetEmptyString());
220 status = JSFunction::SetFunctionName(thread, JSHandle<JSFunctionBase>(boundFunction), emptyString, boundName);
221 } else {
222 status = JSFunction::SetFunctionName(thread, JSHandle<JSFunctionBase>(boundFunction), targetName, boundName);
223 }
224 // Assert: status is not an abrupt completion.
225 ASSERT_PRINT(status, "SetFunctionName failed");
226
227 // 16. Return F.
228 return boundFunction.GetTaggedValue();
229 }
230
231 // ecma 19.2.3.3 Function.prototype.call (thisArg , ...args)
FunctionPrototypeCall(EcmaRuntimeCallInfo * argv)232 JSTaggedValue BuiltinsFunction::FunctionPrototypeCall(EcmaRuntimeCallInfo *argv)
233 {
234 ASSERT(argv);
235 BUILTINS_API_TRACE(argv->GetThread(), Function, PrototypeCall);
236 JSThread *thread = argv->GetThread();
237 [[maybe_unused]] EcmaHandleScope handleScope(thread);
238
239 // 1. If IsCallable(func) is false, throw a TypeError exception.
240 if (!GetThis(argv)->IsCallable()) {
241 THROW_TYPE_ERROR_AND_RETURN(thread, "call target is not callable", JSTaggedValue::Exception());
242 }
243
244 JSHandle<JSTaggedValue> func = GetThis(argv);
245 JSHandle<JSTaggedValue> thisArg = GetCallArg(argv, 0);
246 uint32_t argsLength = 0;
247 if (argv->GetArgsNumber() > 1) {
248 argsLength = argv->GetArgsNumber() - 1;
249 }
250 // 2. Let argList be an empty List.
251 // 3. If this method was called with more than one argument then in left to right order,
252 // starting with the second argument, append each argument as the last element of argList.
253 // 5. Return Call(func, thisArg, argList).
254 JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
255 EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, func, thisArg, undefined, argsLength);
256 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
257 info->SetCallArg(argsLength, 0, argv, 1);
258 return JSFunction::Call(info);
259 }
260
261 // ecma 19.2.3.5 Function.prototype.toString ()
FunctionPrototypeToString(EcmaRuntimeCallInfo * argv)262 JSTaggedValue BuiltinsFunction::FunctionPrototypeToString(EcmaRuntimeCallInfo *argv)
263 {
264 BUILTINS_API_TRACE(argv->GetThread(), Function, PrototypeToString);
265 JSThread *thread = argv->GetThread();
266 [[maybe_unused]] EcmaHandleScope handleScope(thread);
267 JSHandle<JSTaggedValue> thisValue = GetThis(argv);
268 if (thisValue->IsJSObject() && thisValue->IsCallable()) {
269 JSHandle<JSFunction> func = JSHandle<JSFunction>::Cast(thisValue);
270 JSHandle<Method> method(thread, func->GetMethod());
271 if (method->IsNativeWithCallField()) {
272 JSHandle<JSTaggedValue> nameKey = thread->GlobalConstants()->GetHandledNameString();
273 JSHandle<EcmaString> methodName(JSObject::GetProperty(thread, thisValue, nameKey).GetValue());
274 std::string nameStr = EcmaStringAccessor(methodName).ToStdString();
275 std::string startStr = "function ";
276 std::string endStr = "() { [native code] }";
277 startStr.append(nameStr).append(endStr);
278 return GetTaggedString(thread, startStr.c_str());
279 }
280 DebugInfoExtractor *debugExtractor =
281 JSPandaFileManager::GetInstance()->GetJSPtExtractor(method->GetJSPandaFile());
282 const std::string &sourceCode = debugExtractor->GetSourceCode(method->GetMethodId());
283 if (!sourceCode.empty()) {
284 return GetTaggedString(thread, sourceCode.c_str());
285 } else {
286 return GetTaggedString(thread, "Cannot get source code of funtion");
287 }
288 }
289
290 THROW_TYPE_ERROR_AND_RETURN(thread,
291 "function.toString() target is incompatible object", JSTaggedValue::Exception());
292 }
293
294 // ecma 19.2.3.6 Function.prototype[@@hasInstance] (V)
FunctionPrototypeHasInstance(EcmaRuntimeCallInfo * argv)295 JSTaggedValue BuiltinsFunction::FunctionPrototypeHasInstance(EcmaRuntimeCallInfo *argv)
296 {
297 BUILTINS_API_TRACE(argv->GetThread(), Function, PrototypeHasInstance);
298 [[maybe_unused]] EcmaHandleScope handleScope(argv->GetThread());
299 // 1. Let F be the this value.
300 JSHandle<JSTaggedValue> thisValue = GetThis(argv);
301 // 2. Return OrdinaryHasInstance(F, V).
302 JSHandle<JSTaggedValue> arg = GetCallArg(argv, 0);
303 return JSFunction::OrdinaryHasInstance(argv->GetThread(), thisValue, arg) ? GetTaggedBoolean(true)
304 : GetTaggedBoolean(false);
305 }
306 } // namespace panda::ecmascript::builtins
307