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/base/error_helper.h"
17#include "ecmascript/base/builtins_base.h"
18#include "ecmascript/dfx/stackinfo/js_stackinfo.h"
19#include "ecmascript/interpreter/frame_handler.h"
20
21namespace panda::ecmascript::base {
22JSTaggedValue ErrorHelper::ErrorCommonToString(EcmaRuntimeCallInfo *argv, const ErrorType &errorType)
23{
24    ASSERT(argv);
25    JSThread *thread = argv->GetThread();
26    [[maybe_unused]] EcmaHandleScope handleScope(thread);
27
28    // 1. Let O be the this value.
29    // 2. If Type(O) is not Object, throw a TypeError exception
30    JSHandle<JSTaggedValue> thisValue = BuiltinsBase::GetThis(argv);
31    if (!thisValue->IsECMAObject()) {
32        THROW_TYPE_ERROR_AND_RETURN(thread, "ErrorToString:not an object", JSTaggedValue::Exception());
33    }
34    // 3. Let name be Get(O, "name").
35    // 4. ReturnIfAbrupt(name).
36    auto globalConst = thread->GlobalConstants();
37    JSHandle<JSTaggedValue> handleName = globalConst->GetHandledNameString();
38    JSHandle<JSTaggedValue> name = JSObject::GetProperty(thread, thisValue, handleName).GetValue();
39    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
40
41    // 5. If name is undefined, let name be "Error"; otherwise let name be ToString(name).
42    // 6. ReturnIfAbrupt(name).
43    name = ErrorHelper::GetErrorName(thread, name, errorType);
44    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
45
46    // 7. Let msg be Get(O, "message").
47    // 8. ReturnIfAbrupt(msg).
48    JSHandle<JSTaggedValue> handleMsg = globalConst->GetHandledMessageString();
49    JSHandle<JSTaggedValue> msg = JSObject::GetProperty(thread, thisValue, handleMsg).GetValue();
50    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
51
52    // 9. If msg is undefined, let msg be the empty String; otherwise let msg be ToString(msg).
53    // 10. ReturnIfAbrupt(msg).
54    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
55    if (msg->IsUndefined()) {
56        msg = JSHandle<JSTaggedValue>::Cast(factory->GetEmptyString());
57    } else {
58        msg = JSHandle<JSTaggedValue>::Cast(JSTaggedValue::ToString(thread, msg));
59    }
60    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
61
62    // 11. If name is the empty String, return msg.
63    // 12. If msg is the empty String, return name.
64    if (EcmaStringAccessor(JSHandle<EcmaString>::Cast(name)).GetLength() == 0) {
65        return msg.GetTaggedValue();
66    }
67    if (EcmaStringAccessor(JSHandle<EcmaString>::Cast(msg)).GetLength() == 0) {
68        return name.GetTaggedValue();
69    }
70
71    // 13. Return the result of concatenating name, the code unit 0x003A (COLON), the code unit 0x0020 (SPACE), and msg.
72    JSHandle<EcmaString> space = factory->NewFromASCII(": ");
73    JSHandle<EcmaString> jsHandleName = JSHandle<EcmaString>::Cast(name);
74    JSHandle<EcmaString> jsHandleMsg = JSHandle<EcmaString>::Cast(msg);
75    JSHandle<EcmaString> handleNameSpace = factory->ConcatFromString(jsHandleName, space);
76    JSHandle<EcmaString> result = factory->ConcatFromString(handleNameSpace, jsHandleMsg);
77    return result.GetTaggedValue();
78}
79
80JSHandle<JSTaggedValue> ErrorHelper::GetErrorName(JSThread *thread, const JSHandle<JSTaggedValue> &name,
81                                                  const ErrorType &errorType)
82{
83    auto globalConst = thread->GlobalConstants();
84    if (name->IsUndefined()) {
85        JSHandle<JSTaggedValue> errorKey;
86        switch (errorType) {
87            case ErrorType::RANGE_ERROR:
88                errorKey = globalConst->GetHandledRangeErrorString();
89                break;
90            case ErrorType::EVAL_ERROR:
91                errorKey = globalConst->GetHandledEvalErrorString();
92                break;
93            case ErrorType::REFERENCE_ERROR:
94                errorKey = globalConst->GetHandledReferenceErrorString();
95                break;
96            case ErrorType::TYPE_ERROR:
97                errorKey = globalConst->GetHandledTypeErrorString();
98                break;
99            case ErrorType::AGGREGATE_ERROR:
100                errorKey = globalConst->GetHandledAggregateErrorString();
101                break;
102            case ErrorType::URI_ERROR:
103                errorKey = globalConst->GetHandledURIErrorString();
104                break;
105            case ErrorType::SYNTAX_ERROR:
106                errorKey = globalConst->GetHandledSyntaxErrorString();
107                break;
108            case ErrorType::OOM_ERROR:
109                errorKey = globalConst->GetHandledOOMErrorString();
110                break;
111            case ErrorType::TERMINATION_ERROR:
112                errorKey = globalConst->GetHandledTerminationErrorString();
113                break;
114            default:
115                errorKey = globalConst->GetHandledErrorString();
116                break;
117        }
118        return errorKey;
119    }
120    return JSHandle<JSTaggedValue>::Cast(JSTaggedValue::ToString(thread, name));
121}
122
123JSTaggedValue ErrorHelper::ErrorCommonConstructor(EcmaRuntimeCallInfo *argv,
124                                                  [[maybe_unused]] const ErrorType &errorType)
125{
126    JSThread *thread = argv->GetThread();
127    [[maybe_unused]] EcmaHandleScope handleScope(thread);
128
129    // 1. If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget.
130    auto ecmaVm = thread->GetEcmaVM();
131    ObjectFactory *factory = ecmaVm->GetFactory();
132    JSHandle<JSTaggedValue> ctor = BuiltinsBase::GetConstructor(argv);
133    JSHandle<JSTaggedValue> newTarget = BuiltinsBase::GetNewTarget(argv);
134    if (newTarget->IsUndefined()) {
135        newTarget = ctor;
136    }
137    JSHandle<JSTaggedValue> message = BuiltinsBase::GetCallArg(argv, 0);
138
139    // 2. Let O be OrdinaryCreateFromConstructor(newTarget, "%ErrorPrototype%", «[[ErrorData]]»).
140    JSHandle<JSObject> nativeInstanceObj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(ctor), newTarget);
141
142    // 3. ReturnIfAbrupt(O).
143    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
144
145    // 4. If message is not undefined, then
146    //    a. Let msg be ToString(message).
147    //    b. ReturnIfAbrupt(msg).
148    //    c. Let msgDesc be the PropertyDescriptor{[[Value]]: msg, [[Writable]]: true, [[Enumerable]]: false,
149    //       [[Configurable]]: true}.
150    //    d. Let status be DefinePropertyOrThrow(O, "message", msgDesc).
151    //    e. Assert: status is not an abrupt completion
152    auto globalConst = thread->GlobalConstants();
153    if (!message->IsUndefined()) {
154        JSHandle<EcmaString> handleStr = JSTaggedValue::ToString(thread, message);
155        if (errorType != ErrorType::OOM_ERROR) {
156            LOG_ECMA(DEBUG) << "Throw error: " << EcmaStringAccessor(handleStr).ToCString();
157        }
158        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
159        JSHandle<JSTaggedValue> msgKey = globalConst->GetHandledMessageString();
160        PropertyDescriptor msgDesc(thread, JSHandle<JSTaggedValue>::Cast(handleStr), true, false, true);
161        [[maybe_unused]] bool status = JSObject::DefineOwnProperty(thread, nativeInstanceObj, msgKey, msgDesc);
162        ASSERT_PRINT(status == true, "return result exception!");
163    }
164    // InstallErrorCause
165    JSHandle<JSTaggedValue> options = BuiltinsBase::GetCallArg(argv, 1);
166    // If options is an Object and ? HasProperty(options, "cause") is true, then
167    //   a. Let cause be ? Get(options, "cause").
168    //   b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "cause", cause).
169    if (options->IsECMAObject()) {
170        JSHandle<JSTaggedValue> causeKey = globalConst->GetHandledCauseString();
171        bool causePresent = JSTaggedValue::HasProperty(thread, options, causeKey);
172        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
173        if (causePresent) {
174            JSHandle<JSTaggedValue> cause = JSObject::GetProperty(thread, options, causeKey).GetValue();
175            RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
176            PropertyDescriptor causeDesc(thread, cause, true, false, true);
177            [[maybe_unused]] bool status = JSObject::DefineOwnProperty(thread, nativeInstanceObj, causeKey, causeDesc);
178            ASSERT_PRINT(status == true, "return result exception!");
179        }
180    }
181    JSHandle<JSTaggedValue> errorFunc = GetErrorJSFunction(thread);
182    if (!errorFunc->IsUndefined()) {
183        JSHandle<JSTaggedValue> errorFunckey = globalConst->GetHandledErrorFuncString();
184        PropertyDescriptor errorFuncDesc(thread, errorFunc, true, false, true);
185        [[maybe_unused]] bool status = JSObject::DefineOwnProperty(thread,
186                                                                   nativeInstanceObj, errorFunckey, errorFuncDesc);
187        ASSERT_PRINT(status == true, "return result exception!");
188    }
189
190    std::string stack;
191    JSHandle<EcmaString> handleStack = BuildEcmaStackTrace(thread, stack, nativeInstanceObj);
192    JSHandle<JSTaggedValue> stackkey = globalConst->GetHandledStackString();
193    PropertyDescriptor stackDesc(thread, JSHandle<JSTaggedValue>::Cast(handleStack), true, false, true);
194    [[maybe_unused]] bool status = JSObject::DefineOwnProperty(thread, nativeInstanceObj, stackkey, stackDesc);
195    ASSERT_PRINT(status == true, "return result exception!");
196
197    // Uncaught exception parsing source code
198    JSHandle<JSTaggedValue> topStackkey = globalConst->GetHandledTopStackString();
199    PropertyDescriptor topStackDesc(thread, JSHandle<JSTaggedValue>::Cast(factory->NewFromStdString(stack)),
200                                                                          true, false, true);
201    [[maybe_unused]] bool topStackstatus = JSObject::DefineOwnProperty(thread, nativeInstanceObj,
202                                                                       topStackkey, topStackDesc);
203    ASSERT_PRINT(topStackstatus == true, "return result exception!");
204
205    // 5. Return O.
206    return nativeInstanceObj.GetTaggedValue();
207}
208
209JSHandle<JSTaggedValue> ErrorHelper::GetErrorJSFunction(JSThread *thread)
210{
211    FrameHandler frameHandler(thread);
212    for (; frameHandler.HasFrame(); frameHandler.PrevJSFrame()) {
213        if (!frameHandler.IsJSFrame()) {
214            continue;
215        }
216
217        auto function = frameHandler.GetFunction();
218        if (function.IsJSFunctionBase() || function.IsJSProxy()) {
219            Method *method = ECMAObject::Cast(function.GetTaggedObject())->GetCallTarget();
220            if (!method->IsNativeWithCallField()) {
221                return JSHandle<JSTaggedValue>(thread, function);
222            }
223        }
224    }
225    return thread->GlobalConstants()->GetHandledUndefined();
226}
227
228JSHandle<EcmaString> ErrorHelper::BuildEcmaStackTrace(JSThread *thread, std::string &stack,
229                                                      const JSHandle<JSObject> &jsErrorObj)
230{
231    std::string data = JsStackInfo::BuildJsStackTrace(thread, false, jsErrorObj);
232    if (data.size() > MAX_ERROR_SIZE) {
233        // find last line break from 0 to MAX_ERROR_SIZE
234        size_t pos = data.rfind('\n', MAX_ERROR_SIZE);
235        if (pos != std::string::npos) {
236            data = data.substr(0, pos);
237        }
238    }
239    LOG_ECMA(DEBUG) << data;
240    // unconverted stack
241    stack = data;
242    auto ecmaVm = thread->GetEcmaVM();
243    // sourceMap callback
244    auto sourceMapcb = ecmaVm->GetSourceMapCallback();
245    if (sourceMapcb != nullptr && !data.empty()) {
246        data = sourceMapcb(data.c_str());
247    }
248
249    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
250    return factory->NewFromStdString(data);
251}
252}  // namespace panda::ecmascript::base
253