Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8759d674 authored by Michael Wright's avatar Michael Wright
Browse files

Automatically generate flag value strings for flag enums.

Since there can generally only be a maximum of 64 flags for a given
flage type, it isn't that expensive to generate the array of names at
compile time by enumerating the possible flag values and then using a
templated function's debug signature to extract the name. The debug
signature should be relatively stable, and I've confirmed this works on
both GCC as well as clang. If our parsing fails, however, we should just
fallback to bare hex values again. Our tests should hopefully prevent
this from happening for any extended period of time.

Bug: 160010896
Test: atest libinput_tests
Change-Id: I752100bbefb92e7a0ecf7a8473a47e37ff7b1662
parent 7aab8a09
Loading
Loading
Loading
Loading
+88 −11
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

#include <android-base/stringprintf.h>

#include <array>
#include <cstdint>
#include <optional>
#include <string>
@@ -28,6 +29,69 @@

namespace android {

namespace details {
template <typename F, F V>
constexpr std::optional<std::string_view> enum_value_name() {
    // Should look something like (but all on one line):
    //   std::optional<std::string_view>
    //   android::details::enum_value_name()
    //   [F = android::test::TestFlags, V = android::test::TestFlags::ONE]
    std::string_view view = __PRETTY_FUNCTION__;
    size_t templateStart = view.rfind("[");
    size_t templateEnd = view.rfind("]");
    if (templateStart == std::string::npos || templateEnd == std::string::npos) {
        return std::nullopt;
    }

    // Extract the template parameters without the enclosing braces.
    // Example (cont'd): F = android::test::TestFlags, V = android::test::TestFlags::ONE
    view = view.substr(templateStart + 1, templateEnd - templateStart - 1);
    size_t valStart = view.rfind("V = ");
    if (valStart == std::string::npos) {
        return std::nullopt;
    }

    // Example (cont'd): V = android::test::TestFlags::ONE
    view = view.substr(valStart);
    size_t nameStart = view.rfind("::");
    if (nameStart == std::string::npos) {
        return std::nullopt;
    }

    // Chop off the initial "::"
    nameStart += 2;
    return view.substr(nameStart);
}

template <typename F>
inline constexpr auto flag_count = sizeof(F) * __CHAR_BIT__;

template <typename F, typename T, T... I>
constexpr auto generate_flag_values(std::integer_sequence<T, I...> seq) {
    constexpr int count = seq.size();

    std::array<F, count> values{};
    for (int i = 0, v = 0; v < count; ++i) {
        values[v++] = static_cast<F>(T{1} << i);
    }

    return values;
}

template <typename F>
inline constexpr auto flag_values = generate_flag_values<F>(
        std::make_integer_sequence<std::underlying_type_t<F>, flag_count<F>>{});

template <typename F, std::size_t... I>
constexpr auto generate_flag_names(std::index_sequence<I...>) noexcept {
    return std::array<std::optional<std::string_view>, sizeof...(I)>{
            {enum_value_name<F, flag_values<F>[I]>()...}};
}

template <typename F>
inline constexpr auto flag_names =
        generate_flag_names<F>(std::make_index_sequence<flag_count<F>>{});

// A trait for determining whether a type is specifically an enum class or not.
template <typename T, bool = std::is_enum_v<T>>
struct is_enum_class : std::false_type {};
@@ -40,13 +104,28 @@ struct is_enum_class<T, true>

template <typename T>
inline constexpr bool is_enum_class_v = is_enum_class<T>::value;
} // namespace details

template <auto V>
constexpr auto flag_name() {
    using F = decltype(V);
    return details::enum_value_name<F, V>();
}

template <typename F>
constexpr std::optional<std::string_view> flag_name(F flag) {
    using U = std::underlying_type_t<F>;
    auto idx = __builtin_ctzl(static_cast<U>(flag));
    return details::flag_names<F>[idx];
}

/* A class for handling flags defined by an enum or enum class in a type-safe way. */
template <class F, typename = std::enable_if_t<std::is_enum_v<F>>>
template <typename F>
class Flags {
    // F must be an enum or its underlying type is undefined. Theoretically we could specialize this
    // further to avoid this restriction but in general we want to encourage the use of enums
    // anyways.
    static_assert(std::is_enum_v<F>, "Flags type must be an enum");
    using U = typename std::underlying_type_t<F>;

public:
@@ -59,10 +138,11 @@ public:
    // should force them to be explicitly constructed from their underlying types to make full use
    // of the type checker.
    template <typename T = U>
    constexpr Flags(T t, typename std::enable_if_t<!is_enum_class_v<F>, T>* = nullptr)
    constexpr Flags(T t, typename std::enable_if_t<!details::is_enum_class_v<F>, T>* = nullptr)
          : mFlags(t) {}
    template <typename T = U>
    explicit constexpr Flags(T t, typename std::enable_if_t<is_enum_class_v<F>, T>* = nullptr)
    explicit constexpr Flags(T t,
                             typename std::enable_if_t<details::is_enum_class_v<F>, T>* = nullptr)
          : mFlags(t) {}

    class Iterator {
@@ -176,14 +256,12 @@ public:
     */
    U get() const { return mFlags; }

    std::string string() const { return string(defaultStringify); }

    std::string string(std::function<std::optional<std::string>(F)> stringify) const {
    std::string string() const {
        std::string result;
        bool first = true;
        U unstringified = 0;
        for (const F f : *this) {
            std::optional<std::string> flagString = stringify(f);
            std::optional<std::string_view> flagString = flag_name(f);
            if (flagString) {
                appendFlag(result, flagString.value(), first);
            } else {
@@ -205,8 +283,7 @@ public:
private:
    U mFlags;

    static std::optional<std::string> defaultStringify(F) { return std::nullopt; }
    static void appendFlag(std::string& str, const std::string& flag, bool& first) {
    static void appendFlag(std::string& str, const std::string_view& flag, bool& first) {
        if (first) {
            first = false;
        } else {
@@ -220,12 +297,12 @@ private:
// as flags. In order to use these, add them via a `using namespace` declaration.
namespace flag_operators {

template <typename F, typename = std::enable_if_t<is_enum_class_v<F>>>
template <typename F, typename = std::enable_if_t<details::is_enum_class_v<F>>>
inline Flags<F> operator~(F f) {
    using U = typename std::underlying_type_t<F>;
    return static_cast<F>(~static_cast<U>(f));
}
template <typename F, typename = std::enable_if_t<is_enum_class_v<F>>>
template <typename F, typename = std::enable_if_t<details::is_enum_class_v<F>>>
Flags<F> operator|(F lhs, F rhs) {
    using U = typename std::underlying_type_t<F>;
    return static_cast<F>(static_cast<U>(lhs) | static_cast<U>(rhs));
+0 −2
Original line number Diff line number Diff line
@@ -205,8 +205,6 @@ struct InputWindowInfo : public Parcelable {
    status_t writeToParcel(android::Parcel* parcel) const override;

    status_t readFromParcel(const android::Parcel* parcel) override;

    static std::optional<std::string> flagToString(Flag f);
};

/*
+0 −103
Original line number Diff line number Diff line
@@ -198,107 +198,4 @@ sp<IBinder> InputWindowHandle::getToken() const {
void InputWindowHandle::updateFrom(sp<InputWindowHandle> handle) {
    mInfo = handle->mInfo;
}

std::optional<std::string> InputWindowInfo::flagToString(Flag flag) {
    switch (flag) {
        case InputWindowInfo::Flag::ALLOW_LOCK_WHILE_SCREEN_ON: {
            return "ALLOW_LOCK_WHILE_SCREEN_ON";
        }
        case InputWindowInfo::Flag::DIM_BEHIND: {
            return "DIM_BEHIND";
        }
        case InputWindowInfo::Flag::BLUR_BEHIND: {
            return "BLUR_BEHIND";
        }
        case InputWindowInfo::Flag::NOT_FOCUSABLE: {
            return "NOT_FOCUSABLE";
        }
        case InputWindowInfo::Flag::NOT_TOUCHABLE: {
            return "NOT_TOUCHABLE";
        }
        case InputWindowInfo::Flag::NOT_TOUCH_MODAL: {
            return "NOT_TOUCH_MODAL";
        }
        case InputWindowInfo::Flag::TOUCHABLE_WHEN_WAKING: {
            return "TOUCHABLE_WHEN_WAKING";
        }
        case InputWindowInfo::Flag::KEEP_SCREEN_ON: {
            return "KEEP_SCREEN_ON";
        }
        case InputWindowInfo::Flag::LAYOUT_IN_SCREEN: {
            return "LAYOUT_IN_SCREEN";
        }
        case InputWindowInfo::Flag::LAYOUT_NO_LIMITS: {
            return "LAYOUT_NO_LIMITS";
        }
        case InputWindowInfo::Flag::FULLSCREEN: {
            return "FULLSCREEN";
        }
        case InputWindowInfo::Flag::FORCE_NOT_FULLSCREEN: {
            return "FORCE_NOT_FULLSCREEN";
        }
        case InputWindowInfo::Flag::DITHER: {
            return "DITHER";
        }
        case InputWindowInfo::Flag::SECURE: {
            return "SECURE";
        }
        case InputWindowInfo::Flag::SCALED: {
            return "SCALED";
        }
        case InputWindowInfo::Flag::IGNORE_CHEEK_PRESSES: {
            return "IGNORE_CHEEK_PRESSES";
        }
        case InputWindowInfo::Flag::LAYOUT_INSET_DECOR: {
            return "LAYOUT_INSET_DECOR";
        }
        case InputWindowInfo::Flag::ALT_FOCUSABLE_IM: {
            return "ALT_FOCUSABLE_IM";
        }
        case InputWindowInfo::Flag::WATCH_OUTSIDE_TOUCH: {
            return "WATCH_OUTSIDE_TOUCH";
        }
        case InputWindowInfo::Flag::SHOW_WHEN_LOCKED: {
            return "SHOW_WHEN_LOCKED";
        }
        case InputWindowInfo::Flag::SHOW_WALLPAPER: {
            return "SHOW_WALLPAPER";
        }
        case InputWindowInfo::Flag::TURN_SCREEN_ON: {
            return "TURN_SCREEN_ON";
        }
        case InputWindowInfo::Flag::DISMISS_KEYGUARD: {
            return "DISMISS_KEYGUARD";
        }
        case InputWindowInfo::Flag::SPLIT_TOUCH: {
            return "SPLIT_TOUCH";
        }
        case InputWindowInfo::Flag::HARDWARE_ACCELERATED: {
            return "HARDWARE_ACCELERATED";
        }
        case InputWindowInfo::Flag::LAYOUT_IN_OVERSCAN: {
            return "LAYOUT_IN_OVERSCAN";
        }
        case InputWindowInfo::Flag::TRANSLUCENT_STATUS: {
            return "TRANSLUCENT_STATUS";
        }
        case InputWindowInfo::Flag::TRANSLUCENT_NAVIGATION: {
            return "TRANSLUCENT_NAVIGATION";
        }
        case InputWindowInfo::Flag::LOCAL_FOCUS_MODE: {
            return "LOCAL_FOCUS_MODE";
        }
        case InputWindowInfo::Flag::SLIPPERY: {
            return "SLIPPERY";
        }
        case InputWindowInfo::Flag::LAYOUT_ATTACHED_IN_DECOR: {
            return "LAYOUT_ATTACHED_IN_DECOR";
        }
        case InputWindowInfo::Flag::DRAWS_SYSTEM_BAR_BACKGROUNDS: {
            return "DRAWS_SYSTEM_BAR_BACKGROUNDS";
        }
    }
    return std::nullopt;
}

} // namespace android
+20 −40
Original line number Diff line number Diff line
@@ -25,30 +25,6 @@ using namespace android::flag_operators;

enum class TestFlags { ONE = 0x1, TWO = 0x2, THREE = 0x4 };

static std::optional<std::string> toStringComplete(TestFlags f) {
    switch (f) {
        case TestFlags::ONE:
            return "ONE";
        case TestFlags::TWO:
            return "TWO";
        case TestFlags::THREE:
            return "THREE";
    }
    return std::nullopt;
}

static std::optional<std::string> toStringIncomplete(TestFlags f) {
    switch (f) {
        case TestFlags::ONE:
            return "ONE";
        case TestFlags::TWO:
            return "TWO";
        case TestFlags::THREE:
        default:
            return std::nullopt;
    }
}

TEST(Flags, Test) {
    Flags<TestFlags> flags = TestFlags::ONE;
    ASSERT_TRUE(flags.test(TestFlags::ONE));
@@ -172,29 +148,19 @@ TEST(Flags, EqualsOperator_DontShareState) {
    ASSERT_NE(flags1, flags2);
}

TEST(Flags, String_NoFlagsWithDefaultStringify) {
TEST(Flags, String_NoFlags) {
    Flags<TestFlags> flags;
    ASSERT_EQ(flags.string(), "0x0");
}

TEST(Flags, String_NoFlagsWithNonDefaultStringify) {
    Flags<TestFlags> flags;
    ASSERT_EQ(flags.string(toStringComplete), "0x0");
}

TEST(Flags, String_WithDefaultStringify) {
TEST(Flags, String_KnownValues) {
    Flags<TestFlags> flags = TestFlags::ONE | TestFlags::TWO;
    ASSERT_EQ(flags.string(), "0x00000003");
    ASSERT_EQ(flags.string(), "ONE | TWO");
}

TEST(Flags, String_WithCompleteStringify) {
    Flags<TestFlags> flags = TestFlags::ONE | TestFlags::TWO;
    ASSERT_EQ(flags.string(toStringComplete), "ONE | TWO");
}

TEST(Flags, String_WithIncompleteStringify) {
    Flags<TestFlags> flags = TestFlags::ONE | TestFlags::THREE;
    ASSERT_EQ(flags.string(toStringIncomplete), "ONE | 0x00000004");
TEST(Flags, String_UnknownValues) {
    auto flags = Flags<TestFlags>(0b1011);
    ASSERT_EQ(flags.string(), "ONE | TWO | 0x00000008");
}

TEST(FlagsIterator, IteratesOverAllFlags) {
@@ -239,4 +205,18 @@ TEST(FlagsIterator, PreFixIncrement) {
    ASSERT_EQ(++iter, flags.end());
}

TEST(FlagNames, RuntimeFlagName) {
    TestFlags f = TestFlags::ONE;
    ASSERT_EQ(flag_name(f), "ONE");
}

TEST(FlagNames, RuntimeUnknownFlagName) {
    TestFlags f = static_cast<TestFlags>(0x8);
    ASSERT_EQ(flag_name(f), std::nullopt);
}

TEST(FlagNames, CompileTimeFlagName) {
    static_assert(flag_name<TestFlags::TWO>() == "TWO");
}

} // namespace android::test
 No newline at end of file
+1 −2
Original line number Diff line number Diff line
@@ -4229,8 +4229,7 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) {
                                         toString(windowInfo->hasWallpaper),
                                         toString(windowInfo->visible),
                                         toString(windowInfo->canReceiveKeys),
                                         windowInfo->flags.string(InputWindowInfo::flagToString)
                                                 .c_str(),
                                         windowInfo->flags.string().c_str(),
                                         static_cast<int32_t>(windowInfo->type),
                                         windowInfo->frameLeft, windowInfo->frameTop,
                                         windowInfo->frameRight, windowInfo->frameBottom,