Skip to content

Commit

Permalink
Fix x11_change_property not properly copying data
Browse files Browse the repository at this point in the history
Signed-off-by: Tin Švagelj <[email protected]>
  • Loading branch information
Caellian committed Dec 10, 2024
1 parent 384822e commit 1784a59
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 84 deletions.
2 changes: 1 addition & 1 deletion src/x11.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1146,7 +1146,7 @@ void set_struts(alignment align) {

Atom strut = ATOM(_NET_WM_STRUT);
if (strut != None) {
long sizes[STRUT_COUNT] = {0};
long sizes[STRUT_COUNT] = {};

int display_width = workarea.width();
int display_height = workarea.height();
Expand Down
4 changes: 1 addition & 3 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,8 @@ excluding_any("wayland" IF NOT BUILD_WAYLAND)

# Mocking works because it's linked before conky_core, so the linker uses mock
# implementations instead of those that are linked later.
add_library(Catch2 STATIC catch2/catch_amalgamated.cpp)

add_library(conky-mock OBJECT ${mock_sources})
target_link_libraries(conky-mock Catch2)
add_library(Catch2 STATIC catch2/catch_amalgamated.cpp)

add_executable(test-conky test-common.cc ${test_sources})
target_include_directories(test-conky
Expand Down
178 changes: 105 additions & 73 deletions tests/mock/x11-mock.hh
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <cstddef>
#include <cstdint>
#include <string>
#include <string_view>
#include <vector>

#include "mock.hh"

Expand Down Expand Up @@ -36,6 +38,9 @@ enum x11_property_type {

Atom name_to_atom(const char *name);
const std::string_view atom_to_name(Atom atom);
size_t format_size(std::size_t format);
void dump_x11_blob(const std::byte *data, std::size_t format,
std::size_t length);

/// Mutation produced by creating new `Atom`s.
struct x11_define_atom : public state_change {
Expand All @@ -62,29 +67,29 @@ class x11_change_property : public state_change {
Atom m_type;
std::size_t m_format;
set_property_mode m_mode;
const unsigned char *m_data;
std::vector<std::byte> m_data;
std::size_t m_element_count;

public:
x11_change_property(Atom property, Atom type, std::size_t format,
set_property_mode mode, const unsigned char *data,
set_property_mode mode, const std::byte *data,
std::size_t element_count)
: m_property(property),
m_type(type),
m_format(format),
m_mode(mode),
m_data(data),
m_element_count(element_count) {}
m_element_count(element_count),
m_data(std::vector(data, data + format_size(format) * element_count)) {}

static std::string change_name() { return "x11_change_property"; }

Atom property() { return m_property; }
std::string_view property_name() { return atom_to_name(m_property); }
Atom type() { return m_type; }
std::string_view type_name() { return atom_to_name(m_type); }
std::size_t format() { return m_format; }
set_property_mode mode() { return m_mode; }
std::string_view mode_name() {
Atom property() const { return m_property; }
std::string_view property_name() const { return atom_to_name(m_property); }
Atom type() const { return m_type; }
std::string_view type_name() const { return atom_to_name(m_type); }
std::size_t format() const { return m_format; }
set_property_mode mode() const { return m_mode; }
std::string_view mode_name() const {
switch (m_mode) {
case mock::set_property_mode::REPLACE:
return "replace";
Expand All @@ -96,8 +101,8 @@ class x11_change_property : public state_change {
return "other";
}
}
std::size_t element_count() { return m_element_count; }
const unsigned char *const data() { return m_data; }
std::size_t element_count() const { return m_element_count; }
const std::byte *data() const { return m_data.data(); }

std::string debug() {
return debug_format(
Expand All @@ -107,26 +112,24 @@ class x11_change_property : public state_change {
m_element_count);
}
};

template <class T>
struct always_false : std::false_type {};
} // namespace mock

#define REQUIRE_FORMAT_SIZE(format, T) REQUIRE(format == (sizeof(T) * 8))

// These are only macros because including Catch2 from mocking causes spurious
// errors.
// errors. I whish they weren't because they're such a pain to write this way.

// Originally a single templated function:
//
// template <typename D, const std::size_t Count>
// const D &expect_x11_data(
// const unsigned char * const data, Atom type, std::size_t format,
// const std::byte* data, Atom type, std::size_t format,
// std::size_t element_count
// ) {...}
//
// It is a somewhat large blob, but most of it will be compiled away. The only
// downside is that lambdas must return owned values.

#define REQUIRE_FORMAT_SIZE(format, T) REQUIRE(format == (sizeof(T) * 8))
#define EXPECT_X11_VALUE(data, type, format, element_count, T) \
[]() { \
if constexpr (std::is_same_v<XID, std::uint32_t> && \
Expand Down Expand Up @@ -225,6 +228,21 @@ struct always_false : std::false_type {};
} \
}()

#define _COPY_C_ARRAY_TO_CAST(BaseT, TargetT, Length, source) \
[&]() { \
auto values = reinterpret_cast<const BaseT *>(source); \
auto result = std::array<TargetT, Length>{}; \
for (size_t i = 0; i < Length; i++) { \
if constexpr (std::numeric_limits<BaseT>::max() > \
std::numeric_limits<TargetT>::max()) { \
CHECK(values[i] >= std::numeric_limits<TargetT>::min()); \
CHECK(values[i] <= std::numeric_limits<TargetT>::max()); \
} \
result[i] = static_cast<TargetT>(values[i]); \
} \
return result; \
}()

#define EXPECT_X11_ARRAY(data, type, format, element_count, T, Count) \
[&]() { \
if constexpr (std::is_same_v<XID, std::uint32_t> && \
Expand All @@ -244,14 +262,14 @@ struct always_false : std::false_type {};
} \
REQUIRE_FORMAT_SIZE(format, std::uint32_t); \
REQUIRE(element_count == Count); \
return *reinterpret_cast<const std::array<T, Count> *>(data); \
return _COPY_C_ARRAY_TO_CAST(long, std::uint32_t, Count, data); \
} else if constexpr (std::is_same_v<T, std::uint32_t>) { \
if (type != mock::x11_property_type::CARDINAL) { \
FAIL("expected CARDINAL array; got: " << mock::atom_to_name(type)); \
} \
REQUIRE_FORMAT_SIZE(format, std::uint32_t); \
REQUIRE(element_count == Count); \
return *reinterpret_cast<const std::array<T, Count> *>(data); \
return _COPY_C_ARRAY_TO_CAST(long, std::uint32_t, Count, data); \
} else if constexpr (std::is_same_v<T, XID>) { \
if (!(type == mock::x11_property_type::ATOM || \
type == mock::x11_property_type::BITMAP || \
Expand All @@ -266,68 +284,82 @@ struct always_false : std::false_type {};
} \
REQUIRE_FORMAT_SIZE(format, XID); \
REQUIRE(element_count == Count); \
return *reinterpret_cast<const std::array<T, Count> *>(data); \
return _COPY_C_ARRAY_TO_CAST(long, T, Count, data); \
} else if constexpr (std::is_same_v<T, std::int32_t>) { \
if (type != mock::x11_property_type::INTEGER) { \
FAIL("expected INTEGER array; got: " << mock::atom_to_name(type)); \
} \
REQUIRE_FORMAT_SIZE(format, std::uint32_t); \
REQUIRE_FORMAT_SIZE(format, std::int32_t); \
REQUIRE(element_count == Count); \
return *reinterpret_cast<const std::array<T, Count> *>(data); \
return _COPY_C_ARRAY_TO_CAST(long, std::int32_t, Count, data); \
} else { \
throw "unimplemented conversion"; \
} \
}()

#define EXPECT_X11_VEC(data, type, format, element_count, T) \
[&]() { \
REQUIRE(sizeof(T) == format); \
if constexpr (std::is_same_v<XID, std::uint32_t> && \
std::is_same_v<T, std::uint32_t>) { \
if (!(type == mock::x11_property_type::ATOM || \
type == mock::x11_property_type::BITMAP || \
type == mock::x11_property_type::CARDINAL || \
type == mock::x11_property_type::PIXMAP || \
type == mock::x11_property_type::COLORMAP || \
type == mock::x11_property_type::CURSOR || \
type == mock::x11_property_type::DRAWABLE || \
type == mock::x11_property_type::FONT || \
type == mock::x11_property_type::VISUALID || \
type == mock::x11_property_type::WINDOW)) { \
FAIL("expected unsigned long array; got: " \
<< mock::atom_to_name(type)); \
} \
REQUIRE_FORMAT_SIZE(format, std::uint32_t); \
return std::vector<reinterpret_cast<const T *>(data), element_count>; \
} else if constexpr (std::is_same_v<T, std::uint32_t>) { \
if (type != mock::x11_property_type::CARDINAL) { \
FAIL("expected CARDINAL array; got: " << mock::atom_to_name(type)); \
} \
REQUIRE_FORMAT_SIZE(format, std::uint32_t); \
return std::vector<reinterpret_cast<const T *>(data), element_count>; \
} else if constexpr (std::is_same_v<T, XID>) { \
if (!(type == mock::x11_property_type::ATOM || \
type == mock::x11_property_type::BITMAP || \
type == mock::x11_property_type::PIXMAP || \
type == mock::x11_property_type::COLORMAP || \
type == mock::x11_property_type::CURSOR || \
type == mock::x11_property_type::DRAWABLE || \
type == mock::x11_property_type::FONT || \
type == mock::x11_property_type::VISUALID || \
type == mock::x11_property_type::WINDOW)) { \
FAIL("expected XID data; got: " << mock::atom_to_name(type)); \
} \
REQUIRE_FORMAT_SIZE(format, XID); \
return std::vector<reinterpret_cast<const T *>(data), element_count>; \
} else if constexpr (std::is_same_v<T, std::int32_t>) { \
if (type != mock::x11_property_type::INTEGER) { \
FAIL("expected INTEGER array; got: " << mock::atom_to_name(type)); \
} \
REQUIRE_FORMAT_SIZE(format, std::int32_t); \
return std::vector<reinterpret_cast<const T *>(data), element_count>; \
} else { \
throw "unimplemented conversion"; \
} \
#define _COPY_C_ARRAY_TO_VEC(BaseT, TargetT, source, length) \
[&]() { \
auto values = reinterpret_cast<const BaseT *>(source); \
auto result = std::vector<TargetT>(length); \
for (const BaseT *it = values; it < values + length; it++) { \
if constexpr (std::numeric_limits<BaseT>::max() > \
std::numeric_limits<TargetT>::max()) { \
CHECK(*it >= std::numeric_limits<TargetT>::min()); \
CHECK(*it <= std::numeric_limits<TargetT>::max()); \
} \
result.push_back(*it); \
} \
return result; \
}()

#define EXPECT_X11_VEC(data, type, format, element_count, T) \
[&]() { \
if constexpr (std::is_same_v<XID, std::uint32_t> && \
std::is_same_v<T, std::uint32_t>) { \
if (!(type == mock::x11_property_type::ATOM || \
type == mock::x11_property_type::BITMAP || \
type == mock::x11_property_type::CARDINAL || \
type == mock::x11_property_type::PIXMAP || \
type == mock::x11_property_type::COLORMAP || \
type == mock::x11_property_type::CURSOR || \
type == mock::x11_property_type::DRAWABLE || \
type == mock::x11_property_type::FONT || \
type == mock::x11_property_type::VISUALID || \
type == mock::x11_property_type::WINDOW)) { \
FAIL("expected unsigned long array; got: " \
<< mock::atom_to_name(type)); \
} \
REQUIRE_FORMAT_SIZE(format, std::uint32_t); \
return _COPY_C_ARRAY_TO_VEC(long, std::uint32_t, data, element_count); \
} else if constexpr (std::is_same_v<T, std::uint32_t>) { \
if (type != mock::x11_property_type::CARDINAL) { \
FAIL("expected CARDINAL array; got: " << mock::atom_to_name(type)); \
} \
REQUIRE_FORMAT_SIZE(format, std::uint32_t); \
return _COPY_C_ARRAY_TO_VEC(long, std::uint32_t, data, element_count); \
} else if constexpr (std::is_same_v<T, XID>) { \
if (!(type == mock::x11_property_type::ATOM || \
type == mock::x11_property_type::BITMAP || \
type == mock::x11_property_type::PIXMAP || \
type == mock::x11_property_type::COLORMAP || \
type == mock::x11_property_type::CURSOR || \
type == mock::x11_property_type::DRAWABLE || \
type == mock::x11_property_type::FONT || \
type == mock::x11_property_type::VISUALID || \
type == mock::x11_property_type::WINDOW)) { \
FAIL("expected XID data; got: " << mock::atom_to_name(type)); \
} \
REQUIRE_FORMAT_SIZE(format, XID); \
return _COPY_C_ARRAY_TO_VEC(long, XID, data, element_count); \
} else if constexpr (std::is_same_v<T, std::int32_t>) { \
if (type != mock::x11_property_type::INTEGER) { \
FAIL("expected INTEGER array; got: " << mock::atom_to_name(type)); \
} \
REQUIRE_FORMAT_SIZE(format, std::int32_t); \
return _COPY_C_ARRAY_TO_VEC(long, std::int32_t, data, element_count); \
} else { \
throw "unimplemented conversion"; \
} \
}()

#endif /* X11_MOCK_HH */
34 changes: 32 additions & 2 deletions tests/mock/x11.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include <array>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <memory>
#include <string>
Expand Down Expand Up @@ -111,6 +112,33 @@ const std::string_view atom_to_name(Atom atom) {
}
return "UNKNOWN";
}

size_t format_size(std::size_t format) {
if (format == 32) {
return sizeof(long);
} else if (format == 16) {
return sizeof(short);
} else if (format == 8) {
return sizeof(char);
} else {
throw "invalid format";
}
}
void dump_x11_blob(const std::byte *data, std::size_t format,
std::size_t length) {
size_t entry_len = format_size(format);
for (size_t i = 0; i < length * entry_len; i++) {
if (((i + 1) % entry_len) == 1) { printf("%p: ", data + i); }
// Print bytes in order:
// printf("%02x ", data[i]);
// Reorder bytes:
printf("%02x ", (unsigned char)data[((i / entry_len - 1) * entry_len) +
(2 * entry_len - 1 - (i % entry_len))]);
if (i > 0 && ((i + 1) % entry_len) == 0) { puts(""); }
}
printf("Total bytes: %d\n", (int)(length * entry_len));
puts("");
}
} // namespace mock

extern "C" {
Expand All @@ -130,9 +158,11 @@ Atom XInternAtom(Display *display, const char *atom_name, int only_if_exists) {
int XChangeProperty(Display *display, Window w, Atom property, Atom type,
int format, int mode, const unsigned char *data,
int nelements) {
// printf("Setting %s property data:\n", mock::atom_to_name(property).data());
// dump_x11_blob((const std::byte *)data, format, nelements);
mock::push_state_change(std::make_unique<mock::x11_change_property>(
property, type, format, static_cast<mock::set_property_mode>(mode), data,
nelements));
property, type, format, static_cast<mock::set_property_mode>(mode),
(const std::byte *)data, nelements));
return Success;
}
}
Loading

0 comments on commit 1784a59

Please sign in to comment.