* Copyright (c) 2021 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ecmascript/base/error_helper.h"
#include "ecmascript/base/builtins_base.h"
#include "ecmascript/dfx/stackinfo/js_stackinfo.h"
#include "ecmascript/interpreter/frame_handler.h"
#include "ecmascript/platform/log.h"
#include "ecmascript/module/js_module_manager.h"
#include "ecmascript/dfx/stackinfo/async_stack_trace.h"
namespace panda::ecmascript::base {
JSTaggedValue ErrorHelper::ErrorCommonToString(EcmaRuntimeCallInfo *argv, const ErrorType &errorType)
{
ASSERT(argv);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisValue = BuiltinsBase::GetThis(argv);
if (!thisValue->IsECMAObject()) {
THROW_TYPE_ERROR_AND_RETURN(thread, "ErrorToString:not an object", JSTaggedValue::Exception());
}
auto globalConst = thread->GlobalConstants();
JSHandle<JSTaggedValue> handleName = globalConst->GetHandledNameString();
JSHandle<JSTaggedValue> name = JSObject::GetProperty(thread, thisValue, handleName).GetValue();
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
name = ErrorHelper::GetErrorName(thread, name, errorType);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> handleMsg = globalConst->GetHandledMessageString();
JSHandle<JSTaggedValue> msg = JSObject::GetProperty(thread, thisValue, handleMsg).GetValue();
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
if (msg->IsUndefined()) {
msg = JSHandle<JSTaggedValue>::Cast(factory->GetEmptyString());
} else {
msg = JSHandle<JSTaggedValue>::Cast(JSTaggedValue::ToString(thread, msg));
}
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (EcmaStringAccessor(JSHandle<EcmaString>::Cast(name)).GetLength() == 0) {
return msg.GetTaggedValue();
}
if (EcmaStringAccessor(JSHandle<EcmaString>::Cast(msg)).GetLength() == 0) {
return name.GetTaggedValue();
}
JSHandle<EcmaString> space = factory->NewFromASCII(": ");
JSHandle<EcmaString> jsHandleName = JSHandle<EcmaString>::Cast(name);
JSHandle<EcmaString> jsHandleMsg = JSHandle<EcmaString>::Cast(msg);
JSHandle<EcmaString> handleNameSpace = factory->ConcatFromString(jsHandleName, space);
JSHandle<EcmaString> result = factory->ConcatFromString(handleNameSpace, jsHandleMsg);
return result.GetTaggedValue();
}
JSHandle<JSTaggedValue> ErrorHelper::GetErrorName(JSThread *thread, const JSHandle<JSTaggedValue> &name,
const ErrorType &errorType)
{
auto globalConst = thread->GlobalConstants();
if (name->IsUndefined()) {
JSHandle<JSTaggedValue> errorKey;
switch (errorType) {
case ErrorType::RANGE_ERROR:
errorKey = globalConst->GetHandledRangeErrorString();
break;
case ErrorType::EVAL_ERROR:
errorKey = globalConst->GetHandledEvalErrorString();
break;
case ErrorType::REFERENCE_ERROR:
errorKey = globalConst->GetHandledReferenceErrorString();
break;
case ErrorType::TYPE_ERROR:
errorKey = globalConst->GetHandledTypeErrorString();
break;
case ErrorType::AGGREGATE_ERROR:
errorKey = globalConst->GetHandledAggregateErrorString();
break;
case ErrorType::URI_ERROR:
errorKey = globalConst->GetHandledURIErrorString();
break;
case ErrorType::SYNTAX_ERROR:
errorKey = globalConst->GetHandledSyntaxErrorString();
break;
case ErrorType::OOM_ERROR:
errorKey = globalConst->GetHandledOOMErrorString();
break;
case ErrorType::TERMINATION_ERROR:
errorKey = globalConst->GetHandledTerminationErrorString();
break;
default:
errorKey = globalConst->GetHandledErrorString();
break;
}
return errorKey;
}
return JSHandle<JSTaggedValue>::Cast(JSTaggedValue::ToString(thread, name));
}
JSTaggedValue ErrorHelper::ErrorCommonConstructor(EcmaRuntimeCallInfo *argv,
[[maybe_unused]] const ErrorType &errorType)
{
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
auto ecmaVm = thread->GetEcmaVM();
ObjectFactory *factory = ecmaVm->GetFactory();
JSHandle<JSTaggedValue> ctor = BuiltinsBase::GetConstructor(argv);
JSHandle<JSTaggedValue> newTarget = BuiltinsBase::GetNewTarget(argv);
if (newTarget->IsUndefined()) {
newTarget = ctor;
}
JSHandle<JSTaggedValue> message = BuiltinsBase::GetCallArg(argv, 0);
JSHandle<JSObject> nativeInstanceObj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(ctor), newTarget);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
auto globalConst = thread->GlobalConstants();
if (!message->IsUndefined()) {
JSHandle<EcmaString> handleStr = JSTaggedValue::ToString(thread, message);
if (errorType != ErrorType::OOM_ERROR) {
LOG_ECMA(DEBUG) << "Throw error: " << EcmaStringAccessor(handleStr).ToCString(thread);
}
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> msgKey = globalConst->GetHandledMessageString();
PropertyDescriptor msgDesc(thread, JSHandle<JSTaggedValue>::Cast(handleStr), true, false, true);
[[maybe_unused]] bool status = JSObject::DefineOwnProperty(thread, nativeInstanceObj, msgKey, msgDesc);
ASSERT_PRINT(status == true, "return result exception!");
}
JSHandle<JSTaggedValue> options = BuiltinsBase::GetCallArg(argv, 1);
if (options->IsECMAObject()) {
JSHandle<JSTaggedValue> causeKey = globalConst->GetHandledCauseString();
bool causePresent = JSTaggedValue::HasProperty(thread, options, causeKey);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (causePresent) {
JSHandle<JSTaggedValue> cause = JSObject::GetProperty(thread, options, causeKey).GetValue();
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
PropertyDescriptor causeDesc(thread, cause, true, false, true);
[[maybe_unused]] bool status = JSObject::DefineOwnProperty(thread, nativeInstanceObj, causeKey, causeDesc);
ASSERT_PRINT(status == true, "return result exception!");
}
}
JSHandle<JSTaggedValue> errorFunc = GetErrorJSFunction(thread);
if (!errorFunc->IsUndefined()) {
JSHandle<JSTaggedValue> errorFunckey = globalConst->GetHandledErrorFuncString();
PropertyDescriptor errorFuncDesc(thread, errorFunc, true, false, true);
[[maybe_unused]] bool status = JSObject::DefineOwnProperty(thread,
nativeInstanceObj, errorFunckey, errorFuncDesc);
ASSERT_PRINT(status == true, "return result exception!");
}
std::string stackTrace = BuildStackTraceWithLimit(thread, nativeInstanceObj);
JSHandle<EcmaString> stackTraceStr = factory->NewFromStdString(stackTrace);
JSHandle<EcmaString> cbStackTraceStr;
auto sourceMapcb = ecmaVm->GetSourceMapCallback();
if (sourceMapcb != nullptr && !stackTrace.empty()) {
cbStackTraceStr = factory->NewFromStdString(sourceMapcb(stackTrace.c_str()));
} else {
cbStackTraceStr = stackTraceStr;
}
PropertyDescriptor stackDesc(thread, JSHandle<JSTaggedValue>::Cast(cbStackTraceStr), true, false, true);
[[maybe_unused]] bool status = JSObject::DefineOwnProperty(thread, nativeInstanceObj,
globalConst->GetHandledStackString(), stackDesc);
ASSERT_PRINT(status == true, "return result exception!");
PropertyDescriptor topStackDesc(thread, JSHandle<JSTaggedValue>::Cast(stackTraceStr), true, false, true);
[[maybe_unused]] bool topStackstatus = JSObject::DefineOwnProperty(thread, nativeInstanceObj,
globalConst->GetHandledTopStackString(), topStackDesc);
ASSERT_PRINT(topStackstatus == true, "return result exception!");
if (UNLIKELY(ecmaVm->GetJSOptions().EnableModuleImportStack())) {
std::string moduleStack = thread->GetModuleManager()
->GetImportStackDataForJSCrash(64 * 1024);
if (!moduleStack.empty()) {
JSHandle<EcmaString> moduleStackStr = factory->NewFromStdString(moduleStack);
PropertyDescriptor moduleStackDesc(thread,
JSHandle<JSTaggedValue>::Cast(moduleStackStr), true, false, true);
[[maybe_unused]] bool moduleStackstatus = JSObject::DefineOwnProperty(thread, nativeInstanceObj,
globalConst->GetHandledModuleImportStackString(), moduleStackDesc);
ASSERT_PRINT(moduleStackstatus == true, "Failed to define module Import stack property on object!");
}
}
if (UNLIKELY(ecmaVm->IsEnableRuntimeAsyncStack())) {
std::string asyncStackTrace;
ecmaVm->GetAsyncStackTraceManager()->BuildAsyncStackTrace(asyncStackTrace);
if (!asyncStackTrace.empty()) {
JSHandle<EcmaString> asyncStackTraceStr = factory->NewFromStdString(asyncStackTrace);
PropertyDescriptor asyncStackDesc(thread,
JSHandle<JSTaggedValue>::Cast(asyncStackTraceStr), true, false, true);
[[maybe_unused]] bool asyncStackstatus = JSObject::DefineOwnProperty(thread, nativeInstanceObj,
globalConst->GetHandledAsyncStackString(), asyncStackDesc);
ASSERT_PRINT(asyncStackstatus == true, "return result exception!");
PrintJSErrorInfo(thread, JSHandle<JSTaggedValue>::Cast(nativeInstanceObj));
}
}
return nativeInstanceObj.GetTaggedValue();
}
void ErrorHelper::PrintJSErrorInfo(JSThread *thread, const JSHandle<JSTaggedValue> exceptionInfo)
{
CString nameBuffer = GetJSErrorInfo(thread, exceptionInfo, JSErrorProps::NAME);
CString msgBuffer = GetJSErrorInfo(thread, exceptionInfo, JSErrorProps::MESSAGE);
CString stackBuffer = GetJSErrorInfo(thread, exceptionInfo, JSErrorProps::STACK);
LOG_NO_TAG(ERROR) << panda::ecmascript::previewerTag << nameBuffer << ": " << msgBuffer << "\n"
<< (panda::ecmascript::previewerTag.empty()
? stackBuffer
: std::regex_replace(stackBuffer, std::regex(".+(\n|$)"),
panda::ecmascript::previewerTag + "$0"));
if (UNLIKELY(thread->GetEcmaVM()->IsEnableRuntimeAsyncStack())) {
CString asyncStackBuffer = GetJSErrorInfo(thread, exceptionInfo, JSErrorProps::ASYNC_STACK);
std::ostringstream oss;
std::istringstream iss(asyncStackBuffer.c_str());
oss << "AsyncStack:\n";
std::string line;
while (std::getline(iss, line)) {
oss << " " << line << "\n";
}
LOG_NO_TAG(ERROR) << oss.str();
}
}
CString ErrorHelper::GetJSErrorInfo(JSThread *thread, const JSHandle<JSTaggedValue> exceptionInfo, JSErrorProps key)
{
JSHandle<JSTaggedValue> keyStr(thread, JSTaggedValue::Undefined());
switch (key) {
case JSErrorProps::NAME:
keyStr = thread->GlobalConstants()->GetHandledNameString();
break;
case JSErrorProps::MESSAGE:
keyStr = thread->GlobalConstants()->GetHandledMessageString();
break;
case JSErrorProps::STACK:
keyStr = thread->GlobalConstants()->GetHandledStackString();
break;
case JSErrorProps::ASYNC_STACK:
keyStr = thread->GlobalConstants()->GetHandledAsyncStackString();
break;
default:
LOG_ECMA(FATAL) << "this branch is unreachable " << key;
UNREACHABLE();
}
JSHandle<JSTaggedValue> value = JSObject::GetProperty(thread, exceptionInfo, keyStr).GetValue();
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, CString());
JSHandle<EcmaString> errStr = JSTaggedValue::ToString(thread, value);
if (thread->HasPendingException()) {
thread->ClearExceptionAndExtraErrorMessage();
errStr = thread->GetEcmaVM()->GetFactory()->NewFromStdString("<error>");
}
return ConvertToString(thread, *errStr);
}
JSHandle<JSTaggedValue> ErrorHelper::GetErrorJSFunction(JSThread *thread)
{
FrameHandler frameHandler(thread);
for (; frameHandler.HasFrame(); frameHandler.PrevJSFrame()) {
if (!frameHandler.IsJSFrame()) {
continue;
}
auto function = frameHandler.GetFunction();
if (function.IsJSFunctionBase() || function.IsJSProxy()) {
Method *method = ECMAObject::Cast(function.GetTaggedObject())->GetCallTarget(thread);
if (!method->IsNativeWithCallField()) {
return JSHandle<JSTaggedValue>(thread, function);
}
}
}
return thread->GlobalConstants()->GetHandledUndefined();
}
std::string ErrorHelper::BuildStackTraceWithLimit(JSThread *thread, const JSHandle<JSObject> &jsErrorObj)
{
std::string data = JsStackInfo::BuildJsStackTrace(thread, false, jsErrorObj, true);
if (data.size() > MAX_ERROR_SIZE) {
size_t pos = data.rfind('\n', MAX_ERROR_SIZE);
if (pos != std::string::npos) {
data.resize(pos);
}
}
LOG_ECMA(DEBUG) << data;
return data;
}
}