| // Copyright 2010 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/win/scoped_variant.h" |
| |
| #include <wrl/client.h> |
| |
| #include <algorithm> |
| #include <functional> |
| |
| #include "base/check.h" |
| #include "base/logging.h" |
| #include "base/win/propvarutil.h" |
| #include "base/win/variant_conversions.h" |
| |
| namespace base { |
| namespace win { |
| |
| // Global, const instance of an empty variant. |
| const VARIANT ScopedVariant::kEmptyVariant = {{{VT_EMPTY}}}; |
| |
| ScopedVariant::ScopedVariant(ScopedVariant&& var) { |
| var_.vt = VT_EMPTY; |
| Reset(var.Release()); |
| } |
| |
| ScopedVariant::~ScopedVariant() { |
| static_assert(sizeof(ScopedVariant) == sizeof(VARIANT), "ScopedVariantSize"); |
| ::VariantClear(&var_); |
| } |
| |
| ScopedVariant::ScopedVariant(const wchar_t* str) { |
| var_.vt = VT_EMPTY; |
| Set(str); |
| } |
| |
| ScopedVariant::ScopedVariant(const wchar_t* str, UINT length) { |
| var_.vt = VT_BSTR; |
| var_.bstrVal = ::SysAllocStringLen(str, length); |
| } |
| |
| ScopedVariant::ScopedVariant(long value, VARTYPE vt) { // NOLINT(runtime/int) |
| var_.vt = vt; |
| var_.lVal = value; |
| } |
| |
| ScopedVariant::ScopedVariant(int value) { |
| var_.vt = VT_I4; |
| var_.lVal = value; |
| } |
| |
| ScopedVariant::ScopedVariant(bool value) { |
| var_.vt = VT_BOOL; |
| var_.boolVal = value ? VARIANT_TRUE : VARIANT_FALSE; |
| } |
| |
| ScopedVariant::ScopedVariant(double value, VARTYPE vt) { |
| DCHECK(vt == VT_R8 || vt == VT_DATE); |
| var_.vt = vt; |
| var_.dblVal = value; |
| } |
| |
| ScopedVariant::ScopedVariant(IDispatch* dispatch) { |
| var_.vt = VT_EMPTY; |
| Set(dispatch); |
| } |
| |
| ScopedVariant::ScopedVariant(IUnknown* unknown) { |
| var_.vt = VT_EMPTY; |
| Set(unknown); |
| } |
| |
| ScopedVariant::ScopedVariant(SAFEARRAY* safearray) { |
| var_.vt = VT_EMPTY; |
| Set(safearray); |
| } |
| |
| ScopedVariant::ScopedVariant(const VARIANT& var) { |
| var_.vt = VT_EMPTY; |
| Set(var); |
| } |
| |
| void ScopedVariant::Reset(const VARIANT& var) { |
| if (&var != &var_) { |
| ::VariantClear(&var_); |
| var_ = var; |
| } |
| } |
| |
| VARIANT ScopedVariant::Release() { |
| VARIANT var = var_; |
| var_.vt = VT_EMPTY; |
| return var; |
| } |
| |
| void ScopedVariant::Swap(ScopedVariant& var) { |
| VARIANT tmp = var_; |
| var_ = var.var_; |
| var.var_ = tmp; |
| } |
| |
| VARIANT* ScopedVariant::Receive() { |
| DCHECK(!IsLeakableVarType(var_.vt)) << "variant leak. type: " << var_.vt; |
| return &var_; |
| } |
| |
| VARIANT ScopedVariant::Copy() const { |
| VARIANT ret = {{{VT_EMPTY}}}; |
| ::VariantCopy(&ret, &var_); |
| return ret; |
| } |
| |
| int ScopedVariant::Compare(const VARIANT& other, bool ignore_case) const { |
| DCHECK(!V_ISARRAY(&var_)) |
| << "Comparison is not supported when |this| owns a SAFEARRAY"; |
| DCHECK(!V_ISARRAY(&other)) |
| << "Comparison is not supported when |other| owns a SAFEARRAY"; |
| |
| const bool this_is_empty = var_.vt == VT_EMPTY || var_.vt == VT_NULL; |
| const bool other_is_empty = other.vt == VT_EMPTY || other.vt == VT_NULL; |
| |
| // 1. VT_NULL and VT_EMPTY is always considered less-than any other VARTYPE. |
| if (this_is_empty) |
| return other_is_empty ? 0 : -1; |
| if (other_is_empty) |
| return 1; |
| |
| // 2. If both VARIANTS have either VT_UNKNOWN or VT_DISPATCH even if the |
| // VARTYPEs do not match, the address of its IID_IUnknown is compared to |
| // guarantee a logical ordering even though it is not a meaningful order. |
| // e.g. (a.Compare(b) != b.Compare(a)) unless (a == b). |
| const bool this_is_unknown = var_.vt == VT_UNKNOWN || var_.vt == VT_DISPATCH; |
| const bool other_is_unknown = |
| other.vt == VT_UNKNOWN || other.vt == VT_DISPATCH; |
| if (this_is_unknown && other_is_unknown) { |
| // https://docs.microsoft.com/en-us/windows/win32/com/rules-for-implementing-queryinterface |
| // Query IID_IUnknown to determine whether the two variants point |
| // to the same instance of an object |
| Microsoft::WRL::ComPtr<IUnknown> this_unknown; |
| Microsoft::WRL::ComPtr<IUnknown> other_unknown; |
| V_UNKNOWN(&var_)->QueryInterface(IID_PPV_ARGS(&this_unknown)); |
| V_UNKNOWN(&other)->QueryInterface(IID_PPV_ARGS(&other_unknown)); |
| if (this_unknown.Get() == other_unknown.Get()) |
| return 0; |
| // std::less for any pointer type yields a strict total order even if the |
| // built-in operator< does not. |
| return std::less<>{}(this_unknown.Get(), other_unknown.Get()) ? -1 : 1; |
| } |
| |
| // 3. If the VARTYPEs do not match, then the value of the VARTYPE is compared. |
| if (V_VT(&var_) != V_VT(&other)) |
| return (V_VT(&var_) < V_VT(&other)) ? -1 : 1; |
| |
| const VARTYPE shared_vartype = V_VT(&var_); |
| // 4. Comparing VT_BSTR values is a lexicographical comparison of the contents |
| // of the BSTR, taking into account |ignore_case|. |
| if (shared_vartype == VT_BSTR) { |
| ULONG flags = ignore_case ? NORM_IGNORECASE : 0; |
| HRESULT hr = |
| ::VarBstrCmp(V_BSTR(&var_), V_BSTR(&other), LOCALE_USER_DEFAULT, flags); |
| DCHECK(SUCCEEDED(hr) && hr != VARCMP_NULL) |
| << "unsupported variant comparison: " << var_.vt << " and " << other.vt; |
| |
| switch (hr) { |
| case VARCMP_LT: |
| return -1; |
| case VARCMP_GT: |
| case VARCMP_NULL: |
| return 1; |
| default: |
| return 0; |
| } |
| } |
| |
| // 5. Otherwise returns the lexicographical comparison of the values held by |
| // the two VARIANTS that share the same VARTYPE. |
| return ::VariantCompare(var_, other); |
| } |
| |
| void ScopedVariant::Set(const wchar_t* str) { |
| DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; |
| var_.vt = VT_BSTR; |
| var_.bstrVal = ::SysAllocString(str); |
| } |
| |
| void ScopedVariant::Set(int8_t i8) { |
| DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; |
| var_.vt = VT_I1; |
| var_.cVal = i8; |
| } |
| |
| void ScopedVariant::Set(uint8_t ui8) { |
| DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; |
| var_.vt = VT_UI1; |
| var_.bVal = ui8; |
| } |
| |
| void ScopedVariant::Set(int16_t i16) { |
| DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; |
| var_.vt = VT_I2; |
| var_.iVal = i16; |
| } |
| |
| void ScopedVariant::Set(uint16_t ui16) { |
| DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; |
| var_.vt = VT_UI2; |
| var_.uiVal = ui16; |
| } |
| |
| void ScopedVariant::Set(int32_t i32) { |
| DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; |
| var_.vt = VT_I4; |
| var_.lVal = i32; |
| } |
| |
| void ScopedVariant::Set(uint32_t ui32) { |
| DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; |
| var_.vt = VT_UI4; |
| var_.ulVal = ui32; |
| } |
| |
| void ScopedVariant::Set(int64_t i64) { |
| DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; |
| var_.vt = VT_I8; |
| var_.llVal = i64; |
| } |
| |
| void ScopedVariant::Set(uint64_t ui64) { |
| DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; |
| var_.vt = VT_UI8; |
| var_.ullVal = ui64; |
| } |
| |
| void ScopedVariant::Set(float r32) { |
| DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; |
| var_.vt = VT_R4; |
| var_.fltVal = r32; |
| } |
| |
| void ScopedVariant::Set(double r64) { |
| DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; |
| var_.vt = VT_R8; |
| var_.dblVal = r64; |
| } |
| |
| void ScopedVariant::SetDate(DATE date) { |
| DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; |
| var_.vt = VT_DATE; |
| var_.date = date; |
| } |
| |
| void ScopedVariant::Set(IDispatch* disp) { |
| DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; |
| var_.vt = VT_DISPATCH; |
| var_.pdispVal = disp; |
| if (disp) |
| disp->AddRef(); |
| } |
| |
| void ScopedVariant::Set(bool b) { |
| DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; |
| var_.vt = VT_BOOL; |
| var_.boolVal = b ? VARIANT_TRUE : VARIANT_FALSE; |
| } |
| |
| void ScopedVariant::Set(IUnknown* unk) { |
| DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; |
| var_.vt = VT_UNKNOWN; |
| var_.punkVal = unk; |
| if (unk) |
| unk->AddRef(); |
| } |
| |
| void ScopedVariant::Set(SAFEARRAY* array) { |
| DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; |
| if (SUCCEEDED(::SafeArrayGetVartype(array, &var_.vt))) { |
| var_.vt |= VT_ARRAY; |
| var_.parray = array; |
| } else { |
| DCHECK(!array) << "Unable to determine safearray vartype"; |
| var_.vt = VT_EMPTY; |
| } |
| } |
| |
| void ScopedVariant::Set(const VARIANT& var) { |
| DCHECK(!IsLeakableVarType(var_.vt)) << "leaking variant: " << var_.vt; |
| if (FAILED(::VariantCopy(&var_, &var))) { |
| DLOG(ERROR) << "VariantCopy failed"; |
| var_.vt = VT_EMPTY; |
| } |
| } |
| |
| ScopedVariant& ScopedVariant::operator=(ScopedVariant&& var) { |
| if (var.ptr() != &var_) |
| Reset(var.Release()); |
| return *this; |
| } |
| |
| ScopedVariant& ScopedVariant::operator=(const VARIANT& var) { |
| if (&var != &var_) { |
| VariantClear(&var_); |
| Set(var); |
| } |
| return *this; |
| } |
| |
| bool ScopedVariant::IsLeakableVarType(VARTYPE vt) { |
| bool leakable = false; |
| switch (vt & VT_TYPEMASK) { |
| case VT_BSTR: |
| case VT_DISPATCH: |
| // we treat VT_VARIANT as leakable to err on the safe side. |
| case VT_VARIANT: |
| case VT_UNKNOWN: |
| case VT_SAFEARRAY: |
| |
| // very rarely used stuff (if ever): |
| case VT_VOID: |
| case VT_PTR: |
| case VT_CARRAY: |
| case VT_USERDEFINED: |
| case VT_LPSTR: |
| case VT_LPWSTR: |
| case VT_RECORD: |
| case VT_INT_PTR: |
| case VT_UINT_PTR: |
| case VT_FILETIME: |
| case VT_BLOB: |
| case VT_STREAM: |
| case VT_STORAGE: |
| case VT_STREAMED_OBJECT: |
| case VT_STORED_OBJECT: |
| case VT_BLOB_OBJECT: |
| case VT_VERSIONED_STREAM: |
| case VT_BSTR_BLOB: |
| leakable = true; |
| break; |
| } |
| |
| if (!leakable && (vt & VT_ARRAY) != 0) { |
| leakable = true; |
| } |
| |
| return leakable; |
| } |
| |
| } // namespace win |
| } // namespace base |