Commit 9719c0f3 authored by Adam Ettenberger's avatar Adam Ettenberger Committed by Commit Bot

Add VariantVector class for populating a VARIANT or SAFEARRAY

Adding helper class VariantVector which can be populated similarly to
an STL container but without the compile-time requirement of knowing
what type will be stored. Internally represented as a vector of
Scopedvariants with a uniform VARTYPE.

- Can release ownership of its contents as either a Scalar VARIANT or,
  as a SAFEARRAY wrapped by a VARIANT.

- When the vector is destructed, any remaining managed contents are
  released automatically.


Also improving the ScopedVariant::Compare method to be able to handle
some types that ::VarCmp cannot.
e.g. VT_I1, VT_UI2, VT_UI4, VT_UI8, VT_UNKNOWN, VT_DISPATCH.

Split out from :
https://chromium-review.googlesource.com/c/chromium/src/+/2188057

Bug: 928948

AX-Relnotes: (Should not be user facing) Adjusted VARIANT comparisons to be more type-strict and adds support for a few previously unsupported VARTYPEs.

Change-Id: I1e300bae6004ba17986643916e0a182f3e400da6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2243839Reviewed-by: default avatarRobert Liao <robliao@chromium.org>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarGreg Thompson <grt@chromium.org>
Reviewed-by: default avatarNektarios Paisios <nektar@chromium.org>
Reviewed-by: default avatarKevin Babbitt <kbabbitt@microsoft.com>
Commit-Queue: Adam Ettenberger <Adam.Ettenberger@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#787410}
parent d7588331
...@@ -1035,6 +1035,8 @@ jumbo_component("base") { ...@@ -1035,6 +1035,8 @@ jumbo_component("base") {
"win/startup_information.cc", "win/startup_information.cc",
"win/startup_information.h", "win/startup_information.h",
"win/variant_util.h", "win/variant_util.h",
"win/variant_vector.cc",
"win/variant_vector.h",
"win/vector.cc", "win/vector.cc",
"win/vector.h", "win/vector.h",
"win/win_util.cc", "win/win_util.cc",
...@@ -2985,6 +2987,7 @@ test("base_unittests") { ...@@ -2985,6 +2987,7 @@ test("base_unittests") {
"win/shortcut_unittest.cc", "win/shortcut_unittest.cc",
"win/startup_information_unittest.cc", "win/startup_information_unittest.cc",
"win/variant_util_unittest.cc", "win/variant_util_unittest.cc",
"win/variant_vector_unittest.cc",
"win/vector_unittest.cc", "win/vector_unittest.cc",
"win/win_includes_unittest.cc", "win/win_includes_unittest.cc",
"win/win_util_unittest.cc", "win/win_util_unittest.cc",
......
...@@ -4,8 +4,16 @@ ...@@ -4,8 +4,16 @@
#include "base/win/scoped_variant.h" #include "base/win/scoped_variant.h"
#include <wrl/client.h>
#include <algorithm>
#include <functional>
#include "base/check.h" #include "base/check.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/numerics/ranges.h"
#include "base/win/propvarutil.h"
#include "base/win/variant_util.h"
namespace base { namespace base {
namespace win { namespace win {
...@@ -104,22 +112,71 @@ VARIANT ScopedVariant::Copy() const { ...@@ -104,22 +112,71 @@ VARIANT ScopedVariant::Copy() const {
return ret; return ret;
} }
int ScopedVariant::Compare(const VARIANT& var, bool ignore_case) const { int ScopedVariant::Compare(const VARIANT& other, bool ignore_case) const {
ULONG flags = ignore_case ? NORM_IGNORECASE : 0; DCHECK(!V_ISARRAY(&var_))
HRESULT hr = ::VarCmp(const_cast<VARIANT*>(&var_), const_cast<VARIANT*>(&var), << "Comparison is not supported when |this| owns a SAFEARRAY";
LOCALE_USER_DEFAULT, flags); DCHECK(!V_ISARRAY(&other))
DCHECK(SUCCEEDED(hr) && hr != VARCMP_NULL) << "Comparison is not supported when |other| owns a SAFEARRAY";
<< "unsupported variant comparison: " << var_.vt << " and " << var.vt;
const bool this_is_empty = var_.vt == VT_EMPTY || var_.vt == VT_NULL;
switch (hr) { const bool other_is_empty = other.vt == VT_EMPTY || other.vt == VT_NULL;
case VARCMP_LT:
return -1; // 1. VT_NULL and VT_EMPTY is always considered less-than any other VARTYPE.
case VARCMP_GT: if (this_is_empty)
case VARCMP_NULL: return other_is_empty ? 0 : -1;
return 1; if (other_is_empty)
default: 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; 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) { void ScopedVariant::Set(const wchar_t* str) {
......
...@@ -89,8 +89,19 @@ class BASE_EXPORT ScopedVariant { ...@@ -89,8 +89,19 @@ class BASE_EXPORT ScopedVariant {
VARIANT Copy() const; VARIANT Copy() const;
// The return value is 0 if the variants are equal, 1 if this object is // The return value is 0 if the variants are equal, 1 if this object is
// greater than |var|, -1 if it is smaller. // greater than |other|, -1 if it is smaller.
int Compare(const VARIANT& var, bool ignore_case = false) const; // Comparison with an array VARIANT is not supported.
// 1. VT_NULL and VT_EMPTY is always considered less-than any other VARTYPE.
// 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).
// 3. If the VARTYPEs do not match, then the value of the VARTYPE is compared.
// 4. Comparing VT_BSTR values is a lexicographical comparison of the contents
// of the BSTR, taking into account |ignore_case|.
// 5. Otherwise returns the lexicographical comparison of the values held by
// the two VARIANTS that share the same VARTYPE.
int Compare(const VARIANT& other, bool ignore_case = false) const;
// Retrieves the pointer address. // Retrieves the pointer address.
// Used to receive a VARIANT as an out argument (and take ownership). // Used to receive a VARIANT as an out argument (and take ownership).
......
This diff is collapsed.
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_WIN_VARIANT_VECTOR_H_
#define BASE_WIN_VARIANT_VECTOR_H_
#include <objbase.h>
#include <oleauto.h>
#include <type_traits>
#include <utility>
#include <vector>
#include "base/base_export.h"
#include "base/check.h"
#include "base/logging.h"
#include "base/no_destructor.h"
#include "base/win/scoped_variant.h"
#include "base/win/variant_util.h"
namespace base {
namespace win {
// This class has RAII semantics and is used to build a vector for a specific
// OLE VARTYPE, and handles converting the data to a VARIANT or VARIANT
// SAFEARRAY. It can be populated similarly to a STL vector<T>, but without the
// compile-time requirement of knowing what element type the VariantVector will
// store. The VariantVector only allows one variant type to be stored at a time.
//
// This class can release ownership of its contents to a VARIANT, and will
// automatically allocate + populate a SAFEARRAY as needed or when explicitly
// requesting that the results be released as a SAFEARRAY.
class BASE_EXPORT VariantVector final {
public:
VariantVector();
VariantVector(VariantVector&& other);
VariantVector& operator=(VariantVector&& other);
VariantVector(const VariantVector&) = delete;
VariantVector& operator=(const VariantVector&) = delete;
~VariantVector();
bool operator==(const VariantVector& other) const;
bool operator!=(const VariantVector& other) const;
// Returns the variant type for data stored in the VariantVector.
VARTYPE Type() const { return vartype_; }
// Returns the number of elements in the VariantVector.
size_t Size() const { return vector_.size(); }
// Returns whether or not there are any elements.
bool Empty() const { return vector_.empty(); }
// Resets VariantVector to its default state, releasing any managed content.
void Reset();
// Helper template method for selecting the correct |Insert| call based
// on the underlying type that is expected for a VARTYPE.
template <VARTYPE ExpectedVartype,
std::enable_if_t<ExpectedVartype != VT_BOOL, int> = 0>
void Insert(typename internal::VariantUtil<ExpectedVartype>::Type value) {
if (vartype_ == VT_EMPTY)
vartype_ = ExpectedVartype;
AssertVartype<ExpectedVartype>();
ScopedVariant scoped_variant;
scoped_variant.Set(value);
vector_.push_back(std::move(scoped_variant));
}
// Specialize VT_BOOL to accept a bool type instead of VARIANT_BOOL,
// this is to make calling insert with VT_BOOL safer.
template <VARTYPE ExpectedVartype,
std::enable_if_t<ExpectedVartype == VT_BOOL, int> = 0>
void Insert(bool value) {
if (vartype_ == VT_EMPTY)
vartype_ = ExpectedVartype;
AssertVartype<ExpectedVartype>();
ScopedVariant scoped_variant;
scoped_variant.Set(value);
vector_.push_back(std::move(scoped_variant));
}
// Specialize VT_DATE because ScopedVariant has a separate SetDate method,
// this is because VT_R8 and VT_DATE share the same underlying type.
template <>
void Insert<VT_DATE>(typename internal::VariantUtil<VT_DATE>::Type value) {
if (vartype_ == VT_EMPTY)
vartype_ = VT_DATE;
AssertVartype<VT_DATE>();
ScopedVariant scoped_variant;
scoped_variant.SetDate(value);
vector_.push_back(std::move(scoped_variant));
}
// Populates a VARIANT based on what is stored, transferring ownership
// of managed contents.
// This is only valid when the VariantVector is empty or has a single element.
// The VariantVector is then reset.
VARIANT ReleaseAsScalarVariant();
// Populates a VARIANT as a SAFEARRAY, even if there is only one element.
// The VariantVector is then reset.
VARIANT ReleaseAsSafearrayVariant();
// Lexicographical comparison between a VariantVector and a VARIANT.
// The return value is 0 if the variants are equal, 1 if this object is
// greater than |other|, -1 if it is smaller.
int Compare(const VARIANT& other, bool ignore_case = false) const;
// Lexicographical comparison between a VariantVector and a SAFEARRAY.
int Compare(SAFEARRAY* safearray, bool ignore_case = false) const;
// Lexicographical comparison between two VariantVectors.
int Compare(const VariantVector& other, bool ignore_case = false) const;
private:
// Returns true if the current |vartype_| is compatible with |ExpectedVartype|
// for inserting into |vector_|.
template <VARTYPE ExpectedVartype>
void AssertVartype() const {
DCHECK(internal::VariantUtil<ExpectedVartype>::IsConvertibleTo(vartype_))
<< "Type mismatch, " << ExpectedVartype << " is not convertible to "
<< Type();
}
// Creates a SAFEARRAY and populates it with teh values held by each VARIANT
// in |vector_|, transferring ownership to the new SAFEARRAY.
// The VariantVector is reset when successful.
template <VARTYPE ElementVartype>
SAFEARRAY* CreateAndPopulateSafearray();
VARTYPE vartype_ = VT_EMPTY;
std::vector<ScopedVariant> vector_;
};
} // namespace win
} // namespace base
#endif // BASE_WIN_VARIANT_VECTOR_H_
This diff is collapsed.
...@@ -3894,7 +3894,7 @@ TEST_F(AXPlatformNodeTextRangeProviderTest, ...@@ -3894,7 +3894,7 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
expected_variant); expected_variant);
expected_variant.Reset(); expected_variant.Reset();
expected_variant.Set(12.0f); expected_variant.Set(12.0);
EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider, UIA_FontSizeAttributeId, EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider, UIA_FontSizeAttributeId,
expected_variant); expected_variant);
expected_variant.Reset(); expected_variant.Reset();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment