#include "vm/ErrorObject-inl.h"
#include "mozilla/Range.h"
#include <utility>
#include "jsexn.h"
#include "js/CallArgs.h"
#include "js/CharacterEncoding.h"
#include "vm/GlobalObject.h"
#include "vm/SelfHosting.h"
#include "vm/StringType.h"
#include "vm/JSObject-inl.h"
#include "vm/NativeObject-inl.h"
#include "vm/SavedStacks-inl.h"
#include "vm/Shape-inl.h"
using namespace js;
Shape* js::ErrorObject::assignInitialShape(JSContext* cx,
Handle<ErrorObject*> obj) {
MOZ_ASSERT(obj->empty());
if (!NativeObject::addDataProperty(cx, obj, cx->names().fileName,
FILENAME_SLOT, 0)) {
return nullptr;
}
if (!NativeObject::addDataProperty(cx, obj, cx->names().lineNumber,
LINENUMBER_SLOT, 0)) {
return nullptr;
}
return NativeObject::addDataProperty(cx, obj, cx->names().columnNumber,
COLUMNNUMBER_SLOT, 0);
}
bool js::ErrorObject::init(JSContext* cx, Handle<ErrorObject*> obj,
JSExnType type, UniquePtr<JSErrorReport> errorReport,
HandleString fileName, HandleObject stack,
uint32_t sourceId, uint32_t lineNumber,
uint32_t columnNumber, HandleString message) {
AssertObjectIsSavedFrameOrWrapper(cx, stack);
cx->check(obj, stack);
obj->initReservedSlot(ERROR_REPORT_SLOT, PrivateValue(nullptr));
if (!EmptyShape::ensureInitialCustomShape<ErrorObject>(cx, obj)) {
return false;
}
RootedShape messageShape(cx);
if (message) {
messageShape = NativeObject::addDataProperty(cx, obj, cx->names().message,
MESSAGE_SLOT, 0);
if (!messageShape) {
return false;
}
MOZ_ASSERT(messageShape->slot() == MESSAGE_SLOT);
}
MOZ_ASSERT(obj->lookupPure(NameToId(cx->names().fileName))->slot() ==
FILENAME_SLOT);
MOZ_ASSERT(obj->lookupPure(NameToId(cx->names().lineNumber))->slot() ==
LINENUMBER_SLOT);
MOZ_ASSERT(obj->lookupPure(NameToId(cx->names().columnNumber))->slot() ==
COLUMNNUMBER_SLOT);
MOZ_ASSERT_IF(
message,
obj->lookupPure(NameToId(cx->names().message))->slot() == MESSAGE_SLOT);
MOZ_ASSERT(JSEXN_ERR <= type && type < JSEXN_LIMIT);
JSErrorReport* report = errorReport.release();
obj->initReservedSlot(EXNTYPE_SLOT, Int32Value(type));
obj->initReservedSlot(STACK_SLOT, ObjectOrNullValue(stack));
obj->setReservedSlot(ERROR_REPORT_SLOT, PrivateValue(report));
obj->initReservedSlot(FILENAME_SLOT, StringValue(fileName));
obj->initReservedSlot(LINENUMBER_SLOT, Int32Value(lineNumber));
obj->initReservedSlot(COLUMNNUMBER_SLOT, Int32Value(columnNumber));
if (message) {
obj->setSlotWithType(cx, messageShape, StringValue(message));
}
obj->initReservedSlot(SOURCEID_SLOT, Int32Value(sourceId));
if (mozilla::recordreplay::IsRecordingOrReplaying() &&
!cx->runtime()->parentRuntime) {
uint64_t timeWarpTarget = mozilla::recordreplay::NewTimeWarpTarget();
MOZ_RELEASE_ASSERT(timeWarpTarget <
uint64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT));
obj->initReservedSlot(TIME_WARP_SLOT, DoubleValue(timeWarpTarget));
}
return true;
}
ErrorObject* js::ErrorObject::create(JSContext* cx, JSExnType errorType,
HandleObject stack, HandleString fileName,
uint32_t sourceId, uint32_t lineNumber,
uint32_t columnNumber,
UniquePtr<JSErrorReport> report,
HandleString message,
HandleObject protoArg ) {
AssertObjectIsSavedFrameOrWrapper(cx, stack);
RootedObject proto(cx, protoArg);
if (!proto) {
proto = GlobalObject::getOrCreateCustomErrorPrototype(cx, cx->global(),
errorType);
if (!proto) {
return nullptr;
}
}
Rooted<ErrorObject*> errObject(cx);
{
const Class* clasp = ErrorObject::classForType(errorType);
JSObject* obj = NewObjectWithGivenProto(cx, clasp, proto);
if (!obj) {
return nullptr;
}
errObject = &obj->as<ErrorObject>();
}
if (!ErrorObject::init(cx, errObject, errorType, std::move(report), fileName,
stack, sourceId, lineNumber, columnNumber, message)) {
return nullptr;
}
return errObject;
}
JSErrorReport* js::ErrorObject::getOrCreateErrorReport(JSContext* cx) {
if (JSErrorReport* r = getErrorReport()) {
return r;
}
JSErrorReport report;
JSExnType type_ = type();
report.exnType = type_;
UniqueChars filenameStr = JS_EncodeStringToLatin1(cx, fileName(cx));
if (!filenameStr) {
return nullptr;
}
report.filename = filenameStr.get();
report.sourceId = sourceId();
report.lineno = lineNumber();
report.column = columnNumber();
RootedString message(cx, getMessage());
if (!message) {
message = cx->runtime()->emptyString;
}
if (!message->ensureFlat(cx)) {
return nullptr;
}
UniqueChars utf8 = StringToNewUTF8CharsZ(cx, *message);
if (!utf8) {
return nullptr;
}
report.initOwnedMessage(utf8.release());
UniquePtr<JSErrorReport> copy = CopyErrorReport(cx, &report);
if (!copy) {
return nullptr;
}
setReservedSlot(ERROR_REPORT_SLOT, PrivateValue(copy.get()));
return copy.release();
}
static bool FindErrorInstanceOrPrototype(JSContext* cx, HandleObject obj,
MutableHandleObject result) {
RootedObject target(cx, CheckedUnwrapStatic(obj));
if (!target) {
ReportAccessDenied(cx);
return false;
}
RootedObject proto(cx);
while (!IsErrorProtoKey(StandardProtoKeyOrNull(target))) {
if (!GetPrototype(cx, target, &proto)) {
return false;
}
if (!proto) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_INCOMPATIBLE_PROTO, js_Error_str,
"(get stack)", obj->getClass()->name);
return false;
}
target = CheckedUnwrapStatic(proto);
if (!target) {
ReportAccessDenied(cx);
return false;
}
}
result.set(target);
return true;
}
static MOZ_ALWAYS_INLINE bool IsObject(HandleValue v) { return v.isObject(); }
bool js::ErrorObject::getStack(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsObject, getStack_impl>(cx, args);
}
bool js::ErrorObject::getStack_impl(JSContext* cx, const CallArgs& args) {
RootedObject thisObj(cx, &args.thisv().toObject());
RootedObject obj(cx);
if (!FindErrorInstanceOrPrototype(cx, thisObj, &obj)) {
return false;
}
if (!obj->is<ErrorObject>()) {
args.rval().setString(cx->runtime()->emptyString);
return true;
}
JSPrincipals* principals = obj->as<ErrorObject>().realm()->principals();
RootedObject savedFrameObj(cx, obj->as<ErrorObject>().stack());
RootedString stackString(cx);
if (!BuildStackString(cx, principals, savedFrameObj, &stackString)) {
return false;
}
if (cx->runtime()->stackFormat() == js::StackFormat::V8) {
HandlePropertyName name = cx->names().ErrorToStringWithTrailingNewline;
FixedInvokeArgs<0> args2(cx);
RootedValue rval(cx);
if (!CallSelfHostedFunction(cx, name, args.thisv(), args2, &rval)) {
return false;
}
if (!rval.isString()) {
args.rval().setString(cx->runtime()->emptyString);
return true;
}
RootedString stringified(cx, rval.toString());
stackString = ConcatStrings<CanGC>(cx, stringified, stackString);
}
args.rval().setString(stackString);
return true;
}
bool js::ErrorObject::setStack(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsObject, setStack_impl>(cx, args);
}
bool js::ErrorObject::setStack_impl(JSContext* cx, const CallArgs& args) {
RootedObject thisObj(cx, &args.thisv().toObject());
if (!args.requireAtLeast(cx, "(set stack)", 1)) {
return false;
}
RootedValue val(cx, args[0]);
return DefineDataProperty(cx, thisObj, cx->names().stack, val);
}