chromium/components/exo/wayland/fuzzer/harness.cc.tmpl

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

#include "components/exo/wayland/fuzzer/harness.h"

{% for protocol in protocol_names %}
  {% if protocol == 'wayland' %}
    #include <wayland-client-core.h>
    #include <wayland-client-protocol.h>
  {% else %}
    #include <{{protocol | replace('_','-')}}-client-protocol.h>
  {% endif %}
{% endfor %}

#include <cstring>
#include <functional>

#include "base/check.h"
#include "components/exo/wayland/fuzzer/actions.pb.h"

namespace exo {
namespace wayland_fuzzer {

{% for interface in interfaces if interface.has_listener and interface.name != 'wl_display' %}
  namespace {{interface.name}} {
  {% for event in interface.events %}
    void {{event.name}}(
      void* data,
      {{interface.cpp_type}} receiver
      {% for arg in event.args %}
        , {{arg.cpp_type}} {{arg.name}}
      {% endfor %}
    );
  {% endfor %}
  static const struct {{interface.name}}_listener kListener = {
    {% for event in interface.events %}
      {{event.name}},
    {% endfor %}
  };
  }  // namespace {{interface.name}}
{% endfor %}


{% for interface in interfaces if interface.name != 'wl_registry' %}
  namespace {{interface.name}} {
  {% for event in interface.events %}
    void {{event.name}}(
      void* data,
      {{interface.cpp_type}} receiver
      {% for arg in event.args %}
        , {{arg.cpp_type}} {{arg.name}}
      {% endfor %}
    ){
      {% if event.is_constructor %}
        Harness* harness = static_cast<Harness*>(data);
        {% for arg in event.args if arg.type == 'new_id' %}
          {{arg.cpp_type}} new_object = {{arg.name}};
        {% endfor %}
        {% if event.constructed_has_listener %}
          {{event.constructed}}_add_listener(new_object, &{{event.constructed}}::kListener, harness);
        {% endif %}
        harness->add_{{event.constructed}}(new_object);
      {% endif %}
      {% for arg in event.args if arg.type == 'fd' %}
        FILE* _f = fdopen({{arg.name}}, "w");
	CHECK(_f);
	fwrite(".", 1, 1, _f);
        fclose(_f);
      {% endfor %}
    }
  {% endfor %}
  {% for request in interface.requests %}
    void {{request.name}}(Harness* harness,
        const actions::{{interface.name}}_{{request.name}}& action) {
      {{interface.cpp_type}} receiver = harness->get_{{interface.name}}(action.receiver());
      if (!receiver)
        return;
      {% for arg in request.args %}
        {% if arg.type == 'object' %}
          {{arg.cpp_type}} {{arg.name}} = harness->get_{{arg.interface}}(action.{{arg.name}}());
          {% if not arg.nullable %}
            if (!{{arg.name}})
              return;
          {% endif %}
        {% elif arg.type == 'fd' %}
          int {{arg.name}} = harness->GetFileDescriptor(action.{{arg.name}}());
        {% elif arg.type == 'array' %}
          wl_array {{arg.name}}{
            /*size=*/action.{{arg.name}}().size(),
            /*alloc=*/action.{{arg.name}}().capacity(),
            /*data=*/const_cast<char*>(action.{{arg.name}}().data())
          };
        {% endif %}
      {% endfor %}

      {% if request.is_constructor %}
        struct {{request.constructed}}* new_object =
      {% endif %}
      // Invoke the wayland method.  We need ::method_name in order to
      // disambiguate methods which might collide with
      // interface/namespace names.
      ::{{interface.name}}_{{request.name}}(receiver
        {% for arg in request.args %}
          {% if arg.type == 'object' or arg.type == 'fd' %}
            , {{arg.name}}
          {% elif arg.type == 'array' %}
            , &{{arg.name}}
          {% elif arg.type != 'new_id' %}
            , action.{{arg.name}}(){% if arg.type == 'string' %}.c_str(){% endif %}
          {% endif %}
        {% endfor %}
      );
      {% if request.is_constructor %}
        {% if request.constructed_has_listener %}
          {{request.constructed}}_add_listener(new_object, &{{request.constructed}}::kListener, harness);
        {% endif %}
        harness->add_{{request.constructed}}(new_object);
      {% elif request.is_destructor %}
        harness->remove_{{interface.name}}(action.receiver());
      {% endif %}
    }
  {% endfor %}
  }  // namespace {{interface.name}}
{% endfor %}

namespace wl_registry {

void bind(Harness* harness, const actions::wl_registry_bind& action) {
  struct wl_registry* receiver = harness->get_wl_registry(action.receiver());
  if (!receiver)
    return;
  switch (action.global()) {
    {% for interface in interfaces if interface.is_global %}
      case actions::global::{{interface.name}}: {
        if (harness->{{interface.name}}_globals_.empty())
          return;
        void* new_object = wl_registry_bind(
            receiver, harness->{{interface.name}}_globals_[0].first,
            &{{interface.name}}_interface, harness->{{interface.name}}_globals_[0].second);
        {% if interface.has_listener and interface.name != 'wl_display' %}
          {{interface.name}}_add_listener(static_cast<::{{interface.name}}*>(new_object), &{{interface.name}}::kListener, harness);
        {% endif %}
        harness->add_{{interface.name}}(
            static_cast<::{{interface.name}}*>(new_object));
        break;
      }
    {% endfor %}
    case actions::global::global_INT_MIN_SENTINEL_DO_NOT_USE_:
    case actions::global::global_INT_MAX_SENTINEL_DO_NOT_USE_:
    case actions::global::GLOBAL_UNSPECIFIED:
      break;
  }
}

void global(void* data,
            struct wl_registry* registry,
            uint32_t name,
            const char* interface,
            uint32_t version) {
  Harness* harness = static_cast<Harness*>(data);
  {% for interface in interfaces if interface.is_global %}
    if (strcmp(interface, "{{interface.name}}") == 0) {
      harness->{{interface.name}}_globals_.emplace_back(name, version);
      return;
    }
  {% endfor %}
}

}  // namespace wl_registry

void wl_registry::global_remove(void* data, struct wl_registry* registry, uint32_t name) {}

Harness::Harness() {
  wl_display_list_.emplace_back(wl_display_connect(nullptr));
  DCHECK(wl_display_list_.front());
}

Harness::~Harness() {
  {% for interface in interfaces %}
    {% if interface.name != "wl_display" %}
      for (auto ifc : {{interface.name}}_list_) {
        if (ifc)
          {{interface.name}}_destroy(ifc);
      }
    {% endif %}
  {% endfor %}

  // Destroy display last since it is the parent of all the others.
  for (auto display_interface : wl_display_list_) {
    wl_display_disconnect(display_interface);
  }
}

void Harness::Run(const actions::actions& all_steps) {
  for (const actions::action& current : all_steps.acts())
    Run(current);
}

void Harness::Run(const actions::action& current_step) {
  switch (current_step.act_case()) {
    case actions::action::ActCase::ACT_NOT_SET:
      wl_display_roundtrip(wl_display_list_.front());
      break;
    {% for interface in interfaces %}
      {% for request in interface.requests %}
        case actions::action::ActCase::kAct{{(interface.name + "_" + request.name) | replace('_', ' ') | title | replace(' ', '')}}:
          {{interface.name}}::{{request.name}}(this, current_step.act_{{interface.name}}_{{request.name}}());
          break;
      {% endfor %}
    {% endfor %}
  }
}

int Harness::GetFileDescriptor(int id) {
  if (shared_memory_map_.count(id) == 0) {
    base::UnsafeSharedMemoryRegion region = 
        base::UnsafeSharedMemoryRegion::Create(1);
    shared_memory_map_.emplace(id, std::move(region));
  }
  base::subtle::ScopedFDPair fd_pair = 
      base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
          shared_memory_map_[id].Duplicate()).PassPlatformHandle();
  return fd_pair.fd.release();
}

}  // namespace wayland_fuzzer
}  // namespace exo