chromium/components/exo/wayland/wayland_protocol_logger.cc

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "components/exo/wayland/wayland_protocol_logger.h"

// We need wl_object declared below.
#undef WL_HIDE_DEPRECATED
#include <wayland-server.h>

#include <string>

#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/trace_event/typed_macros.h"

namespace {

std::string StringifyWaylandArgument(const wl_interface* type,
                                     const wl_argument& arg,
                                     const char** signature_ptr) {
  while (**signature_ptr) {
    char s = **signature_ptr;

    // Always advance the pointer before returning, so that the next
    // call to this function will look at the next argument's signature.
    (*signature_ptr)++;

    // The type of `arg` is indicated by this character of the signature.
    switch (s) {
      case 'i':
        return base::NumberToString(arg.i);
      case 'u':
        return base::NumberToString(arg.u);
      case 'f':
        return base::NumberToString(wl_fixed_to_double(arg.f));
      case 's':
        return arg.s ? arg.s : "nil";
      case 'o':
        if (arg.o) {
          const wl_interface* interface = arg.o->interface;
          return base::StrCat({interface ? interface->name : "[unknown]", "@",
                               base::NumberToString(arg.o->id)});
        }
        return "nil";
      case 'n':
        // type should not normally be null, but this handles requests
        // from clients so we need to be robust against invalid data.
        return arg.n ? base::StrCat({"new id ", type ? type->name : "[unknown]",
                                     "@", base::NumberToString(arg.n)})
                     : "nil";
      case 'a':
        // Improve on WAYLAND_DEBUG by printing array contents as hex data.
        // If the array is "too long", truncate.
        static const size_t max_printed_bytes = 48;
        return base::StrCat(
            {"array[", base::NumberToString(arg.a->size), " bytes]{",
             base::HexEncode(arg.a->data,
                             std::min(arg.a->size, max_printed_bytes)),
             // Append an ellipsis if the content was truncated.
             max_printed_bytes < arg.a->size ? "...}" : "}"});
      case 'h':
        return base::StrCat({"fd ", base::NumberToString(arg.h)});
      case '?':
        // ? indicates the next argument is optional.
        // Pass through to next loop iteration.
        break;
      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
        // Numbers indicate protocol versions.
        // Pass through to next loop iteration.
        break;
    }
  }
  // Reached a null character in the signature. This shouldn't happen.
  return std::string("<Wayland signature parse error>");
}

void LogToPerfetto(void* user_data,
                   wl_protocol_logger_type type,
                   const wl_protocol_logger_message* message) {
  // Early-out in the common case that tracing is not currently enabled.
  bool tracing_enabled = false;
  TRACE_EVENT_CATEGORY_GROUP_ENABLED("exo", &tracing_enabled);
  if (!tracing_enabled) {
    return;
  }

  std::vector<std::string> msg{
      exo::wayland::WaylandProtocolLogger::FormatMessage(type, message)};

  TRACE_EVENT_INSTANT(
      "exo", perfetto::DynamicString{msg[0]}, [&](perfetto::EventContext ctx) {
        for (size_t i = 1; i < msg.size(); i++) {
          std::string arg_name = base::StrCat({"arg", base::NumberToString(i)});
          ctx.AddDebugAnnotation(perfetto::DynamicString{arg_name}, msg[i]);
        }
      });
}

}  // namespace

namespace exo::wayland {

WaylandProtocolLogger::WaylandProtocolLogger(struct wl_display* display) {
  logger_.reset(wl_display_add_protocol_logger(display, handler_, nullptr));
}

// Complex class/struct needs an explicit out-of-line destructor.
WaylandProtocolLogger::~WaylandProtocolLogger() {}

// static
std::vector<std::string> WaylandProtocolLogger::FormatMessage(
    wl_protocol_logger_type type,
    const wl_protocol_logger_message* message) {
  std::vector<std::string> return_value;
  return_value.push_back(base::StrCat(
      {type == WL_PROTOCOL_LOGGER_EVENT ? "Sent event: " : "Received request: ",
       wl_resource_get_class(message->resource), "@",
       base::NumberToString(wl_resource_get_id(message->resource)), ".",
       message->message->name}));

  const char* signature = message->message->signature;
  for (int i = 0; i < message->arguments_count && *signature; i++) {
    return_value.push_back(StringifyWaylandArgument(
        message->message->types[i], message->arguments[i], &signature));
  }
  return return_value;
}

// static
void WaylandProtocolLogger::SetHandlerFuncForTesting(
    wl_protocol_logger_func_t handler) {
  handler_ = handler;
}

// static
wl_protocol_logger_func_t WaylandProtocolLogger::handler_ = LogToPerfetto;

void WaylandProtocolLogger::Deleter::operator()(wl_protocol_logger* logger) {
  wl_protocol_logger_destroy(logger);
}

}  // namespace exo::wayland