diff options
Diffstat (limited to 'js/src/vm/ErrorObject.cpp')
-rw-r--r-- | js/src/vm/ErrorObject.cpp | 356 |
1 files changed, 356 insertions, 0 deletions
diff --git a/js/src/vm/ErrorObject.cpp b/js/src/vm/ErrorObject.cpp index 8976d81d27..192b0758db 100644 --- a/js/src/vm/ErrorObject.cpp +++ b/js/src/vm/ErrorObject.cpp @@ -12,6 +12,7 @@ #include "js/CallArgs.h" #include "js/CharacterEncoding.h" +#include "vm/StringBuffer.h" #include "vm/GlobalObject.h" #include "vm/String.h" @@ -23,6 +24,283 @@ using namespace js; +#define IMPLEMENT_ERROR_PROTO_CLASS(name) \ + { \ + js_Object_str, \ + JSCLASS_HAS_CACHED_PROTO(JSProto_##name), \ + JS_NULL_CLASS_OPS, \ + &ErrorObject::classSpecs[JSProto_##name - JSProto_Error] \ + } + +const Class +ErrorObject::protoClasses[JSEXN_ERROR_LIMIT] = { + IMPLEMENT_ERROR_PROTO_CLASS(Error), + + IMPLEMENT_ERROR_PROTO_CLASS(InternalError), + IMPLEMENT_ERROR_PROTO_CLASS(EvalError), + IMPLEMENT_ERROR_PROTO_CLASS(RangeError), + IMPLEMENT_ERROR_PROTO_CLASS(ReferenceError), + IMPLEMENT_ERROR_PROTO_CLASS(SyntaxError), + IMPLEMENT_ERROR_PROTO_CLASS(TypeError), + IMPLEMENT_ERROR_PROTO_CLASS(URIError), + + IMPLEMENT_ERROR_PROTO_CLASS(DebuggeeWouldRun), + IMPLEMENT_ERROR_PROTO_CLASS(CompileError), + IMPLEMENT_ERROR_PROTO_CLASS(RuntimeError) +}; + +static bool +exn_toSource(JSContext* cx, unsigned argc, Value* vp); + +static const JSFunctionSpec error_methods[] = { +#if JS_HAS_TOSOURCE + JS_FN(js_toSource_str, exn_toSource, 0, 0), +#endif + JS_SELF_HOSTED_FN(js_toString_str, "ErrorToString", 0,0), + JS_FS_END +}; + +static const JSPropertySpec error_properties[] = { + JS_STRING_PS("message", "", 0), + JS_STRING_PS("name", "Error", 0), + // Only Error.prototype has .stack! + JS_PSGS("stack", ErrorObject::getStack, ErrorObject::setStack, 0), + JS_PS_END +}; + +#define IMPLEMENT_ERROR_PROPERTIES(name) \ + { \ + JS_STRING_PS("message", "", 0), \ + JS_STRING_PS("name", #name, 0), \ + JS_PS_END \ + } + +static const JSPropertySpec other_error_properties[JSEXN_ERROR_LIMIT - 1][3] = { + IMPLEMENT_ERROR_PROPERTIES(InternalError), + IMPLEMENT_ERROR_PROPERTIES(EvalError), + IMPLEMENT_ERROR_PROPERTIES(RangeError), + IMPLEMENT_ERROR_PROPERTIES(ReferenceError), + IMPLEMENT_ERROR_PROPERTIES(SyntaxError), + IMPLEMENT_ERROR_PROPERTIES(TypeError), + IMPLEMENT_ERROR_PROPERTIES(URIError), + IMPLEMENT_ERROR_PROPERTIES(DebuggeeWouldRun), + IMPLEMENT_ERROR_PROPERTIES(CompileError), + IMPLEMENT_ERROR_PROPERTIES(RuntimeError) +}; + +#define IMPLEMENT_NATIVE_ERROR_SPEC(name) \ + { \ + ErrorObject::createConstructor, \ + ErrorObject::createProto, \ + nullptr, \ + nullptr, \ + nullptr, \ + other_error_properties[JSProto_##name - JSProto_Error - 1], \ + nullptr, \ + JSProto_Error \ + } + +#define IMPLEMENT_NONGLOBAL_ERROR_SPEC(name) \ + { \ + ErrorObject::createConstructor, \ + ErrorObject::createProto, \ + nullptr, \ + nullptr, \ + nullptr, \ + other_error_properties[JSProto_##name - JSProto_Error - 1], \ + nullptr, \ + JSProto_Error | ClassSpec::DontDefineConstructor \ + } + +const ClassSpec +ErrorObject::classSpecs[JSEXN_ERROR_LIMIT] = { + { + ErrorObject::createConstructor, + ErrorObject::createProto, + nullptr, + nullptr, + error_methods, + error_properties + }, + + IMPLEMENT_NATIVE_ERROR_SPEC(InternalError), + IMPLEMENT_NATIVE_ERROR_SPEC(EvalError), + IMPLEMENT_NATIVE_ERROR_SPEC(RangeError), + IMPLEMENT_NATIVE_ERROR_SPEC(ReferenceError), + IMPLEMENT_NATIVE_ERROR_SPEC(SyntaxError), + IMPLEMENT_NATIVE_ERROR_SPEC(TypeError), + IMPLEMENT_NATIVE_ERROR_SPEC(URIError), + + IMPLEMENT_NONGLOBAL_ERROR_SPEC(DebuggeeWouldRun), + IMPLEMENT_NONGLOBAL_ERROR_SPEC(CompileError), + IMPLEMENT_NONGLOBAL_ERROR_SPEC(RuntimeError) +}; + +#define IMPLEMENT_ERROR_CLASS(name) \ + { \ + js_Error_str, /* yes, really */ \ + JSCLASS_HAS_CACHED_PROTO(JSProto_##name) | \ + JSCLASS_HAS_RESERVED_SLOTS(ErrorObject::RESERVED_SLOTS) | \ + JSCLASS_BACKGROUND_FINALIZE, \ + &ErrorObjectClassOps, \ + &ErrorObject::classSpecs[JSProto_##name - JSProto_Error ] \ + } + +static void +exn_finalize(FreeOp* fop, JSObject* obj); + +static const ClassOps ErrorObjectClassOps = { + nullptr, /* addProperty */ + nullptr, /* delProperty */ + nullptr, /* getProperty */ + nullptr, /* setProperty */ + nullptr, /* enumerate */ + nullptr, /* resolve */ + nullptr, /* mayResolve */ + exn_finalize, + nullptr, /* call */ + nullptr, /* hasInstance */ + nullptr, /* construct */ + nullptr, /* trace */ +}; + +const Class +ErrorObject::classes[JSEXN_ERROR_LIMIT] = { + IMPLEMENT_ERROR_CLASS(Error), + IMPLEMENT_ERROR_CLASS(InternalError), + IMPLEMENT_ERROR_CLASS(EvalError), + IMPLEMENT_ERROR_CLASS(RangeError), + IMPLEMENT_ERROR_CLASS(ReferenceError), + IMPLEMENT_ERROR_CLASS(SyntaxError), + IMPLEMENT_ERROR_CLASS(TypeError), + IMPLEMENT_ERROR_CLASS(URIError), + // These Error subclasses are not accessible via the global object: + IMPLEMENT_ERROR_CLASS(DebuggeeWouldRun), + IMPLEMENT_ERROR_CLASS(CompileError), + IMPLEMENT_ERROR_CLASS(RuntimeError) +}; + +static void +exn_finalize(FreeOp* fop, JSObject* obj) +{ + MOZ_ASSERT(fop->maybeOffMainThread()); + if (JSErrorReport* report = obj->as<ErrorObject>().getErrorReport()) + fop->delete_(report); +} + +bool +Error(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // ES6 19.5.1.1 mandates the .prototype lookup happens before the toString + RootedObject proto(cx); + if (!GetPrototypeFromCallableConstructor(cx, args, &proto)) + return false; + + /* Compute the error message, if any. */ + RootedString message(cx, nullptr); + if (args.hasDefined(0)) { + message = ToString<CanGC>(cx, args[0]); + if (!message) + return false; + } + + /* Find the scripted caller, but only ones we're allowed to know about. */ + NonBuiltinFrameIter iter(cx, cx->compartment()->principals()); + + /* Set the 'fileName' property. */ + RootedString fileName(cx); + if (args.length() > 1) { + fileName = ToString<CanGC>(cx, args[1]); + } else { + fileName = cx->runtime()->emptyString; + if (!iter.done()) { + if (const char* cfilename = iter.filename()) + fileName = JS_NewStringCopyZ(cx, cfilename); + } + } + if (!fileName) + return false; + + /* Set the 'lineNumber' property. */ + uint32_t lineNumber, columnNumber = 0; + if (args.length() > 2) { + if (!ToUint32(cx, args[2], &lineNumber)) + return false; + } else { + lineNumber = iter.done() ? 0 : iter.computeLine(&columnNumber); + // XXX: Make the column 1-based as in other browsers, instead of 0-based + // which is how SpiderMonkey stores it internally. This will be + // unnecessary once bug 1144340 is fixed. + ++columnNumber; + } + + RootedObject stack(cx); + if (!CaptureStack(cx, &stack)) + return false; + + /* + * ECMA ed. 3, 15.11.1 requires Error, etc., to construct even when + * called as functions, without operator new. But as we do not give + * each constructor a distinct JSClass, we must get the exception type + * ourselves. + */ + JSExnType exnType = JSExnType(args.callee().as<JSFunction>().getExtendedSlot(0).toInt32()); + + RootedObject obj(cx, ErrorObject::create(cx, exnType, stack, fileName, + lineNumber, columnNumber, nullptr, message, proto)); + if (!obj) + return false; + + args.rval().setObject(*obj); + return true; +} + +/* static */ JSObject* +ErrorObject::createProto(JSContext* cx, JSProtoKey key) +{ + JSExnType type = ExnTypeFromProtoKey(key); + + if (type == JSEXN_ERR) { + return GlobalObject::createBlankPrototype(cx, cx->global(), + &ErrorObject::protoClasses[JSEXN_ERR]); + } + + RootedObject protoProto(cx, GlobalObject::getOrCreateErrorPrototype(cx, cx->global())); + if (!protoProto) + return nullptr; + + return GlobalObject::createBlankPrototypeInheriting(cx, cx->global(), + &ErrorObject::protoClasses[type], + protoProto); +} + +/* static */ JSObject* +ErrorObject::createConstructor(JSContext* cx, JSProtoKey key) +{ + JSExnType type = ExnTypeFromProtoKey(key); + RootedObject ctor(cx); + + if (type == JSEXN_ERR) { + ctor = GenericCreateConstructor<Error, 1, gc::AllocKind::FUNCTION_EXTENDED>(cx, key); + } else { + RootedFunction proto(cx, GlobalObject::getOrCreateErrorConstructor(cx, cx->global())); + if (!proto) + return nullptr; + + ctor = NewFunctionWithProto(cx, Error, 1, JSFunction::NATIVE_CTOR, nullptr, + ClassName(key, cx), proto, gc::AllocKind::FUNCTION_EXTENDED, + SingletonObject); + } + + if (!ctor) + return nullptr; + + ctor->as<JSFunction>().setExtendedSlot(0, Int32Value(type)); + return ctor; +} + /* static */ Shape* js::ErrorObject::assignInitialShape(ExclusiveContext* cx, Handle<ErrorObject*> obj) { @@ -280,3 +558,81 @@ js::ErrorObject::setStack_impl(JSContext* cx, const CallArgs& args) return DefineProperty(cx, thisObj, cx->names().stack, val); } + +/* + * Return a string that may eval to something similar to the original object. + */ +static bool +exn_toSource(JSContext* cx, unsigned argc, Value* vp) +{ + JS_CHECK_RECURSION(cx, return false); + CallArgs args = CallArgsFromVp(argc, vp); + + RootedObject obj(cx, ToObject(cx, args.thisv())); + if (!obj) + return false; + + RootedValue nameVal(cx); + RootedString name(cx); + if (!GetProperty(cx, obj, obj, cx->names().name, &nameVal) || + !(name = ToString<CanGC>(cx, nameVal))) + { + return false; + } + + RootedValue messageVal(cx); + RootedString message(cx); + if (!GetProperty(cx, obj, obj, cx->names().message, &messageVal) || + !(message = ValueToSource(cx, messageVal))) + { + return false; + } + + RootedValue filenameVal(cx); + RootedString filename(cx); + if (!GetProperty(cx, obj, obj, cx->names().fileName, &filenameVal) || + !(filename = ValueToSource(cx, filenameVal))) + { + return false; + } + + RootedValue linenoVal(cx); + uint32_t lineno; + if (!GetProperty(cx, obj, obj, cx->names().lineNumber, &linenoVal) || + !ToUint32(cx, linenoVal, &lineno)) + { + return false; + } + + StringBuffer sb(cx); + if (!sb.append("(new ") || !sb.append(name) || !sb.append("(")) + return false; + + if (!sb.append(message)) + return false; + + if (!filename->empty()) { + if (!sb.append(", ") || !sb.append(filename)) + return false; + } + if (lineno != 0) { + /* We have a line, but no filename, add empty string */ + if (filename->empty() && !sb.append(", \"\"")) + return false; + + JSString* linenumber = ToString<CanGC>(cx, linenoVal); + if (!linenumber) + return false; + if (!sb.append(", ") || !sb.append(linenumber)) + return false; + } + + if (!sb.append("))")) + return false; + + JSString* str = sb.finishString(); + if (!str) + return false; + args.rval().setString(str); + return true; +} |