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