no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / dom / ipc / ClonedErrorHolder.cpp
blob8db9bd064e987343d62a6a5a6eca15c45c6b6014
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/ClonedErrorHolder.h"
9 #include "mozilla/dom/BindingUtils.h"
10 #include "mozilla/dom/ClonedErrorHolderBinding.h"
11 #include "mozilla/dom/DOMException.h"
12 #include "mozilla/dom/DOMExceptionBinding.h"
13 #include "mozilla/dom/Exceptions.h"
14 #include "mozilla/dom/StructuredCloneTags.h"
15 #include "mozilla/dom/ToJSValue.h"
16 #include "jsapi.h"
17 #include "jsfriendapi.h"
18 #include "js/StructuredClone.h"
19 #include "nsReadableUtils.h"
20 #include "xpcpublic.h"
22 using namespace mozilla;
23 using namespace mozilla::dom;
25 // static
26 UniquePtr<ClonedErrorHolder> ClonedErrorHolder::Constructor(
27 const GlobalObject& aGlobal, JS::Handle<JSObject*> aError,
28 ErrorResult& aRv) {
29 return Create(aGlobal.Context(), aError, aRv);
32 // static
33 UniquePtr<ClonedErrorHolder> ClonedErrorHolder::Create(
34 JSContext* aCx, JS::Handle<JSObject*> aError, ErrorResult& aRv) {
35 UniquePtr<ClonedErrorHolder> ceh(new ClonedErrorHolder());
36 ceh->Init(aCx, aError, aRv);
37 if (aRv.Failed()) {
38 return nullptr;
40 return ceh;
43 ClonedErrorHolder::ClonedErrorHolder()
44 : mName(VoidCString()),
45 mMessage(VoidCString()),
46 mFilename(VoidCString()),
47 mSourceLine(VoidCString()) {}
49 void ClonedErrorHolder::Init(JSContext* aCx, JS::Handle<JSObject*> aError,
50 ErrorResult& aRv) {
51 JS::Rooted<JSObject*> stack(aCx);
53 if (JSErrorReport* err = JS_ErrorFromException(aCx, aError)) {
54 mType = Type::JSError;
55 if (err->message()) {
56 mMessage = err->message().c_str();
58 if (err->filename) {
59 mFilename = err->filename.c_str();
61 if (err->linebuf()) {
62 AppendUTF16toUTF8(
63 nsDependentSubstring(err->linebuf(), err->linebufLength()),
64 mSourceLine);
65 mTokenOffset = err->tokenOffset();
67 mLineNumber = err->lineno;
68 mColumn = err->column;
69 mErrorNumber = err->errorNumber;
70 mExnType = JSExnType(err->exnType);
72 // Note: We don't save the souce ID here, since this object is cross-process
73 // clonable, and the source ID won't be valid in other processes.
74 // We don't store the source notes either, though for no other reason that
75 // it isn't clear that it's worth the complexity.
77 stack = JS::ExceptionStackOrNull(aError);
78 } else {
79 RefPtr<DOMException> domExn;
80 RefPtr<Exception> exn;
81 if (NS_SUCCEEDED(UNWRAP_OBJECT(DOMException, aError, domExn))) {
82 mType = Type::DOMException;
83 mCode = domExn->Code();
84 exn = std::move(domExn);
85 } else if (NS_SUCCEEDED(UNWRAP_OBJECT(Exception, aError, exn))) {
86 mType = Type::Exception;
87 } else {
88 aRv.ThrowNotSupportedError(
89 "We can only clone DOM Exceptions and native JS Error objects");
90 return;
93 nsAutoString str;
95 exn->GetName(str);
96 CopyUTF16toUTF8(str, mName);
98 exn->GetMessageMoz(str);
99 CopyUTF16toUTF8(str, mMessage);
101 // Note: In DOM exceptions, filename, line number, and column number come
102 // from the stack frame, and don't need to be stored separately. mFilename,
103 // mLineNumber, and mColumn are only used for JS exceptions.
105 // We also don't serialized Exception's mThrownJSVal or mData fields, since
106 // they generally won't be serializable.
108 mResult = nsresult(exn->Result());
110 if (nsCOMPtr<nsIStackFrame> frame = exn->GetLocation()) {
111 JS::Rooted<JS::Value> value(aCx);
112 frame->GetNativeSavedFrame(&value);
113 if (value.isObject()) {
114 stack = &value.toObject();
119 Maybe<JSAutoRealm> ar;
120 if (stack) {
121 ar.emplace(aCx, stack);
123 JS::Rooted<JS::Value> stackValue(aCx, JS::ObjectOrNullValue(stack));
124 mStack.Write(aCx, stackValue, aRv);
127 bool ClonedErrorHolder::WrapObject(JSContext* aCx,
128 JS::Handle<JSObject*> aGivenProto,
129 JS::MutableHandle<JSObject*> aReflector) {
130 return ClonedErrorHolder_Binding::Wrap(aCx, this, aGivenProto, aReflector);
133 static constexpr uint32_t kVoidStringLength = ~0;
135 static bool WriteStringPair(JSStructuredCloneWriter* aWriter,
136 const nsACString& aString1,
137 const nsACString& aString2) {
138 auto StringLength = [](const nsACString& aStr) -> uint32_t {
139 auto length = uint32_t(aStr.Length());
140 MOZ_DIAGNOSTIC_ASSERT(length != kVoidStringLength,
141 "We should not be serializing a 4GiB string");
142 if (aStr.IsVoid()) {
143 return kVoidStringLength;
145 return length;
148 return JS_WriteUint32Pair(aWriter, StringLength(aString1),
149 StringLength(aString2)) &&
150 JS_WriteBytes(aWriter, aString1.BeginReading(), aString1.Length()) &&
151 JS_WriteBytes(aWriter, aString2.BeginReading(), aString2.Length());
154 static bool ReadStringPair(JSStructuredCloneReader* aReader,
155 nsACString& aString1, nsACString& aString2) {
156 auto ReadString = [&](nsACString& aStr, uint32_t aLength) {
157 if (aLength == kVoidStringLength) {
158 aStr.SetIsVoid(true);
159 return true;
161 char* data = nullptr;
162 return aLength == 0 || (aStr.GetMutableData(&data, aLength, fallible) &&
163 JS_ReadBytes(aReader, data, aLength));
166 aString1.Truncate(0);
167 aString2.Truncate(0);
169 uint32_t length1, length2;
170 return JS_ReadUint32Pair(aReader, &length1, &length2) &&
171 ReadString(aString1, length1) && ReadString(aString2, length2);
174 bool ClonedErrorHolder::WriteStructuredClone(JSContext* aCx,
175 JSStructuredCloneWriter* aWriter,
176 StructuredCloneHolder* aHolder) {
177 auto& data = mStack.BufferData();
178 return JS_WriteUint32Pair(aWriter, SCTAG_DOM_CLONED_ERROR_OBJECT, 0) &&
179 WriteStringPair(aWriter, mName, mMessage) &&
180 WriteStringPair(aWriter, mFilename, mSourceLine) &&
181 JS_WriteUint32Pair(aWriter, mLineNumber,
182 *mColumn.addressOfValueForTranscode()) &&
183 JS_WriteUint32Pair(aWriter, mTokenOffset, mErrorNumber) &&
184 JS_WriteUint32Pair(aWriter, uint32_t(mType), uint32_t(mExnType)) &&
185 JS_WriteUint32Pair(aWriter, mCode, uint32_t(mResult)) &&
186 JS_WriteUint32Pair(aWriter, data.Size(),
187 JS_STRUCTURED_CLONE_VERSION) &&
188 data.ForEachDataChunk([&](const char* aData, size_t aSize) {
189 return JS_WriteBytes(aWriter, aData, aSize);
193 bool ClonedErrorHolder::Init(JSContext* aCx, JSStructuredCloneReader* aReader) {
194 uint32_t type, exnType, result, code;
195 if (!(ReadStringPair(aReader, mName, mMessage) &&
196 ReadStringPair(aReader, mFilename, mSourceLine) &&
197 JS_ReadUint32Pair(aReader, &mLineNumber,
198 mColumn.addressOfValueForTranscode()) &&
199 JS_ReadUint32Pair(aReader, &mTokenOffset, &mErrorNumber) &&
200 JS_ReadUint32Pair(aReader, &type, &exnType) &&
201 JS_ReadUint32Pair(aReader, &code, &result) &&
202 mStack.ReadStructuredCloneInternal(aCx, aReader))) {
203 return false;
206 if (type == uint32_t(Type::Uninitialized) || type >= uint32_t(Type::Max_) ||
207 exnType >= uint32_t(JSEXN_ERROR_LIMIT)) {
208 return false;
211 mType = Type(type);
212 mExnType = JSExnType(exnType);
213 mResult = nsresult(result);
214 mCode = code;
216 return true;
219 /* static */
220 JSObject* ClonedErrorHolder::ReadStructuredClone(
221 JSContext* aCx, JSStructuredCloneReader* aReader,
222 StructuredCloneHolder* aHolder) {
223 // Keep the result object rooted across the call to ClonedErrorHolder::Release
224 // to avoid a potential rooting hazard.
225 JS::Rooted<JS::Value> errorVal(aCx);
227 UniquePtr<ClonedErrorHolder> ceh(new ClonedErrorHolder());
228 if (!ceh->Init(aCx, aReader) || !ceh->ToErrorValue(aCx, &errorVal)) {
229 return nullptr;
232 return &errorVal.toObject();
235 static JS::UniqueTwoByteChars ToNullTerminatedJSStringBuffer(
236 JSContext* aCx, const nsString& aStr) {
237 // Since nsString is null terminated, we can simply copy + 1 characters.
238 size_t nbytes = (aStr.Length() + 1) * sizeof(char16_t);
239 JS::UniqueTwoByteChars buffer(static_cast<char16_t*>(JS_malloc(aCx, nbytes)));
240 if (buffer) {
241 memcpy(buffer.get(), aStr.get(), nbytes);
243 return buffer;
246 static bool ToJSString(JSContext* aCx, const nsACString& aStr,
247 JS::MutableHandle<JSString*> aJSString) {
248 if (aStr.IsVoid()) {
249 aJSString.set(nullptr);
250 return true;
252 JS::Rooted<JS::Value> res(aCx);
253 if (xpc::NonVoidStringToJsval(aCx, NS_ConvertUTF8toUTF16(aStr), &res)) {
254 aJSString.set(res.toString());
255 return true;
257 return false;
260 bool ClonedErrorHolder::ToErrorValue(JSContext* aCx,
261 JS::MutableHandle<JS::Value> aResult) {
262 JS::Rooted<JS::Value> stackVal(aCx);
263 JS::Rooted<JSObject*> stack(aCx);
265 IgnoredErrorResult rv;
266 mStack.Read(xpc::CurrentNativeGlobal(aCx), aCx, &stackVal, rv);
267 // Note: We continue even if reading the stack fails, since we can still
268 // produce a useful error object even without a stack. That said, if decoding
269 // the stack fails, there's a pretty good chance that the rest of the message
270 // is corrupt, and there's no telling how useful the final result will
271 // actually be.
272 if (!rv.Failed() && stackVal.isObject()) {
273 stack = &stackVal.toObject();
274 // Make sure that this is really a saved frame. This mainly ensures that
275 // malicious code on the child side can't trigger a memory exploit by
276 // sending an incompatible data type, but also protects against potential
277 // issues like a cross-compartment wrapper being unexpectedly cut.
278 if (!js::IsSavedFrame(stack)) {
279 stack = nullptr;
283 if (mType == Type::JSError) {
284 JS::Rooted<JSString*> filename(aCx);
285 JS::Rooted<JSString*> message(aCx);
287 // For some unknown reason, we can end up with a void string in mFilename,
288 // which will cause filename to be null, which causes JS::CreateError() to
289 // crash. Make this code against robust against this by treating void
290 // strings as the empty string.
291 if (mFilename.IsVoid()) {
292 mFilename.Assign(""_ns);
295 // When fuzzing, we can also end up with the message to be null,
296 // so we should handle that case as well.
297 if (mMessage.IsVoid()) {
298 mMessage.Assign(""_ns);
301 if (!ToJSString(aCx, mFilename, &filename) ||
302 !ToJSString(aCx, mMessage, &message)) {
303 return false;
305 if (!JS::CreateError(aCx, mExnType, stack, filename, mLineNumber, mColumn,
306 nullptr, message, JS::NothingHandleValue, aResult)) {
307 return false;
310 if (!mSourceLine.IsVoid()) {
311 JS::Rooted<JSObject*> errObj(aCx, &aResult.toObject());
312 if (JSErrorReport* err = JS_ErrorFromException(aCx, errObj)) {
313 NS_ConvertUTF8toUTF16 sourceLine(mSourceLine);
314 // Because this string ends up being consumed as an nsDependentString
315 // in nsXPCComponents_Utils::ReportError, this needs to be a null
316 // terminated string.
318 // See Bug 1699569.
319 if (mTokenOffset >= sourceLine.Length()) {
320 // Corrupt data, leave linebuf unset.
321 } else if (JS::UniqueTwoByteChars buffer =
322 ToNullTerminatedJSStringBuffer(aCx, sourceLine)) {
323 err->initOwnedLinebuf(buffer.release(), sourceLine.Length(),
324 mTokenOffset);
325 } else {
326 // Just ignore OOM and continue if the string copy failed.
327 JS_ClearPendingException(aCx);
332 return true;
335 nsCOMPtr<nsIStackFrame> frame(exceptions::CreateStack(aCx, stack));
337 RefPtr<Exception> exn;
338 if (mType == Type::Exception) {
339 exn = new Exception(mMessage, mResult, mName, frame, nullptr);
340 } else {
341 MOZ_ASSERT(mType == Type::DOMException);
342 exn = new DOMException(mResult, mMessage, mName, mCode, frame);
345 return ToJSValue(aCx, exn, aResult);
348 bool ClonedErrorHolder::Holder::ReadStructuredCloneInternal(
349 JSContext* aCx, JSStructuredCloneReader* aReader) {
350 uint32_t length;
351 uint32_t version;
352 if (!JS_ReadUint32Pair(aReader, &length, &version)) {
353 return false;
355 if (length % 8 != 0) {
356 return false;
359 JSStructuredCloneData data(mStructuredCloneScope);
360 while (length) {
361 size_t size;
362 char* buffer = data.AllocateBytes(length, &size);
363 if (!buffer || !JS_ReadBytes(aReader, buffer, size)) {
364 return false;
366 length -= size;
369 mBuffer = MakeUnique<JSAutoStructuredCloneBuffer>(
370 mStructuredCloneScope, &StructuredCloneHolder::sCallbacks, this);
371 mBuffer->adopt(std::move(data), version, &StructuredCloneHolder::sCallbacks);
372 return true;