1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* vim: set ts=8 sts=4 et sw=4 tw=99: */
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/. */
6 
7 /* Data conversion between native and JavaScript types. */
8 
9 #include "mozilla/ArrayUtils.h"
10 #include "mozilla/Range.h"
11 
12 #include "xpcprivate.h"
13 #include "nsIAtom.h"
14 #include "nsWrapperCache.h"
15 #include "nsJSUtils.h"
16 #include "nsQueryObject.h"
17 #include "WrapperFactory.h"
18 
19 #include "nsWrapperCacheInlines.h"
20 
21 #include "jsapi.h"
22 #include "jsfriendapi.h"
23 #include "js/CharacterEncoding.h"
24 #include "jsprf.h"
25 
26 #include "mozilla/dom/BindingUtils.h"
27 #include "mozilla/dom/DOMException.h"
28 #include "mozilla/dom/PrimitiveConversions.h"
29 #include "mozilla/dom/Promise.h"
30 #include "mozilla/jsipc/CrossProcessObjectWrappers.h"
31 
32 using namespace xpc;
33 using namespace mozilla;
34 using namespace mozilla::dom;
35 using namespace JS;
36 
37 //#define STRICT_CHECK_OF_UNICODE
38 #ifdef STRICT_CHECK_OF_UNICODE
39 #define ILLEGAL_RANGE(c) (0!=((c) & 0xFF80))
40 #else // STRICT_CHECK_OF_UNICODE
41 #define ILLEGAL_RANGE(c) (0!=((c) & 0xFF00))
42 #endif // STRICT_CHECK_OF_UNICODE
43 
44 #define ILLEGAL_CHAR_RANGE(c) (0!=((c) & 0x80))
45 
46 /***********************************************************/
47 
48 // static
49 bool
IsMethodReflectable(const XPTMethodDescriptor & info)50 XPCConvert::IsMethodReflectable(const XPTMethodDescriptor& info)
51 {
52     if (XPT_MD_IS_NOTXPCOM(info.flags) || XPT_MD_IS_HIDDEN(info.flags))
53         return false;
54 
55     for (int i = info.num_args-1; i >= 0; i--) {
56         const nsXPTParamInfo& param = info.params[i];
57         const nsXPTType& type = param.GetType();
58 
59         // Reflected methods can't use native types. All native types end up
60         // getting tagged as void*, so this check is easy.
61         if (type.TagPart() == nsXPTType::T_VOID)
62             return false;
63     }
64     return true;
65 }
66 
67 static JSObject*
UnwrapNativeCPOW(nsISupports * wrapper)68 UnwrapNativeCPOW(nsISupports* wrapper)
69 {
70     nsCOMPtr<nsIXPConnectWrappedJS> underware = do_QueryInterface(wrapper);
71     if (underware) {
72         JSObject* mainObj = underware->GetJSObject();
73         if (mainObj && mozilla::jsipc::IsWrappedCPOW(mainObj))
74             return mainObj;
75     }
76     return nullptr;
77 }
78 
79 /***************************************************************************/
80 
81 // static
82 bool
GetISupportsFromJSObject(JSObject * obj,nsISupports ** iface)83 XPCConvert::GetISupportsFromJSObject(JSObject* obj, nsISupports** iface)
84 {
85     const JSClass* jsclass = js::GetObjectJSClass(obj);
86     MOZ_ASSERT(jsclass, "obj has no class");
87     if (jsclass &&
88         (jsclass->flags & JSCLASS_HAS_PRIVATE) &&
89         (jsclass->flags & JSCLASS_PRIVATE_IS_NSISUPPORTS)) {
90         *iface = (nsISupports*) xpc_GetJSPrivate(obj);
91         return true;
92     }
93     *iface = UnwrapDOMObjectToISupports(obj);
94     return !!*iface;
95 }
96 
97 /***************************************************************************/
98 
99 // static
100 bool
NativeData2JS(MutableHandleValue d,const void * s,const nsXPTType & type,const nsID * iid,nsresult * pErr)101 XPCConvert::NativeData2JS(MutableHandleValue d, const void* s,
102                           const nsXPTType& type, const nsID* iid, nsresult* pErr)
103 {
104     NS_PRECONDITION(s, "bad param");
105 
106     AutoJSContext cx;
107     if (pErr)
108         *pErr = NS_ERROR_XPC_BAD_CONVERT_NATIVE;
109 
110     switch (type.TagPart()) {
111     case nsXPTType::T_I8    :
112         d.setInt32(*static_cast<const int8_t*>(s));
113         return true;
114     case nsXPTType::T_I16   :
115         d.setInt32(*static_cast<const int16_t*>(s));
116         return true;
117     case nsXPTType::T_I32   :
118         d.setInt32(*static_cast<const int32_t*>(s));
119         return true;
120     case nsXPTType::T_I64   :
121         d.setNumber(static_cast<double>(*static_cast<const int64_t*>(s)));
122         return true;
123     case nsXPTType::T_U8    :
124         d.setInt32(*static_cast<const uint8_t*>(s));
125         return true;
126     case nsXPTType::T_U16   :
127         d.setInt32(*static_cast<const uint16_t*>(s));
128         return true;
129     case nsXPTType::T_U32   :
130         d.setNumber(*static_cast<const uint32_t*>(s));
131         return true;
132     case nsXPTType::T_U64   :
133         d.setNumber(static_cast<double>(*static_cast<const uint64_t*>(s)));
134         return true;
135     case nsXPTType::T_FLOAT :
136         d.setNumber(*static_cast<const float*>(s));
137         return true;
138     case nsXPTType::T_DOUBLE:
139         d.setNumber(*static_cast<const double*>(s));
140         return true;
141     case nsXPTType::T_BOOL  :
142         d.setBoolean(*static_cast<const bool*>(s));
143         return true;
144     case nsXPTType::T_CHAR  :
145     {
146         char p = *static_cast<const char*>(s);
147 
148 #ifdef STRICT_CHECK_OF_UNICODE
149         MOZ_ASSERT(! ILLEGAL_CHAR_RANGE(p) , "passing non ASCII data");
150 #endif // STRICT_CHECK_OF_UNICODE
151 
152         JSString* str = JS_NewStringCopyN(cx, &p, 1);
153         if (!str)
154             return false;
155 
156         d.setString(str);
157         return true;
158     }
159     case nsXPTType::T_WCHAR :
160     {
161         char16_t p = *static_cast<const char16_t*>(s);
162 
163         JSString* str = JS_NewUCStringCopyN(cx, &p, 1);
164         if (!str)
165             return false;
166 
167         d.setString(str);
168         return true;
169     }
170 
171     case nsXPTType::T_JSVAL :
172     {
173         d.set(*static_cast<const Value*>(s));
174         return JS_WrapValue(cx, d);
175     }
176 
177     case nsXPTType::T_VOID:
178         XPC_LOG_ERROR(("XPCConvert::NativeData2JS : void* params not supported"));
179         return false;
180 
181     case nsXPTType::T_IID:
182     {
183         nsID* iid2 = *static_cast<nsID* const*>(s);
184         if (!iid2) {
185             d.setNull();
186             return true;
187         }
188 
189         RootedObject scope(cx, JS::CurrentGlobalOrNull(cx));
190         JSObject* obj = xpc_NewIDObject(cx, scope, *iid2);
191         if (!obj)
192             return false;
193 
194         d.setObject(*obj);
195         return true;
196     }
197 
198     case nsXPTType::T_ASTRING:
199         // Fall through to T_DOMSTRING case
200 
201     case nsXPTType::T_DOMSTRING:
202     {
203         const nsAString* p = *static_cast<const nsAString* const*>(s);
204         if (!p || p->IsVoid()) {
205             d.setNull();
206             return true;
207         }
208 
209         nsStringBuffer* buf;
210         if (!XPCStringConvert::ReadableToJSVal(cx, *p, &buf, d))
211             return false;
212         if (buf)
213             buf->AddRef();
214         return true;
215     }
216 
217     case nsXPTType::T_CHAR_STR:
218     {
219         const char* p = *static_cast<const char* const*>(s);
220         if (!p) {
221             d.setNull();
222             return true;
223         }
224 
225 #ifdef STRICT_CHECK_OF_UNICODE
226         bool isAscii = true;
227         for (char* t = p; *t && isAscii; t++) {
228           if (ILLEGAL_CHAR_RANGE(*t))
229               isAscii = false;
230         }
231         MOZ_ASSERT(isAscii, "passing non ASCII data");
232 #endif // STRICT_CHECK_OF_UNICODE
233 
234         JSString* str = JS_NewStringCopyZ(cx, p);
235         if (!str)
236             return false;
237 
238         d.setString(str);
239         return true;
240     }
241 
242     case nsXPTType::T_WCHAR_STR:
243     {
244         const char16_t* p = *static_cast<const char16_t* const*>(s);
245         if (!p) {
246             d.setNull();
247             return true;
248         }
249 
250         JSString* str = JS_NewUCStringCopyZ(cx, p);
251         if (!str)
252             return false;
253 
254         d.setString(str);
255         return true;
256     }
257     case nsXPTType::T_UTF8STRING:
258     {
259         const nsACString* utf8String = *static_cast<const nsACString* const*>(s);
260 
261         if (!utf8String || utf8String->IsVoid()) {
262             d.setNull();
263             return true;
264         }
265 
266         if (utf8String->IsEmpty()) {
267             d.set(JS_GetEmptyStringValue(cx));
268             return true;
269         }
270 
271         const uint32_t len = CalcUTF8ToUnicodeLength(*utf8String);
272         // The cString is not empty at this point, but the calculated
273         // UTF-16 length is zero, meaning no valid conversion exists.
274         if (!len)
275             return false;
276 
277         const size_t buffer_size = (len + 1) * sizeof(char16_t);
278         char16_t* buffer =
279             static_cast<char16_t*>(JS_malloc(cx, buffer_size));
280         if (!buffer)
281             return false;
282 
283         uint32_t copied;
284         if (!UTF8ToUnicodeBuffer(*utf8String, buffer, &copied) ||
285             len != copied) {
286             // Copy or conversion during copy failed. Did not copy the
287             // whole string.
288             JS_free(cx, buffer);
289             return false;
290         }
291 
292         // JS_NewUCString takes ownership on success, i.e. a
293         // successful call will make it the responsiblity of the JS VM
294         // to free the buffer.
295         JSString* str = JS_NewUCString(cx, buffer, len);
296         if (!str) {
297             JS_free(cx, buffer);
298             return false;
299         }
300 
301         d.setString(str);
302         return true;
303     }
304     case nsXPTType::T_CSTRING:
305     {
306         const nsACString* cString = *static_cast<const nsACString* const*>(s);
307 
308         if (!cString || cString->IsVoid()) {
309             d.setNull();
310             return true;
311         }
312 
313         // c-strings (binary blobs) are deliberately not converted from
314         // UTF-8 to UTF-16. T_UTF8Sting is for UTF-8 encoded strings
315         // with automatic conversion.
316         JSString* str = JS_NewStringCopyN(cx, cString->Data(),
317                                           cString->Length());
318         if (!str)
319             return false;
320 
321         d.setString(str);
322         return true;
323     }
324 
325     case nsXPTType::T_INTERFACE:
326     case nsXPTType::T_INTERFACE_IS:
327     {
328         nsISupports* iface = *static_cast<nsISupports* const*>(s);
329         if (!iface) {
330             d.setNull();
331             return true;
332         }
333 
334         if (iid->Equals(NS_GET_IID(nsIVariant))) {
335             nsCOMPtr<nsIVariant> variant = do_QueryInterface(iface);
336             if (!variant)
337                 return false;
338 
339             return XPCVariant::VariantDataToJS(variant,
340                                                pErr, d);
341         }
342 
343         xpcObjectHelper helper(iface);
344         return NativeInterface2JSObject(d, nullptr, helper, iid, true, pErr);
345     }
346 
347     default:
348         NS_ERROR("bad type");
349         return false;
350     }
351     return true;
352 }
353 
354 /***************************************************************************/
355 
356 #ifdef DEBUG
357 static bool
CheckChar16InCharRange(char16_t c)358 CheckChar16InCharRange(char16_t c)
359 {
360     if (ILLEGAL_RANGE(c)) {
361         /* U+0080/U+0100 - U+FFFF data lost. */
362         static const size_t MSG_BUF_SIZE = 64;
363         char msg[MSG_BUF_SIZE];
364         snprintf(msg, MSG_BUF_SIZE, "char16_t out of char range; high bits of data lost: 0x%x", int(c));
365         NS_WARNING(msg);
366         return false;
367     }
368 
369     return true;
370 }
371 
372 template<typename CharT>
373 static void
CheckCharsInCharRange(const CharT * chars,size_t len)374 CheckCharsInCharRange(const CharT* chars, size_t len)
375 {
376     for (size_t i = 0; i < len; i++) {
377         if (!CheckChar16InCharRange(chars[i]))
378             break;
379     }
380 }
381 #endif
382 
383 template<typename T>
ConvertToPrimitive(JSContext * cx,HandleValue v,T * retval)384 bool ConvertToPrimitive(JSContext* cx, HandleValue v, T* retval)
385 {
386     return ValueToPrimitive<T, eDefault>(cx, v, retval);
387 }
388 
389 // static
390 bool
JSData2Native(void * d,HandleValue s,const nsXPTType & type,const nsID * iid,nsresult * pErr)391 XPCConvert::JSData2Native(void* d, HandleValue s,
392                           const nsXPTType& type,
393                           const nsID* iid,
394                           nsresult* pErr)
395 {
396     NS_PRECONDITION(d, "bad param");
397 
398     AutoJSContext cx;
399     if (pErr)
400         *pErr = NS_ERROR_XPC_BAD_CONVERT_JS;
401 
402     switch (type.TagPart()) {
403     case nsXPTType::T_I8     :
404         return ConvertToPrimitive(cx, s, static_cast<int8_t*>(d));
405     case nsXPTType::T_I16    :
406         return ConvertToPrimitive(cx, s, static_cast<int16_t*>(d));
407     case nsXPTType::T_I32    :
408         return ConvertToPrimitive(cx, s, static_cast<int32_t*>(d));
409     case nsXPTType::T_I64    :
410         return ConvertToPrimitive(cx, s, static_cast<int64_t*>(d));
411     case nsXPTType::T_U8     :
412         return ConvertToPrimitive(cx, s, static_cast<uint8_t*>(d));
413     case nsXPTType::T_U16    :
414         return ConvertToPrimitive(cx, s, static_cast<uint16_t*>(d));
415     case nsXPTType::T_U32    :
416         return ConvertToPrimitive(cx, s, static_cast<uint32_t*>(d));
417     case nsXPTType::T_U64    :
418         return ConvertToPrimitive(cx, s, static_cast<uint64_t*>(d));
419     case nsXPTType::T_FLOAT  :
420         return ConvertToPrimitive(cx, s, static_cast<float*>(d));
421     case nsXPTType::T_DOUBLE :
422         return ConvertToPrimitive(cx, s, static_cast<double*>(d));
423     case nsXPTType::T_BOOL   :
424         return ConvertToPrimitive(cx, s, static_cast<bool*>(d));
425     case nsXPTType::T_CHAR   :
426     {
427         JSString* str = ToString(cx, s);
428         if (!str) {
429             return false;
430         }
431 
432         char16_t ch;
433         if (JS_GetStringLength(str) == 0) {
434             ch = 0;
435         } else {
436             if (!JS_GetStringCharAt(cx, str, 0, &ch))
437                 return false;
438         }
439 #ifdef DEBUG
440         CheckChar16InCharRange(ch);
441 #endif
442         *((char*)d) = char(ch);
443         break;
444     }
445     case nsXPTType::T_WCHAR  :
446     {
447         JSString* str;
448         if (!(str = ToString(cx, s))) {
449             return false;
450         }
451         size_t length = JS_GetStringLength(str);
452         if (length == 0) {
453             *((uint16_t*)d) = 0;
454             break;
455         }
456 
457         char16_t ch;
458         if (!JS_GetStringCharAt(cx, str, 0, &ch))
459             return false;
460 
461         *((uint16_t*)d) = uint16_t(ch);
462         break;
463     }
464     case nsXPTType::T_JSVAL :
465         *((Value*)d) = s;
466         break;
467     case nsXPTType::T_VOID:
468         XPC_LOG_ERROR(("XPCConvert::JSData2Native : void* params not supported"));
469         NS_ERROR("void* params not supported");
470         return false;
471     case nsXPTType::T_IID:
472     {
473         const nsID* pid = nullptr;
474 
475         // There's no good reason to pass a null IID.
476         if (s.isNullOrUndefined()) {
477             if (pErr)
478                 *pErr = NS_ERROR_XPC_BAD_CONVERT_JS;
479             return false;
480         }
481 
482         if (!s.isObject() ||
483             (!(pid = xpc_JSObjectToID(cx, &s.toObject()))) ||
484             (!(pid = (const nsID*) nsMemory::Clone(pid, sizeof(nsID))))) {
485             return false;
486         }
487         *((const nsID**)d) = pid;
488         return true;
489     }
490 
491     case nsXPTType::T_ASTRING:
492     {
493         if (s.isUndefined()) {
494             (**((nsAString**)d)).SetIsVoid(true);
495             return true;
496         }
497         MOZ_FALLTHROUGH;
498     }
499     case nsXPTType::T_DOMSTRING:
500     {
501         if (s.isNull()) {
502             (**((nsAString**)d)).SetIsVoid(true);
503             return true;
504         }
505         size_t length = 0;
506         JSString* str = nullptr;
507         if (!s.isUndefined()) {
508             str = ToString(cx, s);
509             if (!str)
510                 return false;
511 
512             length = JS_GetStringLength(str);
513             if (!length) {
514                 (**((nsAString**)d)).Truncate();
515                 return true;
516             }
517         }
518 
519         nsAString* ws = *((nsAString**)d);
520 
521         if (!str) {
522             ws->AssignLiteral(u"undefined");
523         } else if (XPCStringConvert::IsDOMString(str)) {
524             // The characters represent an existing nsStringBuffer that
525             // was shared by XPCStringConvert::ReadableToJSVal.
526             const char16_t* chars = JS_GetTwoByteExternalStringChars(str);
527             if (chars[length] == '\0') {
528                 // Safe to share the buffer.
529                 nsStringBuffer::FromData((void*)chars)->ToString(length, *ws);
530             } else {
531                 // We have to copy to ensure null-termination.
532                 ws->Assign(chars, length);
533             }
534         } else if (XPCStringConvert::IsLiteral(str)) {
535             // The characters represent a literal char16_t string constant
536             // compiled into libxul, such as the string "undefined" above.
537             const char16_t* chars = JS_GetTwoByteExternalStringChars(str);
538             ws->AssignLiteral(chars, length);
539         } else {
540             if (!AssignJSString(cx, *ws, str))
541                 return false;
542         }
543         return true;
544     }
545 
546     case nsXPTType::T_CHAR_STR:
547     {
548         if (s.isUndefined() || s.isNull()) {
549             *((char**)d) = nullptr;
550             return true;
551         }
552 
553         JSString* str = ToString(cx, s);
554         if (!str) {
555             return false;
556         }
557 #ifdef DEBUG
558         if (JS_StringHasLatin1Chars(str)) {
559             size_t len;
560             AutoCheckCannotGC nogc;
561             const Latin1Char* chars = JS_GetLatin1StringCharsAndLength(cx, nogc, str, &len);
562             if (chars)
563                 CheckCharsInCharRange(chars, len);
564         } else {
565             size_t len;
566             AutoCheckCannotGC nogc;
567             const char16_t* chars = JS_GetTwoByteStringCharsAndLength(cx, nogc, str, &len);
568             if (chars)
569                 CheckCharsInCharRange(chars, len);
570         }
571 #endif // DEBUG
572         size_t length = JS_GetStringEncodingLength(cx, str);
573         if (length == size_t(-1)) {
574             return false;
575         }
576         char* buffer = static_cast<char*>(moz_xmalloc(length + 1));
577         if (!buffer) {
578             return false;
579         }
580         JS_EncodeStringToBuffer(cx, str, buffer, length);
581         buffer[length] = '\0';
582         *((void**)d) = buffer;
583         return true;
584     }
585 
586     case nsXPTType::T_WCHAR_STR:
587     {
588         JSString* str;
589 
590         if (s.isUndefined() || s.isNull()) {
591             *((char16_t**)d) = nullptr;
592             return true;
593         }
594 
595         if (!(str = ToString(cx, s))) {
596             return false;
597         }
598         int len = JS_GetStringLength(str);
599         int byte_len = (len+1)*sizeof(char16_t);
600         if (!(*((void**)d) = moz_xmalloc(byte_len))) {
601             // XXX should report error
602             return false;
603         }
604         mozilla::Range<char16_t> destChars(*((char16_t**)d), len + 1);
605         if (!JS_CopyStringChars(cx, destChars, str))
606             return false;
607         destChars[len] = 0;
608 
609         return true;
610     }
611 
612     case nsXPTType::T_UTF8STRING:
613     {
614         if (s.isNull() || s.isUndefined()) {
615             nsCString* rs = *((nsCString**)d);
616             rs->SetIsVoid(true);
617             return true;
618         }
619 
620         // The JS val is neither null nor void...
621         JSString* str = ToString(cx, s);
622         if (!str)
623             return false;
624 
625         size_t length = JS_GetStringLength(str);
626         if (!length) {
627             nsCString* rs = *((nsCString**)d);
628             rs->Truncate();
629             return true;
630         }
631 
632         JSFlatString* flat = JS_FlattenString(cx, str);
633         if (!flat)
634             return false;
635 
636         size_t utf8Length = JS::GetDeflatedUTF8StringLength(flat);
637         nsACString* rs = *((nsACString**)d);
638         rs->SetLength(utf8Length);
639 
640         JS::DeflateStringToUTF8Buffer(flat, mozilla::RangedPtr<char>(rs->BeginWriting(), utf8Length));
641 
642         return true;
643     }
644 
645     case nsXPTType::T_CSTRING:
646     {
647         if (s.isNull() || s.isUndefined()) {
648             nsACString* rs = *((nsACString**)d);
649             rs->SetIsVoid(true);
650             return true;
651         }
652 
653         // The JS val is neither null nor void...
654         JSString* str = ToString(cx, s);
655         if (!str) {
656             return false;
657         }
658 
659         size_t length = JS_GetStringEncodingLength(cx, str);
660         if (length == size_t(-1)) {
661             return false;
662         }
663 
664         if (!length) {
665             nsCString* rs = *((nsCString**)d);
666             rs->Truncate();
667             return true;
668         }
669 
670         nsACString* rs = *((nsACString**)d);
671         rs->SetLength(uint32_t(length));
672         if (rs->Length() != uint32_t(length)) {
673             return false;
674         }
675         JS_EncodeStringToBuffer(cx, str, rs->BeginWriting(), length);
676 
677         return true;
678     }
679 
680     case nsXPTType::T_INTERFACE:
681     case nsXPTType::T_INTERFACE_IS:
682     {
683         MOZ_ASSERT(iid,"can't do interface conversions without iid");
684 
685         if (iid->Equals(NS_GET_IID(nsIVariant))) {
686             nsCOMPtr<nsIVariant> variant = XPCVariant::newVariant(cx, s);
687             if (!variant)
688                 return false;
689 
690             variant.forget(static_cast<nsISupports**>(d));
691             return true;
692         } else if (iid->Equals(NS_GET_IID(nsIAtom)) && s.isString()) {
693             // We're trying to pass a string as an nsIAtom.  Let's atomize!
694             JSString* str = s.toString();
695             nsAutoJSString autoStr;
696             if (!autoStr.init(cx, str)) {
697                 if (pErr)
698                     *pErr = NS_ERROR_XPC_BAD_CONVERT_JS_NULL_REF;
699                 return false;
700             }
701             nsCOMPtr<nsIAtom> atom = NS_Atomize(autoStr);
702             atom.forget((nsISupports**)d);
703             return true;
704         }
705         //else ...
706 
707         if (s.isNullOrUndefined()) {
708             *((nsISupports**)d) = nullptr;
709             return true;
710         }
711 
712         // only wrap JSObjects
713         if (!s.isObject()) {
714             if (pErr && s.isInt32() && 0 == s.toInt32())
715                 *pErr = NS_ERROR_XPC_BAD_CONVERT_JS_ZERO_ISNOT_NULL;
716             return false;
717         }
718 
719         RootedObject src(cx, &s.toObject());
720         return JSObject2NativeInterface((void**)d, src, iid, nullptr, pErr);
721     }
722     default:
723         NS_ERROR("bad type");
724         return false;
725     }
726     return true;
727 }
728 
729 static inline bool
CreateHolderIfNeeded(HandleObject obj,MutableHandleValue d,nsIXPConnectJSObjectHolder ** dest)730 CreateHolderIfNeeded(HandleObject obj, MutableHandleValue d,
731                      nsIXPConnectJSObjectHolder** dest)
732 {
733     if (dest) {
734         if (!obj)
735             return false;
736         RefPtr<XPCJSObjectHolder> objHolder = new XPCJSObjectHolder(obj);
737         objHolder.forget(dest);
738     }
739 
740     d.setObjectOrNull(obj);
741 
742     return true;
743 }
744 
745 /***************************************************************************/
746 // static
747 bool
NativeInterface2JSObject(MutableHandleValue d,nsIXPConnectJSObjectHolder ** dest,xpcObjectHelper & aHelper,const nsID * iid,bool allowNativeWrapper,nsresult * pErr)748 XPCConvert::NativeInterface2JSObject(MutableHandleValue d,
749                                      nsIXPConnectJSObjectHolder** dest,
750                                      xpcObjectHelper& aHelper,
751                                      const nsID* iid,
752                                      bool allowNativeWrapper,
753                                      nsresult* pErr)
754 {
755     if (!iid)
756         iid = &NS_GET_IID(nsISupports);
757 
758     d.setNull();
759     if (dest)
760         *dest = nullptr;
761     if (!aHelper.Object())
762         return true;
763     if (pErr)
764         *pErr = NS_ERROR_XPC_BAD_CONVERT_NATIVE;
765 
766     // We used to have code here that unwrapped and simply exposed the
767     // underlying JSObject. That caused anomolies when JSComponents were
768     // accessed from other JS code - they didn't act like other xpconnect
769     // wrapped components. So, instead, we create "double wrapped" objects
770     // (that means an XPCWrappedNative around an nsXPCWrappedJS). This isn't
771     // optimal -- we could detect this and roll the functionality into a
772     // single wrapper, but the current solution is good enough for now.
773     AutoJSContext cx;
774     XPCWrappedNativeScope* xpcscope = ObjectScope(JS::CurrentGlobalOrNull(cx));
775     if (!xpcscope)
776         return false;
777 
778     // First, see if this object supports the wrapper cache.
779     // Note: If |cache->IsDOMBinding()| is true, then it means that the object
780     // implementing it doesn't want a wrapped native as its JS Object, but
781     // instead it provides its own proxy object. In that case, the object
782     // to use is found as cache->GetWrapper(). If that is null, then the
783     // object will create (and fill the cache) from its WrapObject call.
784     nsWrapperCache* cache = aHelper.GetWrapperCache();
785 
786     RootedObject flat(cx, cache ? cache->GetWrapper() : nullptr);
787     if (!flat && cache && cache->IsDOMBinding()) {
788         RootedObject global(cx, xpcscope->GetGlobalJSObject());
789         js::AssertSameCompartment(cx, global);
790         flat = cache->WrapObject(cx, nullptr);
791         if (!flat)
792             return false;
793     }
794     if (flat) {
795         if (allowNativeWrapper && !JS_WrapObject(cx, &flat))
796             return false;
797         return CreateHolderIfNeeded(flat, d, dest);
798     }
799 
800 #ifdef SPIDERMONKEY_PROMISE
801     if (iid->Equals(NS_GET_IID(nsISupports))) {
802         // Check for a Promise being returned via nsISupports.  In that
803         // situation, we want to dig out its underlying JS object and return
804         // that.
805         RefPtr<Promise> promise = do_QueryObject(aHelper.Object());
806         if (promise) {
807             flat = promise->PromiseObj();
808             if (!JS_WrapObject(cx, &flat))
809                 return false;
810             return CreateHolderIfNeeded(flat, d, dest);
811         }
812     }
813 #endif // SPIDERMONKEY_PROMISE
814 
815     // Don't double wrap CPOWs. This is a temporary measure for compatibility
816     // with objects that don't provide necessary QIs (such as objects under
817     // the new DOM bindings). We expect the other side of the CPOW to have
818     // the appropriate wrappers in place.
819     RootedObject cpow(cx, UnwrapNativeCPOW(aHelper.Object()));
820     if (cpow) {
821         if (!JS_WrapObject(cx, &cpow))
822             return false;
823         d.setObject(*cpow);
824         return true;
825     }
826 
827     // Go ahead and create an XPCWrappedNative for this object.
828     RefPtr<XPCNativeInterface> iface =
829         XPCNativeInterface::GetNewOrUsed(iid);
830     if (!iface)
831         return false;
832 
833     RefPtr<XPCWrappedNative> wrapper;
834     nsresult rv = XPCWrappedNative::GetNewOrUsed(aHelper, xpcscope, iface,
835                                                  getter_AddRefs(wrapper));
836     if (NS_FAILED(rv) && pErr)
837         *pErr = rv;
838 
839     // If creating the wrapped native failed, then return early.
840     if (NS_FAILED(rv) || !wrapper)
841         return false;
842 
843     // If we're not creating security wrappers, we can return the
844     // XPCWrappedNative as-is here.
845     flat = wrapper->GetFlatJSObject();
846     if (!allowNativeWrapper) {
847         d.setObjectOrNull(flat);
848         if (dest)
849             wrapper.forget(dest);
850         if (pErr)
851             *pErr = NS_OK;
852         return true;
853     }
854 
855     // The call to wrap here handles both cross-compartment and same-compartment
856     // security wrappers.
857     RootedObject original(cx, flat);
858     if (!JS_WrapObject(cx, &flat))
859         return false;
860 
861     d.setObjectOrNull(flat);
862 
863     if (dest) {
864         // The wrapper still holds the original flat object.
865         if (flat == original) {
866             wrapper.forget(dest);
867         } else {
868             if (!flat)
869                 return false;
870             RefPtr<XPCJSObjectHolder> objHolder = new XPCJSObjectHolder(flat);
871             objHolder.forget(dest);
872         }
873     }
874 
875     if (pErr)
876         *pErr = NS_OK;
877 
878     return true;
879 }
880 
881 /***************************************************************************/
882 
883 // static
884 bool
JSObject2NativeInterface(void ** dest,HandleObject src,const nsID * iid,nsISupports * aOuter,nsresult * pErr)885 XPCConvert::JSObject2NativeInterface(void** dest, HandleObject src,
886                                      const nsID* iid,
887                                      nsISupports* aOuter,
888                                      nsresult* pErr)
889 {
890     MOZ_ASSERT(dest, "bad param");
891     MOZ_ASSERT(src, "bad param");
892     MOZ_ASSERT(iid, "bad param");
893 
894     AutoJSContext cx;
895     JSAutoCompartment ac(cx, src);
896 
897     *dest = nullptr;
898      if (pErr)
899         *pErr = NS_ERROR_XPC_BAD_CONVERT_JS;
900 
901     nsISupports* iface;
902 
903     if (!aOuter) {
904         // Note that if we have a non-null aOuter then it means that we are
905         // forcing the creation of a wrapper even if the object *is* a
906         // wrappedNative or other wise has 'nsISupportness'.
907         // This allows wrapJSAggregatedToNative to work.
908 
909         // If we're looking at a security wrapper, see now if we're allowed to
910         // pass it to C++. If we are, then fall through to the code below. If
911         // we aren't, throw an exception eagerly.
912         //
913         // NB: It's very important that we _don't_ unwrap in the aOuter case,
914         // because the caller may explicitly want to create the XPCWrappedJS
915         // around a security wrapper. XBL does this with Xrays from the XBL
916         // scope - see nsBindingManager::GetBindingImplementation.
917         //
918         // It's also very important that "inner" be rooted here.
919         RootedObject inner(cx,
920                            js::CheckedUnwrap(src,
921                                              /* stopAtWindowProxy = */ false));
922         if (!inner) {
923             if (pErr)
924                 *pErr = NS_ERROR_XPC_SECURITY_MANAGER_VETO;
925             return false;
926         }
927 
928         // Is this really a native xpcom object with a wrapper?
929         XPCWrappedNative* wrappedNative = nullptr;
930         if (IS_WN_REFLECTOR(inner))
931             wrappedNative = XPCWrappedNative::Get(inner);
932         if (wrappedNative) {
933             iface = wrappedNative->GetIdentityObject();
934             return NS_SUCCEEDED(iface->QueryInterface(*iid, dest));
935         }
936         // else...
937 
938         // Deal with slim wrappers here.
939         if (GetISupportsFromJSObject(inner ? inner : src, &iface)) {
940             if (iface && NS_SUCCEEDED(iface->QueryInterface(*iid, dest))) {
941                 return true;
942             }
943 
944             // If that failed, and iid is for mozIDOMWindowProxy, we actually
945             // want the outer!
946             if (iid->Equals(NS_GET_IID(mozIDOMWindowProxy))) {
947                 if (nsCOMPtr<mozIDOMWindow> inner = do_QueryInterface(iface)) {
948                     iface = nsPIDOMWindowInner::From(inner)->GetOuterWindow();
949                     return NS_SUCCEEDED(iface->QueryInterface(*iid, dest));
950                 }
951             }
952 
953             return false;
954         }
955 
956 #ifdef SPIDERMONKEY_PROMISE
957         // Deal with Promises being passed as nsISupports.  In that situation we
958         // want to create a dom::Promise and use that.
959         if (iid->Equals(NS_GET_IID(nsISupports))) {
960             RootedObject innerObj(cx, inner);
961             if (IsPromiseObject(innerObj)) {
962                 nsIGlobalObject* glob = NativeGlobal(innerObj);
963                 RefPtr<Promise> p = Promise::CreateFromExisting(glob, innerObj);
964                 return p && NS_SUCCEEDED(p->QueryInterface(*iid, dest));
965             }
966         }
967 #endif // SPIDERMONKEY_PROMISE
968     }
969 
970     RefPtr<nsXPCWrappedJS> wrapper;
971     nsresult rv = nsXPCWrappedJS::GetNewOrUsed(src, *iid, getter_AddRefs(wrapper));
972     if (pErr)
973         *pErr = rv;
974 
975     if (NS_FAILED(rv) || !wrapper)
976         return false;
977 
978     // If the caller wanted to aggregate this JS object to a native,
979     // attach it to the wrapper. Note that we allow a maximum of one
980     // aggregated native for a given XPCWrappedJS.
981     if (aOuter)
982         wrapper->SetAggregatedNativeObject(aOuter);
983 
984     // We need to go through the QueryInterface logic to make this return
985     // the right thing for the various 'special' interfaces; e.g.
986     // nsIPropertyBag. We must use AggregatedQueryInterface in cases where
987     // there is an outer to avoid nasty recursion.
988     rv = aOuter ? wrapper->AggregatedQueryInterface(*iid, dest) :
989         wrapper->QueryInterface(*iid, dest);
990     if (pErr)
991         *pErr = rv;
992     return NS_SUCCEEDED(rv);
993 }
994 
995 /***************************************************************************/
996 /***************************************************************************/
997 
998 // static
999 nsresult
ConstructException(nsresult rv,const char * message,const char * ifaceName,const char * methodName,nsISupports * data,nsIException ** exceptn,JSContext * cx,Value * jsExceptionPtr)1000 XPCConvert::ConstructException(nsresult rv, const char* message,
1001                                const char* ifaceName, const char* methodName,
1002                                nsISupports* data,
1003                                nsIException** exceptn,
1004                                JSContext* cx,
1005                                Value* jsExceptionPtr)
1006 {
1007     MOZ_ASSERT(!cx == !jsExceptionPtr, "Expected cx and jsExceptionPtr to cooccur.");
1008 
1009     static const char format[] = "\'%s\' when calling method: [%s::%s]";
1010     const char * msg = message;
1011     nsXPIDLString xmsg;
1012     nsAutoCString sxmsg;
1013 
1014     nsCOMPtr<nsIScriptError> errorObject = do_QueryInterface(data);
1015     if (errorObject) {
1016         if (NS_SUCCEEDED(errorObject->GetMessageMoz(getter_Copies(xmsg)))) {
1017             CopyUTF16toUTF8(xmsg, sxmsg);
1018             msg = sxmsg.get();
1019         }
1020     }
1021     if (!msg)
1022         if (!nsXPCException::NameAndFormatForNSResult(rv, nullptr, &msg) || ! msg)
1023             msg = "<error>";
1024 
1025     nsCString msgStr(msg);
1026     if (ifaceName && methodName)
1027         msgStr.AppendPrintf(format, msg, ifaceName, methodName);
1028 
1029     RefPtr<Exception> e = new Exception(msgStr, rv, EmptyCString(), nullptr, data);
1030 
1031     if (cx && jsExceptionPtr) {
1032         e->StowJSVal(*jsExceptionPtr);
1033     }
1034 
1035     e.forget(exceptn);
1036     return NS_OK;
1037 }
1038 
1039 /********************************/
1040 
1041 class MOZ_STACK_CLASS AutoExceptionRestorer
1042 {
1043 public:
AutoExceptionRestorer(JSContext * cx,const Value & v)1044     AutoExceptionRestorer(JSContext* cx, const Value& v)
1045         : mContext(cx), tvr(cx, v)
1046     {
1047         JS_ClearPendingException(mContext);
1048     }
1049 
~AutoExceptionRestorer()1050     ~AutoExceptionRestorer()
1051     {
1052         JS_SetPendingException(mContext, tvr);
1053     }
1054 
1055 private:
1056     JSContext * const mContext;
1057     RootedValue tvr;
1058 };
1059 
1060 static nsresult
JSErrorToXPCException(const char * toStringResult,const char * ifaceName,const char * methodName,const JSErrorReport * report,nsIException ** exceptn)1061 JSErrorToXPCException(const char* toStringResult,
1062                       const char* ifaceName,
1063                       const char* methodName,
1064                       const JSErrorReport* report,
1065                       nsIException** exceptn)
1066 {
1067     AutoJSContext cx;
1068     nsresult rv = NS_ERROR_FAILURE;
1069     RefPtr<nsScriptError> data;
1070     if (report) {
1071         nsAutoString bestMessage;
1072         if (report && report->message()) {
1073             CopyUTF8toUTF16(report->message().c_str(), bestMessage);
1074         } else if (toStringResult) {
1075             CopyUTF8toUTF16(toStringResult, bestMessage);
1076         } else {
1077             bestMessage.AssignLiteral("JavaScript Error");
1078         }
1079 
1080         const char16_t* linebuf = report->linebuf();
1081 
1082         data = new nsScriptError();
1083         data->InitWithWindowID(
1084             bestMessage,
1085             NS_ConvertASCIItoUTF16(report->filename),
1086             linebuf ? nsDependentString(linebuf, report->linebufLength()) : EmptyString(),
1087             report->lineno,
1088             report->tokenOffset(), report->flags,
1089             NS_LITERAL_CSTRING("XPConnect JavaScript"),
1090             nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(cx));
1091     }
1092 
1093     if (data) {
1094         nsAutoCString formattedMsg;
1095         data->ToString(formattedMsg);
1096 
1097         rv = XPCConvert::ConstructException(NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS,
1098                                             formattedMsg.get(), ifaceName,
1099                                             methodName,
1100                                             static_cast<nsIScriptError*>(data.get()),
1101                                             exceptn, nullptr, nullptr);
1102     } else {
1103         rv = XPCConvert::ConstructException(NS_ERROR_XPC_JAVASCRIPT_ERROR,
1104                                             nullptr, ifaceName, methodName,
1105                                             nullptr, exceptn, nullptr, nullptr);
1106     }
1107     return rv;
1108 }
1109 
1110 // static
1111 nsresult
JSValToXPCException(MutableHandleValue s,const char * ifaceName,const char * methodName,nsIException ** exceptn)1112 XPCConvert::JSValToXPCException(MutableHandleValue s,
1113                                 const char* ifaceName,
1114                                 const char* methodName,
1115                                 nsIException** exceptn)
1116 {
1117     AutoJSContext cx;
1118     AutoExceptionRestorer aer(cx, s);
1119 
1120     if (!s.isPrimitive()) {
1121         // we have a JSObject
1122         RootedObject obj(cx, s.toObjectOrNull());
1123 
1124         if (!obj) {
1125             NS_ERROR("when is an object not an object?");
1126             return NS_ERROR_FAILURE;
1127         }
1128 
1129         // is this really a native xpcom object with a wrapper?
1130         JSObject* unwrapped = js::CheckedUnwrap(obj, /* stopAtWindowProxy = */ false);
1131         if (!unwrapped)
1132             return NS_ERROR_XPC_SECURITY_MANAGER_VETO;
1133         if (nsCOMPtr<nsISupports> supports = UnwrapReflectorToISupports(unwrapped)) {
1134             nsCOMPtr<nsIException> iface = do_QueryInterface(supports);
1135             if (iface) {
1136                 // just pass through the exception (with extra ref and all)
1137                 nsCOMPtr<nsIException> temp = iface;
1138                 temp.forget(exceptn);
1139                 return NS_OK;
1140             } else {
1141                 // it is a wrapped native, but not an exception!
1142                 return ConstructException(NS_ERROR_XPC_JS_THREW_NATIVE_OBJECT,
1143                                           nullptr, ifaceName, methodName, supports,
1144                                           exceptn, nullptr, nullptr);
1145             }
1146         } else {
1147             // It is a JSObject, but not a wrapped native...
1148 
1149             // If it is an engine Error with an error report then let's
1150             // extract the report and build an xpcexception from that
1151             const JSErrorReport* report;
1152             if (nullptr != (report = JS_ErrorFromException(cx, obj))) {
1153                 JSAutoByteString toStringResult;
1154                 RootedString str(cx, ToString(cx, s));
1155                 if (str)
1156                     toStringResult.encodeUtf8(cx, str);
1157                 return JSErrorToXPCException(toStringResult.ptr(), ifaceName,
1158                                              methodName, report, exceptn);
1159             }
1160 
1161             // XXX we should do a check against 'js_ErrorClass' here and
1162             // do the right thing - even though it has no JSErrorReport,
1163             // The fact that it is a JSError exceptions means we can extract
1164             // particular info and our 'result' should reflect that.
1165 
1166             // otherwise we'll just try to convert it to a string
1167 
1168             JSString* str = ToString(cx, s);
1169             if (!str)
1170                 return NS_ERROR_FAILURE;
1171 
1172             JSAutoByteString strBytes(cx, str);
1173             if (!strBytes)
1174                 return NS_ERROR_FAILURE;
1175 
1176             return ConstructException(NS_ERROR_XPC_JS_THREW_JS_OBJECT,
1177                                       strBytes.ptr(), ifaceName, methodName,
1178                                       nullptr, exceptn, cx, s.address());
1179         }
1180     }
1181 
1182     if (s.isUndefined() || s.isNull()) {
1183         return ConstructException(NS_ERROR_XPC_JS_THREW_NULL,
1184                                   nullptr, ifaceName, methodName, nullptr,
1185                                   exceptn, cx, s.address());
1186     }
1187 
1188     if (s.isNumber()) {
1189         // lets see if it looks like an nsresult
1190         nsresult rv;
1191         double number;
1192         bool isResult = false;
1193 
1194         if (s.isInt32()) {
1195             rv = (nsresult) s.toInt32();
1196             if (NS_FAILED(rv))
1197                 isResult = true;
1198             else
1199                 number = (double) s.toInt32();
1200         } else {
1201             number = s.toDouble();
1202             if (number > 0.0 &&
1203                 number < (double)0xffffffff &&
1204                 0.0 == fmod(number,1)) {
1205                 // Visual Studio 9 doesn't allow casting directly from a
1206                 // double to an enumeration type, contrary to 5.2.9(10) of
1207                 // C++11, so add an intermediate cast.
1208                 rv = (nsresult)(uint32_t) number;
1209                 if (NS_FAILED(rv))
1210                     isResult = true;
1211             }
1212         }
1213 
1214         if (isResult)
1215             return ConstructException(rv, nullptr, ifaceName, methodName,
1216                                       nullptr, exceptn, cx, s.address());
1217         else {
1218             // XXX all this nsISupportsDouble code seems a little redundant
1219             // now that we're storing the Value in the exception...
1220             nsCOMPtr<nsISupportsDouble> data;
1221             nsCOMPtr<nsIComponentManager> cm;
1222             if (NS_FAILED(NS_GetComponentManager(getter_AddRefs(cm))) || !cm ||
1223                 NS_FAILED(cm->CreateInstanceByContractID(NS_SUPPORTS_DOUBLE_CONTRACTID,
1224                                                          nullptr,
1225                                                          NS_GET_IID(nsISupportsDouble),
1226                                                          getter_AddRefs(data))))
1227                 return NS_ERROR_FAILURE;
1228             data->SetData(number);
1229             rv = ConstructException(NS_ERROR_XPC_JS_THREW_NUMBER, nullptr,
1230                                     ifaceName, methodName, data, exceptn, cx, s.address());
1231             return rv;
1232         }
1233     }
1234 
1235     // otherwise we'll just try to convert it to a string
1236     // Note: e.g., bools get converted to JSStrings by this code.
1237 
1238     JSString* str = ToString(cx, s);
1239     if (str) {
1240         JSAutoByteString strBytes(cx, str);
1241         if (!!strBytes) {
1242             return ConstructException(NS_ERROR_XPC_JS_THREW_STRING,
1243                                       strBytes.ptr(), ifaceName, methodName,
1244                                       nullptr, exceptn, cx, s.address());
1245         }
1246     }
1247     return NS_ERROR_FAILURE;
1248 }
1249 
1250 /***************************************************************************/
1251 
1252 // array fun...
1253 
1254 #ifdef POPULATE
1255 #undef POPULATE
1256 #endif
1257 
1258 // static
1259 bool
NativeArray2JS(MutableHandleValue d,const void ** s,const nsXPTType & type,const nsID * iid,uint32_t count,nsresult * pErr)1260 XPCConvert::NativeArray2JS(MutableHandleValue d, const void** s,
1261                            const nsXPTType& type, const nsID* iid,
1262                            uint32_t count, nsresult* pErr)
1263 {
1264     NS_PRECONDITION(s, "bad param");
1265 
1266     AutoJSContext cx;
1267 
1268     // XXX add support for putting chars in a string rather than an array
1269 
1270     // XXX add support to indicate *which* array element was not convertable
1271 
1272     RootedObject array(cx, JS_NewArrayObject(cx, count));
1273     if (!array)
1274         return false;
1275 
1276     if (pErr)
1277         *pErr = NS_ERROR_XPC_BAD_CONVERT_NATIVE;
1278 
1279     uint32_t i;
1280     RootedValue current(cx, JS::NullValue());
1281 
1282 #define POPULATE(_t)                                                                    \
1283     PR_BEGIN_MACRO                                                                      \
1284         for (i = 0; i < count; i++) {                                                   \
1285             if (!NativeData2JS(&current, ((_t*)*s)+i, type, iid, pErr) ||               \
1286                 !JS_DefineElement(cx, array, i, current, JSPROP_ENUMERATE))             \
1287                 return false;                                                           \
1288         }                                                                               \
1289     PR_END_MACRO
1290 
1291     // XXX check IsPtr - esp. to handle array of nsID (as opposed to nsID*)
1292 
1293     switch (type.TagPart()) {
1294     case nsXPTType::T_I8            : POPULATE(int8_t);         break;
1295     case nsXPTType::T_I16           : POPULATE(int16_t);        break;
1296     case nsXPTType::T_I32           : POPULATE(int32_t);        break;
1297     case nsXPTType::T_I64           : POPULATE(int64_t);        break;
1298     case nsXPTType::T_U8            : POPULATE(uint8_t);        break;
1299     case nsXPTType::T_U16           : POPULATE(uint16_t);       break;
1300     case nsXPTType::T_U32           : POPULATE(uint32_t);       break;
1301     case nsXPTType::T_U64           : POPULATE(uint64_t);       break;
1302     case nsXPTType::T_FLOAT         : POPULATE(float);          break;
1303     case nsXPTType::T_DOUBLE        : POPULATE(double);         break;
1304     case nsXPTType::T_BOOL          : POPULATE(bool);           break;
1305     case nsXPTType::T_CHAR          : POPULATE(char);           break;
1306     case nsXPTType::T_WCHAR         : POPULATE(char16_t);       break;
1307     case nsXPTType::T_VOID          : NS_ERROR("bad type");     return false;
1308     case nsXPTType::T_IID           : POPULATE(nsID*);          break;
1309     case nsXPTType::T_DOMSTRING     : NS_ERROR("bad type");     return false;
1310     case nsXPTType::T_CHAR_STR      : POPULATE(char*);          break;
1311     case nsXPTType::T_WCHAR_STR     : POPULATE(char16_t*);      break;
1312     case nsXPTType::T_INTERFACE     : POPULATE(nsISupports*);   break;
1313     case nsXPTType::T_INTERFACE_IS  : POPULATE(nsISupports*);   break;
1314     case nsXPTType::T_UTF8STRING    : NS_ERROR("bad type");     return false;
1315     case nsXPTType::T_CSTRING       : NS_ERROR("bad type");     return false;
1316     case nsXPTType::T_ASTRING       : NS_ERROR("bad type");     return false;
1317     default                         : NS_ERROR("bad type");     return false;
1318     }
1319 
1320     if (pErr)
1321         *pErr = NS_OK;
1322     d.setObject(*array);
1323     return true;
1324 
1325 #undef POPULATE
1326 }
1327 
1328 
1329 
1330 // Check that the tag part of the type matches the type
1331 // of the array. If the check succeeds, check that the size
1332 // of the output does not exceed UINT32_MAX bytes. Allocate
1333 // the memory and copy the elements by memcpy.
1334 static bool
CheckTargetAndPopulate(const nsXPTType & type,uint8_t requiredType,size_t typeSize,uint32_t count,JSObject * tArr,void ** output,nsresult * pErr)1335 CheckTargetAndPopulate(const nsXPTType& type,
1336                        uint8_t requiredType,
1337                        size_t typeSize,
1338                        uint32_t count,
1339                        JSObject* tArr,
1340                        void** output,
1341                        nsresult* pErr)
1342 {
1343     // Check that the element type expected by the interface matches
1344     // the type of the elements in the typed array exactly, including
1345     // signedness.
1346     if (type.TagPart() != requiredType) {
1347         if (pErr)
1348             *pErr = NS_ERROR_XPC_BAD_CONVERT_JS;
1349 
1350         return false;
1351     }
1352 
1353     // Calulate the maximum number of elements that can fit in
1354     // UINT32_MAX bytes.
1355     size_t max = UINT32_MAX / typeSize;
1356 
1357     // This could overflow on 32-bit systems so check max first.
1358     size_t byteSize = count * typeSize;
1359     if (count > max || !(*output = moz_xmalloc(byteSize))) {
1360         if (pErr)
1361             *pErr = NS_ERROR_OUT_OF_MEMORY;
1362 
1363         return false;
1364     }
1365 
1366     JS::AutoCheckCannotGC nogc;
1367     bool isShared;
1368     void* buf = JS_GetArrayBufferViewData(tArr, &isShared, nogc);
1369 
1370     // Require opting in to shared memory - a future project.
1371     if (isShared) {
1372         if (pErr)
1373             *pErr = NS_ERROR_XPC_BAD_CONVERT_JS;
1374 
1375         return false;
1376     }
1377 
1378     memcpy(*output, buf, byteSize);
1379     return true;
1380 }
1381 
1382 // Fast conversion of typed arrays to native using memcpy.
1383 // No float or double canonicalization is done. Called by
1384 // JSarray2Native whenever a TypedArray is met. ArrayBuffers
1385 // are not accepted; create a properly typed array view on them
1386 // first. The element type of array must match the XPCOM
1387 // type in size, type and signedness exactly. As an exception,
1388 // Uint8ClampedArray is allowed for arrays of uint8_t. DataViews
1389 // are not supported.
1390 
1391 // static
1392 bool
JSTypedArray2Native(void ** d,JSObject * jsArray,uint32_t count,const nsXPTType & type,nsresult * pErr)1393 XPCConvert::JSTypedArray2Native(void** d,
1394                                 JSObject* jsArray,
1395                                 uint32_t count,
1396                                 const nsXPTType& type,
1397                                 nsresult* pErr)
1398 {
1399     MOZ_ASSERT(jsArray, "bad param");
1400     MOZ_ASSERT(d, "bad param");
1401     MOZ_ASSERT(JS_IsTypedArrayObject(jsArray), "not a typed array");
1402 
1403     // Check the actual length of the input array against the
1404     // given size_is.
1405     uint32_t len = JS_GetTypedArrayLength(jsArray);
1406     if (len < count) {
1407         if (pErr)
1408             *pErr = NS_ERROR_XPC_NOT_ENOUGH_ELEMENTS_IN_ARRAY;
1409 
1410         return false;
1411     }
1412 
1413     void* output = nullptr;
1414 
1415     switch (JS_GetArrayBufferViewType(jsArray)) {
1416     case js::Scalar::Int8:
1417         if (!CheckTargetAndPopulate(nsXPTType::T_I8, type,
1418                                     sizeof(int8_t), count,
1419                                     jsArray, &output, pErr)) {
1420             return false;
1421         }
1422         break;
1423 
1424     case js::Scalar::Uint8:
1425     case js::Scalar::Uint8Clamped:
1426         if (!CheckTargetAndPopulate(nsXPTType::T_U8, type,
1427                                     sizeof(uint8_t), count,
1428                                     jsArray, &output, pErr)) {
1429             return false;
1430         }
1431         break;
1432 
1433     case js::Scalar::Int16:
1434         if (!CheckTargetAndPopulate(nsXPTType::T_I16, type,
1435                                     sizeof(int16_t), count,
1436                                     jsArray, &output, pErr)) {
1437             return false;
1438         }
1439         break;
1440 
1441     case js::Scalar::Uint16:
1442         if (!CheckTargetAndPopulate(nsXPTType::T_U16, type,
1443                                     sizeof(uint16_t), count,
1444                                     jsArray, &output, pErr)) {
1445             return false;
1446         }
1447         break;
1448 
1449     case js::Scalar::Int32:
1450         if (!CheckTargetAndPopulate(nsXPTType::T_I32, type,
1451                                     sizeof(int32_t), count,
1452                                     jsArray, &output, pErr)) {
1453             return false;
1454         }
1455         break;
1456 
1457     case js::Scalar::Uint32:
1458         if (!CheckTargetAndPopulate(nsXPTType::T_U32, type,
1459                                     sizeof(uint32_t), count,
1460                                     jsArray, &output, pErr)) {
1461             return false;
1462         }
1463         break;
1464 
1465     case js::Scalar::Float32:
1466         if (!CheckTargetAndPopulate(nsXPTType::T_FLOAT, type,
1467                                     sizeof(float), count,
1468                                     jsArray, &output, pErr)) {
1469             return false;
1470         }
1471         break;
1472 
1473     case js::Scalar::Float64:
1474         if (!CheckTargetAndPopulate(nsXPTType::T_DOUBLE, type,
1475                                     sizeof(double), count,
1476                                     jsArray, &output, pErr)) {
1477             return false;
1478         }
1479         break;
1480 
1481     // Yet another array type was defined? It is not supported yet...
1482     default:
1483         if (pErr)
1484             *pErr = NS_ERROR_XPC_BAD_CONVERT_JS;
1485 
1486         return false;
1487     }
1488 
1489     *d = output;
1490     if (pErr)
1491         *pErr = NS_OK;
1492 
1493     return true;
1494 }
1495 
1496 // static
1497 bool
JSArray2Native(void ** d,HandleValue s,uint32_t count,const nsXPTType & type,const nsID * iid,nsresult * pErr)1498 XPCConvert::JSArray2Native(void** d, HandleValue s,
1499                            uint32_t count, const nsXPTType& type,
1500                            const nsID* iid, nsresult* pErr)
1501 {
1502     MOZ_ASSERT(d, "bad param");
1503 
1504     AutoJSContext cx;
1505 
1506     // XXX add support for getting chars from strings
1507 
1508     // XXX add support to indicate *which* array element was not convertable
1509 
1510     if (s.isNullOrUndefined()) {
1511         if (0 != count) {
1512             if (pErr)
1513                 *pErr = NS_ERROR_XPC_NOT_ENOUGH_ELEMENTS_IN_ARRAY;
1514             return false;
1515         }
1516 
1517         *d = nullptr;
1518         return true;
1519     }
1520 
1521     if (!s.isObject()) {
1522         if (pErr)
1523             *pErr = NS_ERROR_XPC_CANT_CONVERT_PRIMITIVE_TO_ARRAY;
1524         return false;
1525     }
1526 
1527     RootedObject jsarray(cx, &s.toObject());
1528 
1529     // If this is a typed array, then try a fast conversion with memcpy.
1530     if (JS_IsTypedArrayObject(jsarray)) {
1531         return JSTypedArray2Native(d, jsarray, count, type, pErr);
1532     }
1533 
1534     bool isArray;
1535     if (!JS_IsArrayObject(cx, jsarray, &isArray) || !isArray) {
1536         if (pErr)
1537             *pErr = NS_ERROR_XPC_CANT_CONVERT_OBJECT_TO_ARRAY;
1538         return false;
1539     }
1540 
1541     uint32_t len;
1542     if (!JS_GetArrayLength(cx, jsarray, &len) || len < count) {
1543         if (pErr)
1544             *pErr = NS_ERROR_XPC_NOT_ENOUGH_ELEMENTS_IN_ARRAY;
1545         return false;
1546     }
1547 
1548     if (pErr)
1549         *pErr = NS_ERROR_XPC_BAD_CONVERT_JS;
1550 
1551 #define POPULATE(_mode, _t)                                                    \
1552     PR_BEGIN_MACRO                                                             \
1553         cleanupMode = _mode;                                                   \
1554         size_t max = UINT32_MAX / sizeof(_t);                                  \
1555         if (count > max ||                                                     \
1556             nullptr == (array = moz_xmalloc(count * sizeof(_t)))) {            \
1557             if (pErr)                                                          \
1558                 *pErr = NS_ERROR_OUT_OF_MEMORY;                                \
1559             goto failure;                                                      \
1560         }                                                                      \
1561         for (initedCount = 0; initedCount < count; initedCount++) {            \
1562             if (!JS_GetElement(cx, jsarray, initedCount, &current) ||          \
1563                 !JSData2Native(((_t*)array)+initedCount, current, type,        \
1564                                iid, pErr))                                     \
1565                 goto failure;                                                  \
1566         }                                                                      \
1567     PR_END_MACRO
1568 
1569     // No Action, FRee memory, RElease object
1570     enum CleanupMode {na, fr, re};
1571 
1572     CleanupMode cleanupMode;
1573 
1574     void* array = nullptr;
1575     uint32_t initedCount;
1576     RootedValue current(cx);
1577 
1578     // XXX check IsPtr - esp. to handle array of nsID (as opposed to nsID*)
1579     // XXX make extra space at end of char* and wchar* and null termintate
1580 
1581     switch (type.TagPart()) {
1582     case nsXPTType::T_I8            : POPULATE(na, int8_t);         break;
1583     case nsXPTType::T_I16           : POPULATE(na, int16_t);        break;
1584     case nsXPTType::T_I32           : POPULATE(na, int32_t);        break;
1585     case nsXPTType::T_I64           : POPULATE(na, int64_t);        break;
1586     case nsXPTType::T_U8            : POPULATE(na, uint8_t);        break;
1587     case nsXPTType::T_U16           : POPULATE(na, uint16_t);       break;
1588     case nsXPTType::T_U32           : POPULATE(na, uint32_t);       break;
1589     case nsXPTType::T_U64           : POPULATE(na, uint64_t);       break;
1590     case nsXPTType::T_FLOAT         : POPULATE(na, float);          break;
1591     case nsXPTType::T_DOUBLE        : POPULATE(na, double);         break;
1592     case nsXPTType::T_BOOL          : POPULATE(na, bool);           break;
1593     case nsXPTType::T_CHAR          : POPULATE(na, char);           break;
1594     case nsXPTType::T_WCHAR         : POPULATE(na, char16_t);       break;
1595     case nsXPTType::T_VOID          : NS_ERROR("bad type");         goto failure;
1596     case nsXPTType::T_IID           : POPULATE(fr, nsID*);          break;
1597     case nsXPTType::T_DOMSTRING     : NS_ERROR("bad type");         goto failure;
1598     case nsXPTType::T_CHAR_STR      : POPULATE(fr, char*);          break;
1599     case nsXPTType::T_WCHAR_STR     : POPULATE(fr, char16_t*);      break;
1600     case nsXPTType::T_INTERFACE     : POPULATE(re, nsISupports*);   break;
1601     case nsXPTType::T_INTERFACE_IS  : POPULATE(re, nsISupports*);   break;
1602     case nsXPTType::T_UTF8STRING    : NS_ERROR("bad type");         goto failure;
1603     case nsXPTType::T_CSTRING       : NS_ERROR("bad type");         goto failure;
1604     case nsXPTType::T_ASTRING       : NS_ERROR("bad type");         goto failure;
1605     default                         : NS_ERROR("bad type");         goto failure;
1606     }
1607 
1608     *d = array;
1609     if (pErr)
1610         *pErr = NS_OK;
1611     return true;
1612 
1613 failure:
1614     // we may need to cleanup the partially filled array of converted stuff
1615     if (array) {
1616         if (cleanupMode == re) {
1617             nsISupports** a = (nsISupports**) array;
1618             for (uint32_t i = 0; i < initedCount; i++) {
1619                 nsISupports* p = a[i];
1620                 NS_IF_RELEASE(p);
1621             }
1622         } else if (cleanupMode == fr) {
1623             void** a = (void**) array;
1624             for (uint32_t i = 0; i < initedCount; i++) {
1625                 void* p = a[i];
1626                 if (p) free(p);
1627             }
1628         }
1629         free(array);
1630     }
1631 
1632     return false;
1633 
1634 #undef POPULATE
1635 }
1636 
1637 // static
1638 bool
NativeStringWithSize2JS(MutableHandleValue d,const void * s,const nsXPTType & type,uint32_t count,nsresult * pErr)1639 XPCConvert::NativeStringWithSize2JS(MutableHandleValue d, const void* s,
1640                                     const nsXPTType& type,
1641                                     uint32_t count,
1642                                     nsresult* pErr)
1643 {
1644     NS_PRECONDITION(s, "bad param");
1645 
1646     AutoJSContext cx;
1647     if (pErr)
1648         *pErr = NS_ERROR_XPC_BAD_CONVERT_NATIVE;
1649 
1650     switch (type.TagPart()) {
1651         case nsXPTType::T_PSTRING_SIZE_IS:
1652         {
1653             char* p = *((char**)s);
1654             if (!p)
1655                 break;
1656             JSString* str;
1657             if (!(str = JS_NewStringCopyN(cx, p, count)))
1658                 return false;
1659             d.setString(str);
1660             break;
1661         }
1662         case nsXPTType::T_PWSTRING_SIZE_IS:
1663         {
1664             char16_t* p = *((char16_t**)s);
1665             if (!p)
1666                 break;
1667             JSString* str;
1668             if (!(str = JS_NewUCStringCopyN(cx, p, count)))
1669                 return false;
1670             d.setString(str);
1671             break;
1672         }
1673         default:
1674             XPC_LOG_ERROR(("XPCConvert::NativeStringWithSize2JS : unsupported type"));
1675             return false;
1676     }
1677     return true;
1678 }
1679 
1680 // static
1681 bool
JSStringWithSize2Native(void * d,HandleValue s,uint32_t count,const nsXPTType & type,nsresult * pErr)1682 XPCConvert::JSStringWithSize2Native(void* d, HandleValue s,
1683                                     uint32_t count, const nsXPTType& type,
1684                                     nsresult* pErr)
1685 {
1686     NS_PRECONDITION(!s.isNull(), "bad param");
1687     NS_PRECONDITION(d, "bad param");
1688 
1689     AutoJSContext cx;
1690     uint32_t len;
1691 
1692     if (pErr)
1693         *pErr = NS_ERROR_XPC_BAD_CONVERT_NATIVE;
1694 
1695     switch (type.TagPart()) {
1696         case nsXPTType::T_PSTRING_SIZE_IS:
1697         {
1698             if (s.isUndefined() || s.isNull()) {
1699                 if (0 != count) {
1700                     if (pErr)
1701                         *pErr = NS_ERROR_XPC_NOT_ENOUGH_CHARS_IN_STRING;
1702                     return false;
1703                 }
1704                 if (0 != count) {
1705                     len = (count + 1) * sizeof(char);
1706                     if (!(*((void**)d) = moz_xmalloc(len)))
1707                         return false;
1708                     return true;
1709                 }
1710                 // else ...
1711 
1712                 *((char**)d) = nullptr;
1713                 return true;
1714             }
1715 
1716             JSString* str = ToString(cx, s);
1717             if (!str) {
1718                 return false;
1719             }
1720 
1721             size_t length = JS_GetStringEncodingLength(cx, str);
1722             if (length == size_t(-1)) {
1723                 return false;
1724             }
1725             if (length > count) {
1726                 if (pErr)
1727                     *pErr = NS_ERROR_XPC_NOT_ENOUGH_CHARS_IN_STRING;
1728                 return false;
1729             }
1730             len = uint32_t(length);
1731 
1732             if (len < count)
1733                 len = count;
1734 
1735             uint32_t alloc_len = (len + 1) * sizeof(char);
1736             char* buffer = static_cast<char*>(moz_xmalloc(alloc_len));
1737             if (!buffer) {
1738                 return false;
1739             }
1740             JS_EncodeStringToBuffer(cx, str, buffer, len);
1741             buffer[len] = '\0';
1742             *((char**)d) = buffer;
1743 
1744             return true;
1745         }
1746 
1747         case nsXPTType::T_PWSTRING_SIZE_IS:
1748         {
1749             JSString* str;
1750 
1751             if (s.isUndefined() || s.isNull()) {
1752                 if (0 != count) {
1753                     if (pErr)
1754                         *pErr = NS_ERROR_XPC_NOT_ENOUGH_CHARS_IN_STRING;
1755                     return false;
1756                 }
1757 
1758                 if (0 != count) {
1759                     len = (count + 1) * sizeof(char16_t);
1760                     if (!(*((void**)d) = moz_xmalloc(len)))
1761                         return false;
1762                     return true;
1763                 }
1764 
1765                 // else ...
1766                 *((const char16_t**)d) = nullptr;
1767                 return true;
1768             }
1769 
1770             if (!(str = ToString(cx, s))) {
1771                 return false;
1772             }
1773 
1774             len = JS_GetStringLength(str);
1775             if (len > count) {
1776                 if (pErr)
1777                     *pErr = NS_ERROR_XPC_NOT_ENOUGH_CHARS_IN_STRING;
1778                 return false;
1779             }
1780 
1781             len = count;
1782 
1783             uint32_t alloc_len = (len + 1) * sizeof(char16_t);
1784             if (!(*((void**)d) = moz_xmalloc(alloc_len))) {
1785                 // XXX should report error
1786                 return false;
1787             }
1788             mozilla::Range<char16_t> destChars(*((char16_t**)d), len + 1);
1789             if (!JS_CopyStringChars(cx, destChars, str))
1790                 return false;
1791             destChars[count] = 0;
1792 
1793             return true;
1794         }
1795         default:
1796             XPC_LOG_ERROR(("XPCConvert::JSStringWithSize2Native : unsupported type"));
1797             return false;
1798     }
1799 }
1800