Bug 1761357 [wpt PR 33355] - Fix #33204: Move Safari stable runs to Big Sur, a=testonly
[gecko.git] / ipc / mscom / Utils.cpp
blob377bbeba5c8128882b469e28f13898982e8e27ff
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 #if defined(MOZILLA_INTERNAL_API)
8 # include "MainThreadUtils.h"
9 # include "mozilla/dom/ContentChild.h"
10 #endif
12 #if defined(ACCESSIBILITY)
13 # include "mozilla/mscom/Registration.h"
14 # if defined(MOZILLA_INTERNAL_API)
15 # include "nsTArray.h"
16 # endif
17 #endif
19 #include "mozilla/ArrayUtils.h"
20 #include "mozilla/mscom/COMWrappers.h"
21 #include "mozilla/DebugOnly.h"
22 #include "mozilla/mscom/Objref.h"
23 #include "mozilla/mscom/Utils.h"
24 #include "mozilla/RefPtr.h"
25 #include "mozilla/WindowsVersion.h"
27 #include <objidl.h>
28 #include <shlwapi.h>
29 #include <winnt.h>
31 #include <utility>
33 #if defined(_MSC_VER)
34 extern "C" IMAGE_DOS_HEADER __ImageBase;
35 #endif
37 namespace mozilla {
38 namespace mscom {
40 bool IsCOMInitializedOnCurrentThread() {
41 APTTYPE aptType;
42 APTTYPEQUALIFIER aptTypeQualifier;
43 HRESULT hr = wrapped::CoGetApartmentType(&aptType, &aptTypeQualifier);
44 return hr != CO_E_NOTINITIALIZED;
47 bool IsCurrentThreadMTA() {
48 APTTYPE aptType;
49 APTTYPEQUALIFIER aptTypeQualifier;
50 HRESULT hr = wrapped::CoGetApartmentType(&aptType, &aptTypeQualifier);
51 if (FAILED(hr)) {
52 return false;
55 return aptType == APTTYPE_MTA;
58 bool IsCurrentThreadExplicitMTA() {
59 APTTYPE aptType;
60 APTTYPEQUALIFIER aptTypeQualifier;
61 HRESULT hr = wrapped::CoGetApartmentType(&aptType, &aptTypeQualifier);
62 if (FAILED(hr)) {
63 return false;
66 return aptType == APTTYPE_MTA &&
67 aptTypeQualifier != APTTYPEQUALIFIER_IMPLICIT_MTA;
70 bool IsCurrentThreadImplicitMTA() {
71 APTTYPE aptType;
72 APTTYPEQUALIFIER aptTypeQualifier;
73 HRESULT hr = wrapped::CoGetApartmentType(&aptType, &aptTypeQualifier);
74 if (FAILED(hr)) {
75 return false;
78 return aptType == APTTYPE_MTA &&
79 aptTypeQualifier == APTTYPEQUALIFIER_IMPLICIT_MTA;
82 #if defined(MOZILLA_INTERNAL_API)
83 bool IsCurrentThreadNonMainMTA() {
84 if (NS_IsMainThread()) {
85 return false;
88 return IsCurrentThreadMTA();
90 #endif // defined(MOZILLA_INTERNAL_API)
92 bool IsProxy(IUnknown* aUnknown) {
93 if (!aUnknown) {
94 return false;
97 // Only proxies implement this interface, so if it is present then we must
98 // be dealing with a proxied object.
99 RefPtr<IClientSecurity> clientSecurity;
100 HRESULT hr = aUnknown->QueryInterface(IID_IClientSecurity,
101 (void**)getter_AddRefs(clientSecurity));
102 if (SUCCEEDED(hr) || hr == RPC_E_WRONG_THREAD) {
103 return true;
105 return false;
108 bool IsValidGUID(REFGUID aCheckGuid) {
109 // This function determines whether or not aCheckGuid conforms to RFC4122
110 // as it applies to Microsoft COM.
112 BYTE variant = aCheckGuid.Data4[0];
113 if (!(variant & 0x80)) {
114 // NCS Reserved
115 return false;
117 if ((variant & 0xE0) == 0xE0) {
118 // Reserved for future use
119 return false;
121 if ((variant & 0xC0) == 0xC0) {
122 // Microsoft Reserved.
123 return true;
126 BYTE version = HIBYTE(aCheckGuid.Data3) >> 4;
127 // Other versions are specified in RFC4122 but these are the two used by COM.
128 return version == 1 || version == 4;
131 uintptr_t GetContainingModuleHandle() {
132 HMODULE thisModule = nullptr;
133 #if defined(_MSC_VER)
134 thisModule = reinterpret_cast<HMODULE>(&__ImageBase);
135 #else
136 if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
137 GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
138 reinterpret_cast<LPCTSTR>(&GetContainingModuleHandle),
139 &thisModule)) {
140 return 0;
142 #endif
143 return reinterpret_cast<uintptr_t>(thisModule);
146 namespace detail {
148 long BuildRegGuidPath(REFGUID aGuid, const GuidType aGuidType, wchar_t* aBuf,
149 const size_t aBufLen) {
150 constexpr wchar_t kClsid[] = L"CLSID\\";
151 constexpr wchar_t kAppid[] = L"AppID\\";
152 constexpr wchar_t kSubkeyBase[] = L"SOFTWARE\\Classes\\";
154 // We exclude null terminators in these length calculations because we include
155 // the stringified GUID's null terminator at the end. Since kClsid and kAppid
156 // have identical lengths, we just choose one to compute this length.
157 constexpr size_t kSubkeyBaseLen = mozilla::ArrayLength(kSubkeyBase) - 1;
158 constexpr size_t kSubkeyLen =
159 kSubkeyBaseLen + mozilla::ArrayLength(kClsid) - 1;
160 // Guid length as formatted for the registry (including curlies and dashes),
161 // but excluding null terminator.
162 constexpr size_t kGuidLen = kGuidRegFormatCharLenInclNul - 1;
163 constexpr size_t kExpectedPathLenInclNul = kSubkeyLen + kGuidLen + 1;
165 if (aBufLen < kExpectedPathLenInclNul) {
166 // Buffer is too short
167 return E_INVALIDARG;
170 if (wcscpy_s(aBuf, aBufLen, kSubkeyBase)) {
171 return E_INVALIDARG;
174 const wchar_t* strGuidType = aGuidType == GuidType::CLSID ? kClsid : kAppid;
175 if (wcscat_s(aBuf, aBufLen, strGuidType)) {
176 return E_INVALIDARG;
179 int guidConversionResult =
180 ::StringFromGUID2(aGuid, &aBuf[kSubkeyLen], aBufLen - kSubkeyLen);
181 if (!guidConversionResult) {
182 return E_INVALIDARG;
185 return S_OK;
188 } // namespace detail
190 long CreateStream(const uint8_t* aInitBuf, const uint32_t aInitBufSize,
191 IStream** aOutStream) {
192 if (!aInitBufSize || !aOutStream) {
193 return E_INVALIDARG;
196 *aOutStream = nullptr;
198 HRESULT hr;
199 RefPtr<IStream> stream;
201 if (IsWin8OrLater()) {
202 // SHCreateMemStream is not safe for us to use until Windows 8. On older
203 // versions of Windows it is not thread-safe and it creates IStreams that do
204 // not support the full IStream API.
206 // If aInitBuf is null then initSize must be 0.
207 UINT initSize = aInitBuf ? aInitBufSize : 0;
208 stream = already_AddRefed<IStream>(::SHCreateMemStream(aInitBuf, initSize));
209 if (!stream) {
210 return E_OUTOFMEMORY;
213 if (!aInitBuf) {
214 // Now we'll set the required size
215 ULARGE_INTEGER newSize;
216 newSize.QuadPart = aInitBufSize;
217 hr = stream->SetSize(newSize);
218 if (FAILED(hr)) {
219 return hr;
222 } else {
223 HGLOBAL hglobal = ::GlobalAlloc(GMEM_MOVEABLE, aInitBufSize);
224 if (!hglobal) {
225 return HRESULT_FROM_WIN32(::GetLastError());
228 // stream takes ownership of hglobal if this call is successful
229 hr = ::CreateStreamOnHGlobal(hglobal, TRUE, getter_AddRefs(stream));
230 if (FAILED(hr)) {
231 ::GlobalFree(hglobal);
232 return hr;
235 // The default stream size is derived from ::GlobalSize(hglobal), which due
236 // to rounding may be larger than aInitBufSize. We forcibly set the correct
237 // stream size here.
238 ULARGE_INTEGER streamSize;
239 streamSize.QuadPart = aInitBufSize;
240 hr = stream->SetSize(streamSize);
241 if (FAILED(hr)) {
242 return hr;
245 if (aInitBuf) {
246 ULONG bytesWritten;
247 hr = stream->Write(aInitBuf, aInitBufSize, &bytesWritten);
248 if (FAILED(hr)) {
249 return hr;
252 if (bytesWritten != aInitBufSize) {
253 return E_UNEXPECTED;
258 // Ensure that the stream is rewound
259 LARGE_INTEGER streamOffset;
260 streamOffset.QuadPart = 0LL;
261 hr = stream->Seek(streamOffset, STREAM_SEEK_SET, nullptr);
262 if (FAILED(hr)) {
263 return hr;
266 stream.forget(aOutStream);
267 return S_OK;
270 long CopySerializedProxy(IStream* aInStream, IStream** aOutStream) {
271 if (!aInStream || !aOutStream) {
272 return E_INVALIDARG;
275 *aOutStream = nullptr;
277 uint32_t desiredStreamSize = GetOBJREFSize(WrapNotNull(aInStream));
278 if (!desiredStreamSize) {
279 return E_INVALIDARG;
282 RefPtr<IStream> stream;
283 HRESULT hr = CreateStream(nullptr, desiredStreamSize, getter_AddRefs(stream));
284 if (FAILED(hr)) {
285 return hr;
288 ULARGE_INTEGER numBytesToCopy;
289 numBytesToCopy.QuadPart = desiredStreamSize;
290 hr = aInStream->CopyTo(stream, numBytesToCopy, nullptr, nullptr);
291 if (FAILED(hr)) {
292 return hr;
295 LARGE_INTEGER seekTo;
296 seekTo.QuadPart = 0LL;
297 hr = stream->Seek(seekTo, STREAM_SEEK_SET, nullptr);
298 if (FAILED(hr)) {
299 return hr;
302 stream.forget(aOutStream);
303 return S_OK;
306 #if defined(MOZILLA_INTERNAL_API)
308 void GUIDToString(REFGUID aGuid, nsAString& aOutString) {
309 // This buffer length is long enough to hold a GUID string that is formatted
310 // to include curly braces and dashes.
311 const int kBufLenWithNul = 39;
312 aOutString.SetLength(kBufLenWithNul);
313 int result = StringFromGUID2(aGuid, char16ptr_t(aOutString.BeginWriting()),
314 kBufLenWithNul);
315 MOZ_ASSERT(result);
316 if (result) {
317 // Truncate the terminator
318 aOutString.SetLength(result - 1);
322 // Undocumented IIDs that are relevant for diagnostic purposes
323 static const IID IID_ISCMLocalActivator = {
324 0x00000136,
325 0x0000,
326 0x0000,
327 {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}};
328 static const IID IID_IRundown = {
329 0x00000134,
330 0x0000,
331 0x0000,
332 {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}};
333 static const IID IID_IRemUnknown = {
334 0x00000131,
335 0x0000,
336 0x0000,
337 {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}};
338 static const IID IID_IRemUnknown2 = {
339 0x00000143,
340 0x0000,
341 0x0000,
342 {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}};
344 struct IIDToLiteralMapEntry {
345 constexpr IIDToLiteralMapEntry(REFIID aIid, nsLiteralCString&& aStr)
346 : mIid(aIid), mStr(std::forward<nsLiteralCString>(aStr)) {}
348 REFIID mIid;
349 const nsLiteralCString mStr;
353 * Given the name of an interface, the IID_ENTRY macro generates a pair
354 * containing a reference to the interface ID and a stringified version of
355 * the interface name.
357 * For example:
359 * {IID_ENTRY(IUnknown)}
360 * is expanded to:
361 * {IID_IUnknown, "IUnknown"_ns}
364 // clang-format off
365 # define IID_ENTRY_STRINGIFY(iface) #iface##_ns
366 # define IID_ENTRY(iface) IID_##iface, IID_ENTRY_STRINGIFY(iface)
367 // clang-format on
369 // Mapping of selected IIDs to friendly, human readable descriptions for each
370 // interface.
371 static constexpr IIDToLiteralMapEntry sIidDiagStrs[] = {
372 {IID_ENTRY(IUnknown)},
373 {IID_IRemUnknown, "cross-apartment IUnknown"_ns},
374 {IID_IRundown, "cross-apartment object management"_ns},
375 {IID_ISCMLocalActivator, "out-of-process object instantiation"_ns},
376 {IID_IRemUnknown2, "cross-apartment IUnknown"_ns}};
378 # undef IID_ENTRY
379 # undef IID_ENTRY_STRINGIFY
381 void DiagnosticNameForIID(REFIID aIid, nsACString& aOutString) {
382 // If the IID matches something in sIidDiagStrs, output its string.
383 for (const auto& curEntry : sIidDiagStrs) {
384 if (curEntry.mIid == aIid) {
385 aOutString.Assign(curEntry.mStr);
386 return;
390 // Otherwise just convert the IID to string form and output that.
391 nsAutoString strIid;
392 GUIDToString(aIid, strIid);
394 aOutString.AssignLiteral("IID ");
395 AppendUTF16toUTF8(strIid, aOutString);
398 #else
400 void GUIDToString(REFGUID aGuid,
401 wchar_t (&aOutBuf)[kGuidRegFormatCharLenInclNul]) {
402 DebugOnly<int> result =
403 ::StringFromGUID2(aGuid, aOutBuf, ArrayLength(aOutBuf));
404 MOZ_ASSERT(result);
407 #endif // defined(MOZILLA_INTERNAL_API)
409 #if defined(ACCESSIBILITY)
411 static bool IsVtableIndexFromParentInterface(TYPEATTR* aTypeAttr,
412 unsigned long aVtableIndex) {
413 MOZ_ASSERT(aTypeAttr);
415 // This is the number of functions declared in this interface (excluding
416 // parent interfaces).
417 unsigned int numExclusiveFuncs = aTypeAttr->cFuncs;
419 // This is the number of vtable entries (which includes parent interfaces).
420 // TYPEATTR::cbSizeVft is the entire vtable size in bytes, so we need to
421 // divide in order to compute the number of entries.
422 unsigned int numVtblEntries = aTypeAttr->cbSizeVft / sizeof(void*);
424 // This is the index of the first entry in the vtable that belongs to this
425 // interface and not a parent.
426 unsigned int firstVtblIndex = numVtblEntries - numExclusiveFuncs;
428 // If aVtableIndex is less than firstVtblIndex, then we're asking for an
429 // index that may belong to a parent interface.
430 return aVtableIndex < firstVtblIndex;
433 bool IsVtableIndexFromParentInterface(REFIID aInterface,
434 unsigned long aVtableIndex) {
435 RefPtr<ITypeInfo> typeInfo;
436 if (!RegisteredProxy::Find(aInterface, getter_AddRefs(typeInfo))) {
437 return false;
440 TYPEATTR* typeAttr = nullptr;
441 HRESULT hr = typeInfo->GetTypeAttr(&typeAttr);
442 if (FAILED(hr)) {
443 return false;
446 bool result = IsVtableIndexFromParentInterface(typeAttr, aVtableIndex);
448 typeInfo->ReleaseTypeAttr(typeAttr);
449 return result;
452 # if defined(MOZILLA_INTERNAL_API)
454 bool IsCallerExternalProcess() {
455 MOZ_ASSERT(XRE_IsContentProcess());
458 * CoGetCallerTID() gives us the caller's thread ID when that thread resides
459 * in a single-threaded apartment. Since our chrome main thread does live
460 * inside an STA, we will therefore be able to check whether the caller TID
461 * equals our chrome main thread TID. This enables us to distinguish
462 * between our chrome thread vs other out-of-process callers. We check for
463 * S_FALSE to ensure that the caller is a different process from ours, which
464 * is the only scenario that we care about.
466 DWORD callerTid;
467 if (::CoGetCallerTID(&callerTid) != S_FALSE) {
468 return false;
471 // Now check whether the caller is our parent process main thread.
472 const DWORD parentMainTid =
473 dom::ContentChild::GetSingleton()->GetChromeMainThreadId();
474 return callerTid != parentMainTid;
477 bool IsInterfaceEqualToOrInheritedFrom(REFIID aInterface, REFIID aFrom,
478 unsigned long aVtableIndexHint) {
479 if (aInterface == aFrom) {
480 return true;
483 // We expect this array to be length 1 but that is not guaranteed by the API.
484 AutoTArray<RefPtr<ITypeInfo>, 1> typeInfos;
486 // Grab aInterface's ITypeInfo so that we may obtain information about its
487 // inheritance hierarchy.
488 RefPtr<ITypeInfo> typeInfo;
489 if (RegisteredProxy::Find(aInterface, getter_AddRefs(typeInfo))) {
490 typeInfos.AppendElement(std::move(typeInfo));
494 * The main loop of this function searches the hierarchy of aInterface's
495 * parent interfaces, searching for aFrom.
497 while (!typeInfos.IsEmpty()) {
498 RefPtr<ITypeInfo> curTypeInfo(typeInfos.PopLastElement());
500 TYPEATTR* typeAttr = nullptr;
501 HRESULT hr = curTypeInfo->GetTypeAttr(&typeAttr);
502 if (FAILED(hr)) {
503 break;
506 bool isFromParentVtable =
507 IsVtableIndexFromParentInterface(typeAttr, aVtableIndexHint);
508 WORD numParentInterfaces = typeAttr->cImplTypes;
510 curTypeInfo->ReleaseTypeAttr(typeAttr);
511 typeAttr = nullptr;
513 if (!isFromParentVtable) {
514 // The vtable index cannot belong to this interface (otherwise the IIDs
515 // would already have matched and we would have returned true). Since we
516 // now also know that the vtable index cannot possibly be contained inside
517 // curTypeInfo's parent interface, there is no point searching any further
518 // up the hierarchy from here. OTOH we still should check any remaining
519 // entries that are still in the typeInfos array, so we continue.
520 continue;
523 for (WORD i = 0; i < numParentInterfaces; ++i) {
524 HREFTYPE refCookie;
525 hr = curTypeInfo->GetRefTypeOfImplType(i, &refCookie);
526 if (FAILED(hr)) {
527 continue;
530 RefPtr<ITypeInfo> nextTypeInfo;
531 hr = curTypeInfo->GetRefTypeInfo(refCookie, getter_AddRefs(nextTypeInfo));
532 if (FAILED(hr)) {
533 continue;
536 hr = nextTypeInfo->GetTypeAttr(&typeAttr);
537 if (FAILED(hr)) {
538 continue;
541 IID nextIid = typeAttr->guid;
543 nextTypeInfo->ReleaseTypeAttr(typeAttr);
544 typeAttr = nullptr;
546 if (nextIid == aFrom) {
547 return true;
550 typeInfos.AppendElement(std::move(nextTypeInfo));
554 return false;
557 # endif // defined(MOZILLA_INTERNAL_API)
559 #endif // defined(ACCESSIBILITY)
561 #if defined(MOZILLA_INTERNAL_API)
562 bool IsClassThreadAwareInprocServer(REFCLSID aClsid) {
563 nsAutoString strClsid;
564 GUIDToString(aClsid, strClsid);
566 nsAutoString inprocServerSubkey(u"CLSID\\"_ns);
567 inprocServerSubkey.Append(strClsid);
568 inprocServerSubkey.Append(u"\\InprocServer32"_ns);
570 // Of the possible values, "Apartment" is the longest, so we'll make this
571 // buffer large enough to hold that one.
572 wchar_t threadingModelBuf[ArrayLength(L"Apartment")] = {};
574 DWORD numBytes = sizeof(threadingModelBuf);
575 LONG result = ::RegGetValueW(HKEY_CLASSES_ROOT, inprocServerSubkey.get(),
576 L"ThreadingModel", RRF_RT_REG_SZ, nullptr,
577 threadingModelBuf, &numBytes);
578 if (result != ERROR_SUCCESS) {
579 // This will also handle the case where the CLSID is not an inproc server.
580 return false;
583 DWORD numChars = numBytes / sizeof(wchar_t);
584 // numChars includes the null terminator
585 if (numChars <= 1) {
586 return false;
589 nsDependentString threadingModel(threadingModelBuf, numChars - 1);
591 // Ensure that the threading model is one of the known values that indicates
592 // that the class can operate natively (ie, no proxying) inside a MTA.
593 return threadingModel.LowerCaseEqualsLiteral("both") ||
594 threadingModel.LowerCaseEqualsLiteral("free") ||
595 threadingModel.LowerCaseEqualsLiteral("neutral");
597 #endif // defined(MOZILLA_INTERNAL_API)
599 } // namespace mscom
600 } // namespace mozilla