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