Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : * vim: set ts=8 sw=4 et tw=78:
3 : *
4 : * This Source Code Form is subject to the terms of the Mozilla Public
5 : * License, v. 2.0. If a copy of the MPL was not distributed with this
6 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 :
8 : #include "vm/ErrorObject-inl.h"
9 :
10 : #include "mozilla/Range.h"
11 :
12 : #include "jsexn.h"
13 :
14 : #include "js/CallArgs.h"
15 : #include "js/CharacterEncoding.h"
16 : #include "vm/GlobalObject.h"
17 : #include "vm/String.h"
18 :
19 : #include "jsobjinlines.h"
20 :
21 : #include "vm/NativeObject-inl.h"
22 : #include "vm/SavedStacks-inl.h"
23 : #include "vm/Shape-inl.h"
24 :
25 : using namespace js;
26 :
27 : /* static */ Shape*
28 3 : js::ErrorObject::assignInitialShape(JSContext* cx, Handle<ErrorObject*> obj)
29 : {
30 3 : MOZ_ASSERT(obj->empty());
31 :
32 3 : if (!NativeObject::addDataProperty(cx, obj, cx->names().fileName, FILENAME_SLOT, 0))
33 0 : return nullptr;
34 3 : if (!NativeObject::addDataProperty(cx, obj, cx->names().lineNumber, LINENUMBER_SLOT, 0))
35 0 : return nullptr;
36 3 : return NativeObject::addDataProperty(cx, obj, cx->names().columnNumber, COLUMNNUMBER_SLOT, 0);
37 : }
38 :
39 : /* static */ bool
40 3 : js::ErrorObject::init(JSContext* cx, Handle<ErrorObject*> obj, JSExnType type,
41 : ScopedJSFreePtr<JSErrorReport>* errorReport, HandleString fileName,
42 : HandleObject stack, uint32_t lineNumber, uint32_t columnNumber,
43 : HandleString message)
44 : {
45 3 : AssertObjectIsSavedFrameOrWrapper(cx, stack);
46 3 : assertSameCompartment(cx, obj, stack);
47 :
48 : // Null out early in case of error, for exn_finalize's sake.
49 3 : obj->initReservedSlot(ERROR_REPORT_SLOT, PrivateValue(nullptr));
50 :
51 3 : if (!EmptyShape::ensureInitialCustomShape<ErrorObject>(cx, obj))
52 0 : return false;
53 :
54 : // The .message property isn't part of the initial shape because it's
55 : // present in some error objects -- |Error.prototype|, |new Error("f")|,
56 : // |new Error("")| -- but not in others -- |new Error(undefined)|,
57 : // |new Error()|.
58 6 : RootedShape messageShape(cx);
59 3 : if (message) {
60 2 : messageShape = NativeObject::addDataProperty(cx, obj, cx->names().message, MESSAGE_SLOT, 0);
61 2 : if (!messageShape)
62 0 : return false;
63 2 : MOZ_ASSERT(messageShape->slot() == MESSAGE_SLOT);
64 : }
65 :
66 3 : MOZ_ASSERT(obj->lookupPure(NameToId(cx->names().fileName))->slot() == FILENAME_SLOT);
67 3 : MOZ_ASSERT(obj->lookupPure(NameToId(cx->names().lineNumber))->slot() == LINENUMBER_SLOT);
68 3 : MOZ_ASSERT(obj->lookupPure(NameToId(cx->names().columnNumber))->slot() ==
69 : COLUMNNUMBER_SLOT);
70 3 : MOZ_ASSERT_IF(message,
71 : obj->lookupPure(NameToId(cx->names().message))->slot() == MESSAGE_SLOT);
72 :
73 3 : MOZ_ASSERT(JSEXN_ERR <= type && type < JSEXN_LIMIT);
74 :
75 3 : JSErrorReport* report = errorReport ? errorReport->forget() : nullptr;
76 3 : obj->initReservedSlot(EXNTYPE_SLOT, Int32Value(type));
77 3 : obj->initReservedSlot(STACK_SLOT, ObjectOrNullValue(stack));
78 3 : obj->setReservedSlot(ERROR_REPORT_SLOT, PrivateValue(report));
79 3 : obj->initReservedSlot(FILENAME_SLOT, StringValue(fileName));
80 3 : obj->initReservedSlot(LINENUMBER_SLOT, Int32Value(lineNumber));
81 3 : obj->initReservedSlot(COLUMNNUMBER_SLOT, Int32Value(columnNumber));
82 3 : if (message)
83 2 : obj->setSlotWithType(cx, messageShape, StringValue(message));
84 :
85 3 : return true;
86 : }
87 :
88 : /* static */ ErrorObject*
89 3 : js::ErrorObject::create(JSContext* cx, JSExnType errorType, HandleObject stack,
90 : HandleString fileName, uint32_t lineNumber, uint32_t columnNumber,
91 : ScopedJSFreePtr<JSErrorReport>* report, HandleString message,
92 : HandleObject protoArg /* = nullptr */)
93 : {
94 3 : AssertObjectIsSavedFrameOrWrapper(cx, stack);
95 :
96 6 : RootedObject proto(cx, protoArg);
97 3 : if (!proto) {
98 3 : proto = GlobalObject::getOrCreateCustomErrorPrototype(cx, cx->global(), errorType);
99 3 : if (!proto)
100 0 : return nullptr;
101 : }
102 :
103 6 : Rooted<ErrorObject*> errObject(cx);
104 : {
105 3 : const Class* clasp = ErrorObject::classForType(errorType);
106 3 : JSObject* obj = NewObjectWithGivenProto(cx, clasp, proto);
107 3 : if (!obj)
108 0 : return nullptr;
109 3 : errObject = &obj->as<ErrorObject>();
110 : }
111 :
112 3 : if (!ErrorObject::init(cx, errObject, errorType, report, fileName, stack,
113 : lineNumber, columnNumber, message))
114 : {
115 0 : return nullptr;
116 : }
117 :
118 3 : return errObject;
119 : }
120 :
121 : JSErrorReport*
122 0 : js::ErrorObject::getOrCreateErrorReport(JSContext* cx)
123 : {
124 0 : if (JSErrorReport* r = getErrorReport())
125 0 : return r;
126 :
127 : // We build an error report on the stack and then use CopyErrorReport to do
128 : // the nitty-gritty malloc stuff.
129 0 : JSErrorReport report;
130 :
131 : // Type.
132 0 : JSExnType type_ = type();
133 0 : report.exnType = type_;
134 :
135 : // Filename.
136 0 : JSAutoByteString filenameStr;
137 0 : if (!filenameStr.encodeLatin1(cx, fileName(cx)))
138 0 : return nullptr;
139 0 : report.filename = filenameStr.ptr();
140 :
141 : // Coordinates.
142 0 : report.lineno = lineNumber();
143 0 : report.column = columnNumber();
144 :
145 : // Message. Note that |new Error()| will result in an undefined |message|
146 : // slot, so we need to explicitly substitute the empty string in that case.
147 0 : RootedString message(cx, getMessage());
148 0 : if (!message)
149 0 : message = cx->runtime()->emptyString;
150 0 : if (!message->ensureFlat(cx))
151 0 : return nullptr;
152 :
153 0 : UniquePtr<char[], JS::FreePolicy> utf8 = StringToNewUTF8CharsZ(cx, *message);
154 0 : if (!utf8)
155 0 : return nullptr;
156 0 : report.initOwnedMessage(utf8.release());
157 :
158 : // Cache and return.
159 0 : JSErrorReport* copy = CopyErrorReport(cx, &report);
160 0 : if (!copy)
161 0 : return nullptr;
162 0 : setReservedSlot(ERROR_REPORT_SLOT, PrivateValue(copy));
163 0 : return copy;
164 : }
165 :
166 : static bool
167 0 : FindErrorInstanceOrPrototype(JSContext* cx, HandleObject obj, MutableHandleObject result)
168 : {
169 : // Walk up the prototype chain until we find an error object instance or
170 : // prototype object. This allows code like:
171 : // Object.create(Error.prototype).stack
172 : // or
173 : // function NYI() { }
174 : // NYI.prototype = new Error;
175 : // (new NYI).stack
176 : // to continue returning stacks that are useless, but at least don't throw.
177 :
178 0 : RootedObject target(cx, CheckedUnwrap(obj));
179 0 : if (!target) {
180 0 : ReportAccessDenied(cx);
181 0 : return false;
182 : }
183 :
184 0 : RootedObject proto(cx);
185 0 : while (!IsErrorProtoKey(StandardProtoKeyOrNull(target))) {
186 0 : if (!GetPrototype(cx, target, &proto))
187 0 : return false;
188 :
189 0 : if (!proto) {
190 : // We walked the whole prototype chain and did not find an Error
191 : // object.
192 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
193 0 : js_Error_str, "(get stack)", obj->getClass()->name);
194 0 : return false;
195 : }
196 :
197 0 : target = CheckedUnwrap(proto);
198 0 : if (!target) {
199 0 : ReportAccessDenied(cx);
200 0 : return false;
201 : }
202 : }
203 :
204 0 : result.set(target);
205 0 : return true;
206 : }
207 :
208 :
209 : static MOZ_ALWAYS_INLINE bool
210 0 : IsObject(HandleValue v)
211 : {
212 0 : return v.isObject();
213 : }
214 :
215 : /* static */ bool
216 0 : js::ErrorObject::getStack(JSContext* cx, unsigned argc, Value* vp)
217 : {
218 0 : CallArgs args = CallArgsFromVp(argc, vp);
219 : // We accept any object here, because of poor-man's subclassing of Error.
220 0 : return CallNonGenericMethod<IsObject, getStack_impl>(cx, args);
221 : }
222 :
223 : /* static */ bool
224 0 : js::ErrorObject::getStack_impl(JSContext* cx, const CallArgs& args)
225 : {
226 0 : RootedObject thisObj(cx, &args.thisv().toObject());
227 :
228 0 : RootedObject obj(cx);
229 0 : if (!FindErrorInstanceOrPrototype(cx, thisObj, &obj))
230 0 : return false;
231 :
232 0 : if (!obj->is<ErrorObject>()) {
233 0 : args.rval().setString(cx->runtime()->emptyString);
234 0 : return true;
235 : }
236 :
237 0 : RootedObject savedFrameObj(cx, obj->as<ErrorObject>().stack());
238 0 : RootedString stackString(cx);
239 0 : if (!BuildStackString(cx, savedFrameObj, &stackString))
240 0 : return false;
241 :
242 0 : if (cx->runtime()->stackFormat() == js::StackFormat::V8) {
243 : // When emulating V8 stack frames, we also need to prepend the
244 : // stringified Error to the stack string.
245 0 : HandlePropertyName name = cx->names().ErrorToStringWithTrailingNewline;
246 0 : RootedValue val(cx);
247 0 : if (!GlobalObject::getSelfHostedFunction(cx, cx->global(), name, name, 0, &val))
248 0 : return false;
249 :
250 0 : RootedValue rval(cx);
251 0 : if (!js::Call(cx, val, args.thisv(), &rval))
252 0 : return false;
253 :
254 0 : if (!rval.isString())
255 0 : return false;
256 :
257 0 : RootedString stringified(cx, rval.toString());
258 0 : stackString = ConcatStrings<CanGC>(cx, stringified, stackString);
259 : }
260 :
261 0 : args.rval().setString(stackString);
262 0 : return true;
263 : }
264 :
265 : /* static */ bool
266 0 : js::ErrorObject::setStack(JSContext* cx, unsigned argc, Value* vp)
267 : {
268 0 : CallArgs args = CallArgsFromVp(argc, vp);
269 : // We accept any object here, because of poor-man's subclassing of Error.
270 0 : return CallNonGenericMethod<IsObject, setStack_impl>(cx, args);
271 : }
272 :
273 : /* static */ bool
274 0 : js::ErrorObject::setStack_impl(JSContext* cx, const CallArgs& args)
275 : {
276 0 : RootedObject thisObj(cx, &args.thisv().toObject());
277 :
278 0 : if (!args.requireAtLeast(cx, "(set stack)", 1))
279 0 : return false;
280 0 : RootedValue val(cx, args[0]);
281 :
282 0 : return DefineProperty(cx, thisObj, cx->names().stack, val);
283 : }
|