Commit da6ed776 authored by Chris Hamilton's avatar Chris Hamilton Committed by Commit Bot

Create MultiToken helper class.

A variant container for hosting instances of util::TokenType.
Allows strong typing when a token can be one of many types.

BUG=1096617

Change-Id: Iec386f9760d4b093931ecdc6c477c202ae00bdc6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2327644
Commit-Queue: Chris Hamilton <chrisha@chromium.org>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Cr-Commit-Position: refs/heads/master@{#794585}
parent ce9a6b55
...@@ -243,6 +243,7 @@ jumbo_source_set("common_unittests_sources") { ...@@ -243,6 +243,7 @@ jumbo_source_set("common_unittests_sources") {
"origin_trials/trial_token_unittest.cc", "origin_trials/trial_token_unittest.cc",
"origin_trials/trial_token_validator_unittest.cc", "origin_trials/trial_token_validator_unittest.cc",
"test/run_all_unittests.cc", "test/run_all_unittests.cc",
"tokens/multi_token_unittest.cc",
"user_agent/user_agent_metadata_unittest.cc", "user_agent/user_agent_metadata_unittest.cc",
"web_package/web_package_request_matcher_unittest.cc", "web_package/web_package_request_matcher_unittest.cc",
] ]
......
// 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.
#include "third_party/blink/public/common/tokens/multi_token.h"
#include <algorithm>
#include "base/unguessable_token.h"
#include "base/util/type_safety/token_type.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace blink {
using FooToken = util::TokenType<class FooTokenTag>;
using BarToken = util::TokenType<class BarTokenTag>;
using BazToken = util::TokenType<class BazTokenTag>;
// Test MultiTokenVariantCount.
static_assert(internal::MultiTokenVariantCount<FooToken, BarToken>::kValue == 2,
"unexpected count");
static_assert(
internal::MultiTokenVariantCount<FooToken, BarToken, BazToken>::kValue == 3,
"unexpected count");
// Test MultiTokenTypeRepeated.
static_assert(!internal::MultiTokenTypeRepeated<FooToken>::kValue,
"unexpected repeated value");
static_assert(!internal::MultiTokenTypeRepeated<FooToken, FooToken>::kValue,
"unexpected repeated value");
static_assert(
!internal::MultiTokenTypeRepeated<FooToken, FooToken, BarToken>::kValue,
"unexpected repeated value");
static_assert(
internal::MultiTokenTypeRepeated<FooToken, FooToken, BarToken, FooToken>::
kValue,
"unexpected repeated value");
static_assert(
internal::MultiTokenTypeRepeated<FooToken, BarToken, FooToken, FooToken>::
kValue,
"unexpected repeated value");
// Test MultiTokenAnyTypeRepeated.
static_assert(!internal::MultiTokenAnyTypeRepeated<FooToken>::kValue,
"unexpected any repeated value");
static_assert(!internal::MultiTokenAnyTypeRepeated<FooToken, BarToken>::kValue,
"unexpected any repeated value");
static_assert(
!internal::MultiTokenAnyTypeRepeated<FooToken, BarToken, BazToken>::kValue,
"unexpected any repeated value");
static_assert(
internal::MultiTokenAnyTypeRepeated<FooToken, BarToken, FooToken>::kValue,
"unexpected any repeated value");
static_assert(
internal::MultiTokenAnyTypeRepeated<FooToken, BarToken, BarToken>::kValue,
"unexpected any repeated value");
// Test MultiTokenVariantIsTokenType.
static_assert(internal::MultiTokenVariantIsTokenType<FooToken>::kValue,
"unexpected is token type value");
static_assert(!internal::MultiTokenVariantIsTokenType<int>::kValue,
"unexpected is token type value");
// Test MultiTokenAllVariantsAreTokenType.
static_assert(
internal::MultiTokenAllVariantsAreTokenType<FooToken, BarToken>::kValue,
"unexpected all variants are token type value");
static_assert(!internal::MultiTokenAllVariantsAreTokenType<FooToken,
BarToken,
int>::kValue,
"unexpected all variants are token type value");
using FooBarToken = MultiToken<FooToken, BarToken>;
using FooBarBazToken = MultiToken<FooToken, BarToken, BazToken>;
TEST(MultiTokenTest, MultiTokenWorks) {
// Test default initialization.
FooBarToken token1;
EXPECT_FALSE(token1.value().is_empty());
EXPECT_EQ(0u, token1.variant_index());
EXPECT_TRUE(token1.Is<FooToken>());
EXPECT_FALSE(token1.Is<BarToken>());
// Test copy construction.
BarToken bar = BarToken::Create();
FooBarToken token2(bar);
EXPECT_EQ(token2.value(), bar.value());
EXPECT_FALSE(token2.value().is_empty());
EXPECT_EQ(1u, token2.variant_index());
EXPECT_FALSE(token2.Is<FooToken>());
EXPECT_TRUE(token2.Is<BarToken>());
// Test assignment.
FooBarToken token3;
token3 = token2;
EXPECT_EQ(token3.value(), token2.value());
EXPECT_FALSE(token3.value().is_empty());
EXPECT_EQ(token2.variant_index(), token3.variant_index());
EXPECT_FALSE(token3.Is<FooToken>());
EXPECT_TRUE(token3.Is<BarToken>());
// Test comparison operators.
EXPECT_FALSE(token1 == token2);
EXPECT_TRUE(token1 != token2);
EXPECT_TRUE(token2 == token3);
EXPECT_FALSE(token2 != token3);
EXPECT_EQ(token1 < token2, token1.value() < token2.value());
EXPECT_FALSE(token2 < token3);
EXPECT_FALSE(token3 < token2);
// Test hasher.
EXPECT_EQ(FooBarToken::Hasher()(token2),
base::UnguessableTokenHash()(token2.value()));
// Test string representation.
EXPECT_EQ(token2.ToString(), token2.value().ToString());
// Test type conversions.
FooToken foo(token1.value());
EXPECT_EQ(foo, token1.GetAs<FooToken>());
EXPECT_EQ(token2.GetAs<BarToken>(), token3.GetAs<BarToken>());
}
} // namespace blink
...@@ -167,6 +167,8 @@ source_set("headers") { ...@@ -167,6 +167,8 @@ source_set("headers") {
"sms/sms_receiver_destroyed_reason.h", "sms/sms_receiver_destroyed_reason.h",
"sms/sms_receiver_outcome.h", "sms/sms_receiver_outcome.h",
"switches.h", "switches.h",
"tokens/multi_token.h",
"tokens/multi_token_internal.h",
"tokens/token_mojom_traits_helper.h", "tokens/token_mojom_traits_helper.h",
"tokens/tokens.h", "tokens/tokens.h",
"tokens/tokens_mojom_traits.h", "tokens/tokens_mojom_traits.h",
......
// 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.
// Helper class for a strongly-typed multiple variant token. Allows creating
// a token that can represent one of a collection of distinct token types.
// It would be great to replace this with a much simpler C++17 std::variant
// when that is available.
#ifndef THIRD_PARTY_BLINK_PUBLIC_COMMON_TOKENS_MULTI_TOKEN_H_
#define THIRD_PARTY_BLINK_PUBLIC_COMMON_TOKENS_MULTI_TOKEN_H_
#include <type_traits>
#include "base/unguessable_token.h"
#include "third_party/blink/public/common/tokens/multi_token_internal.h"
namespace blink {
// Defines MultiToken, which is effectively a variant over 2 or more
// instances of util::TokenType.
//
// A MultiToken<..> emulates a token like interface. When default constructed
// it will construct itself as an instance of |TokenVariant0|. Additionally it
// offers the following functions allowing casting and querying token types at
// runtime:
//
// // Determines whether this token stores an instance of a TokenType.
// bool Is<TokenType>() const;
//
// // Extracts the stored token in its original type. The stored token must
// // be of the provided type otherwise this will explode at runtime.
// const TokenType& GetAs<TokenType>() const;
//
// A variant must have at least 2 valid input types, but can have arbitrarily
// many. They must all be distinct, and they must all be instances of
// util::TokenType.
template <typename TokenVariant0,
typename TokenVariant1,
typename... TokenVariants>
class MultiToken : public internal::MultiTokenBase<TokenVariant0,
TokenVariant1,
TokenVariants...> {
public:
using Base =
internal::MultiTokenBase<TokenVariant0, TokenVariant1, TokenVariants...>;
// The total number of types.
static const uint32_t kVariantCount = Base::VariantCount::kValue;
// Default constructor. The resulting token will be a valid token of type
// TokenVariant0.
MultiToken() = default;
// Copy constructors.
MultiToken(const MultiToken& other) = default;
template <typename InputTokenType,
typename = typename std::enable_if<
Base::template ValidType<InputTokenType>::kValue>::type>
explicit MultiToken(const InputTokenType& input_token)
: value_(input_token.value()),
variant_index_(Base::template TypeIndex<InputTokenType>::kValue) {}
~MultiToken() = default;
// Assignment operators.
MultiToken& operator=(const MultiToken& other) = default;
template <typename InputTokenType,
typename = typename std::enable_if<
Base::template ValidType<InputTokenType>::kValue>::type>
MultiToken& operator=(const InputTokenType& input_token) {
value_ = input_token.value();
variant_index_ = Base::template TypeIndex<InputTokenType>::kValue;
return *this;
}
const base::UnguessableToken& value() const { return value_; }
uint32_t variant_index() const { return variant_index_; }
std::string ToString() const { return value().ToString(); }
// Type checking.
template <typename InputTokenType,
typename = typename std::enable_if<
Base::template ValidType<InputTokenType>::kValue>::type>
bool Is() const {
return variant_index_ == Base::template TypeIndex<InputTokenType>::kValue;
}
// Type conversion. Allows extracting the underlying token type. This should
// only be called for the actual type that is stored in this token. This can
// be checked by calling "Is<>" first.
template <typename InputTokenType,
typename = typename std::enable_if<
Base::template ValidType<InputTokenType>::kValue>::type>
InputTokenType GetAs() const {
CHECK(Is<InputTokenType>()) << "invalid token type cast";
// Type-punning via casting is undefined behaviour, so we return by value.
return InputTokenType(value_);
}
// Comparison with untyped tokens. Only compares the token value, ignoring the
// type.
int Compare(const base::UnguessableToken& other) const {
return Base::CompareImpl(value_, other);
}
bool operator<(const base::UnguessableToken& other) const {
return Compare(other) == -1;
}
bool operator==(const base::UnguessableToken& other) const {
return Compare(other) == 0;
}
bool operator!=(const base::UnguessableToken& other) const {
return Compare(other) != 0;
}
// Comparison with other MultiTokens. Compares by token, then type.
int Compare(const MultiToken& other) const {
return Base::CompareImpl(std::tie(value_, variant_index_),
std::tie(other.value_, other.variant_index_));
}
bool operator<(const MultiToken& other) const { return Compare(other) == -1; }
bool operator==(const MultiToken& other) const { return Compare(other) == 0; }
bool operator!=(const MultiToken& other) const { return Compare(other) != 0; }
// Comparison with individual typed tokens. Compares by token, then type.
template <typename InputTokenType,
typename = typename std::enable_if<
Base::template ValidType<InputTokenType>::kValue>::type>
int Compare(const InputTokenType& other) const {
static constexpr uint32_t kInputTokenTypeIndex =
Base::template TypeIndex<InputTokenType>::kValue;
return Base::CompareImpl(std::tie(value_, variant_index_),
std::tie(other.value_, kInputTokenTypeIndex));
}
template <typename InputTokenType,
typename = typename std::enable_if<
Base::template ValidType<InputTokenType>::kValue>::type>
bool operator<(const InputTokenType& other) const {
return Compare(other) == -1;
}
template <typename InputTokenType,
typename = typename std::enable_if<
Base::template ValidType<InputTokenType>::kValue>::type>
bool operator==(const InputTokenType& other) const {
return Compare(other) == 0;
}
template <typename InputTokenType,
typename = typename std::enable_if<
Base::template ValidType<InputTokenType>::kValue>::type>
bool operator!=(const InputTokenType& other) const {
return Compare(other) != 0;
}
// Hash functor for use in unordered containers.
struct Hasher {
using argument_type = MultiToken;
using result_type = size_t;
result_type operator()(const MultiToken& token) const {
return base::UnguessableTokenHash()(token.value_);
}
};
private:
// The underlying untyped token value. This will *never* be null initialized.
base::UnguessableToken value_ = base::UnguessableToken::Create();
// The index of the variant type that is currently stored in this token.
uint32_t variant_index_ = 0;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_PUBLIC_COMMON_TOKENS_MULTI_TOKEN_H_
// 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.
// Internal implementation details for MultiToken. Only intended to be included
// from multi_token.h.
#ifndef THIRD_PARTY_BLINK_PUBLIC_COMMON_TOKENS_MULTI_TOKEN_INTERNAL_H_
#define THIRD_PARTY_BLINK_PUBLIC_COMMON_TOKENS_MULTI_TOKEN_INTERNAL_H_
#include <algorithm>
#include <cstring>
#include <type_traits>
#include "base/unguessable_token.h"
#include "base/util/type_safety/token_type.h"
namespace blink {
namespace internal {
////////////////////////////////////////////////////////////////////////////////
// MultiTokenVariantCount
//
// Counts the number of token types.
template <typename... VariantTypes>
struct MultiTokenVariantCount;
// Recursive case.
template <typename FirstVariantType, typename... OtherVariantTypes>
struct MultiTokenVariantCount<FirstVariantType, OtherVariantTypes...> {
// Deliberately use uint32_t here so as not to incur an extra 4 bytes of
// overhead on 64-bit systems, as this is the same type used by the
// |variant_index_|.
static constexpr uint32_t kValue =
1 + MultiTokenVariantCount<OtherVariantTypes...>::kValue;
};
// Base case.
template <>
struct MultiTokenVariantCount<> {
static constexpr uint32_t kValue = 0;
};
////////////////////////////////////////////////////////////////////////////////
// MultiTokenVariantIsTokenType
//
// Ensures if a QueryType is a a util::TokenType<>.
// Default case.
template <typename QueryType>
struct MultiTokenVariantIsTokenType {
static constexpr bool kValue = false;
};
// Specialization for util::TokenType<>.
template <typename TokenTypeTag>
struct MultiTokenVariantIsTokenType<::util::TokenType<TokenTypeTag>> {
static constexpr bool kValue = true;
// We expect an identical layout, which allows us to reinterpret_cast between
// types. The spec does not guarantee this, but sane compilers do. Thankfully
// we can check whether or not the compiler is sane (and if the behaviour is
// safe) at compile-time.
static_assert(
sizeof(::util::TokenType<TokenTypeTag>) ==
sizeof(::base::UnguessableToken),
"util::TokenType must have the same sizeof as base::UnguessableToken");
static_assert(
alignof(::util::TokenType<TokenTypeTag>) ==
alignof(::base::UnguessableToken),
"util::TokenType must have the same alignof as base::UnguessableToken");
};
////////////////////////////////////////////////////////////////////////////////
// MultiTokenAllVariantsAreTokenType
//
// Ensures that all variants are of type util::TokenType.
template <typename... VariantTypes>
struct MultiTokenAllVariantsAreTokenType;
// Recursive case.
template <typename FirstVariantType, typename... OtherVariantTypes>
struct MultiTokenAllVariantsAreTokenType<FirstVariantType,
OtherVariantTypes...> {
static constexpr bool kValue =
MultiTokenVariantIsTokenType<FirstVariantType>::kValue &&
MultiTokenAllVariantsAreTokenType<OtherVariantTypes...>::kValue;
};
// Base case.
template <>
struct MultiTokenAllVariantsAreTokenType<> {
static constexpr bool kValue = true;
};
////////////////////////////////////////////////////////////////////////////////
// MultiTokenTypeRepeated
//
// Determines if a QueryType is repeated in a variadic list of types.
template <typename QueryType, typename... VariantTypes>
struct MultiTokenTypeRepeated;
// Recursive case.
template <typename QueryType,
typename FirstVariantType,
typename... OtherVariantTypes>
struct MultiTokenTypeRepeated<QueryType,
FirstVariantType,
OtherVariantTypes...> {
static constexpr size_t kCount =
(std::is_same<QueryType, FirstVariantType>::value ? 1 : 0) +
MultiTokenTypeRepeated<QueryType, OtherVariantTypes...>::kCount;
static constexpr bool kValue = kCount > 1;
};
// Base case.
template <typename QueryType>
struct MultiTokenTypeRepeated<QueryType> {
static constexpr size_t kCount = 0;
static constexpr bool kValue = false;
};
////////////////////////////////////////////////////////////////////////////////
// MultiTokenAnyTypeRepeated
//
// Determines if any type is repeated in a variadic list of types.
template <typename... VariantTypes>
struct MultiTokenAnyTypeRepeated;
// Recursive case.
template <typename FirstVariantType, typename... OtherVariantTypes>
struct MultiTokenAnyTypeRepeated<FirstVariantType, OtherVariantTypes...> {
static constexpr bool kValue =
MultiTokenTypeRepeated<FirstVariantType,
FirstVariantType,
OtherVariantTypes...>::kValue ||
MultiTokenAnyTypeRepeated<OtherVariantTypes...>::kValue;
};
// Base case.
template <>
struct MultiTokenAnyTypeRepeated<> {
static constexpr bool kValue = false;
};
////////////////////////////////////////////////////////////////////////////////
// MultiTokenTypeIndex
//
// Returns the index of a QueryType from a variadic list of N types, or N if the
// QueryType is not found in the list.
template <typename QueryType, typename... VariantTypes>
struct MultiTokenTypeIndex;
// Recursive case.
template <typename QueryType,
typename FirstVariantType,
typename... OtherVariantTypes>
struct MultiTokenTypeIndex<QueryType, FirstVariantType, OtherVariantTypes...> {
static constexpr size_t kValue =
(std::is_same<QueryType, FirstVariantType>::value
? 0
: (1 +
MultiTokenTypeIndex<QueryType, OtherVariantTypes...>::kValue));
};
// Base case.
template <typename QueryType>
struct MultiTokenTypeIndex<QueryType> {
static constexpr size_t kValue = 0;
};
////////////////////////////////////////////////////////////////////////////////
// MultiTokenBase
//
// Base class that brings helper structs into a single namespace for
// convenience.
template <typename... TokenVariants>
class MultiTokenBase {
public:
// Ensures that no types are repeated, as that's non-sensical.
using AnyRepeated = internal::MultiTokenAnyTypeRepeated<TokenVariants...>;
static_assert(!AnyRepeated::kValue, "input types must not be repeated");
// Ensures that all variants are instances of util::TokenType.
using AllVariantsAreTokenType =
internal::MultiTokenAllVariantsAreTokenType<TokenVariants...>;
static_assert(AllVariantsAreTokenType::kValue,
"input types must be instances of util::TokenType");
// Counts the number of variants.
using VariantCount = internal::MultiTokenVariantCount<TokenVariants...>;
// For determining the index of a type. Used to assign an integer ID to a
// type, as a kind of untyped enum.
template <typename QueryType>
struct TypeIndex
: public internal::MultiTokenTypeIndex<QueryType, TokenVariants...> {};
// For determining if a type is valid for this variant. Useful in enable_if
// statements.
template <typename QueryType>
struct ValidType {
static constexpr bool kValue =
TypeIndex<QueryType>::kValue != VariantCount::kValue;
};
// Helper comparator. Compares underlying types using only < and == to
// return -1, 0, or 1 depending on their relative values.
template <typename InputType>
static int CompareImpl(const InputType& lhs, const InputType& rhs) {
if (lhs < rhs)
return -1;
if (lhs == rhs)
return 0;
DCHECK(rhs < lhs);
return 1;
}
};
} // namespace internal
} // namespace blink
#endif // THIRD_PARTY_BLINK_PUBLIC_COMMON_TOKENS_MULTI_TOKEN_INTERNAL_H_
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