blob: 528bff1995f32ba72b1d1f4013ffeeecdd778ac1 [file] [log] [blame] [edit]
// Copyright 2019 The Fuchsia 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 "tools/kazoo/syscall_library.h"
#include <stdio.h>
#include <zircon/assert.h>
#include <zircon/compiler.h>
#include <algorithm>
#include "tools/kazoo/alias_workaround.h"
#include "tools/kazoo/output_util.h"
#include "tools/kazoo/string_util.h"
namespace {
bool ValidateTransport(const rapidjson::Value& interface) {
if (!interface.HasMember("maybe_attributes")) {
return false;
}
for (const auto& attrib : interface["maybe_attributes"].GetArray()) {
if (attrib.GetObject()["name"].Get<std::string>() == "Transport") {
if (attrib.GetObject()["value"].Get<std::string>() == "Syscall") {
return true;
}
}
}
return false;
}
std::string StripLibraryName(const std::string& full_name) {
auto prefix_pos = full_name.find_first_of('/');
ZX_ASSERT_MSG(prefix_pos != full_name.npos, "%s has no library prefix", full_name.c_str());
size_t prefix_len = prefix_pos + 1;
return full_name.substr(prefix_len, full_name.size() - prefix_len);
}
std::string GetCategory(const rapidjson::Value& interface, const std::string& interface_name) {
if (interface.HasMember("maybe_attributes")) {
for (const auto& attrib : interface["maybe_attributes"].GetArray()) {
if (attrib.GetObject()["name"].Get<std::string>() == "NoProtocolPrefix") {
return std::string();
}
}
}
return ToLowerAscii(StripLibraryName(interface_name));
}
std::string GetDocAttribute(const rapidjson::Value& method) {
if (!method.HasMember("maybe_attributes")) {
return std::string();
}
for (const auto& attrib : method["maybe_attributes"].GetArray()) {
if (attrib.GetObject()["name"].Get<std::string>() == "Doc") {
return attrib.GetObject()["value"].GetString();
}
}
return std::string();
}
Required GetRequiredAttribute(const rapidjson::Value& field) {
if (!field.HasMember("maybe_attributes")) {
return Required::kNo;
}
for (const auto& attrib : field["maybe_attributes"].GetArray()) {
if (attrib.GetObject()["name"].Get<std::string>() == "Required") {
ZX_ASSERT(attrib.GetObject()["value"].Get<std::string>() == "");
return Required::kYes;
}
}
return Required::kNo;
}
constexpr char kRightsPrefix[] = " Rights: ";
std::string GetShortDescriptionFromDocAttribute(const std::string& full_doc_attribute) {
auto lines = SplitString(full_doc_attribute, '\n', kKeepWhitespace);
if (lines.size() < 1 || lines[0].substr(0, strlen(kRightsPrefix)) == kRightsPrefix) {
return std::string();
}
return TrimString(lines[0], " \t\n");
}
std::vector<std::string> GetCleanDocAttribute(const std::string& full_doc_attribute) {
auto lines = SplitString(full_doc_attribute, '\n', kTrimWhitespace);
if (!lines.empty() && lines[lines.size() - 1].empty()) {
lines.pop_back();
}
std::for_each(lines.begin(), lines.end(),
[](std::string& line) { return TrimString(line, " \t\n"); });
return lines;
}
std::vector<std::string> GetRightsSpecsFromDocAttribute(const std::string& full_doc_attribute) {
auto lines = SplitString(full_doc_attribute, '\n', kKeepWhitespace);
std::vector<std::string> ret;
for (const auto& line : lines) {
if (line.substr(0, strlen(kRightsPrefix)) == kRightsPrefix) {
ret.push_back(line.substr(strlen(kRightsPrefix)));
}
}
return ret;
}
std::optional<Type> PrimitiveTypeFromName(std::string subtype) {
if (subtype == "uint8") {
return Type(TypeUint8{});
} else if (subtype == "uint16") {
return Type(TypeUint16{});
} else if (subtype == "int32") {
return Type(TypeInt32{});
} else if (subtype == "uint32") {
return Type(TypeUint32{});
} else if (subtype == "int64") {
return Type(TypeInt64{});
} else if (subtype == "uint64") {
return Type(TypeUint64{});
} else if (subtype == "bool") {
return Type(TypeBool{});
}
return std::nullopt;
}
Type TypeFromJson(const SyscallLibrary& library, const rapidjson::Value& type,
const rapidjson::Value* type_alias) {
if (type_alias) {
// If the "experimental_maybe_from_type_alias" field is non-null, then the source-level has used
// a type that's declared as "using x = y;". Here, treat various "x"s as special types. This
// is likely mostly (?) temporary until there's 1) a more nailed down alias implementation in
// the front end (fidlc) and 2) we move various parts of zx.fidl from being built-in to fidlc to
// actual source level fidl and shared between the syscall definitions and normal FIDL.
const std::string full_name((*type_alias)["name"].GetString());
if (full_name.substr(0, 3) == "zx/") {
const std::string name = full_name.substr(3);
if (name == "duration" || name == "Futex" || name == "koid" || name == "paddr" ||
name == "rights" || name == "signals" || name == "status" || name == "time" ||
name == "ticks" || name == "vaddr" || name == "VmOption") {
return Type(TypeZxBasicAlias(CamelToSnake(name)));
}
}
const std::string name = StripLibraryName(full_name);
if (name == "uintptr") {
return Type(TypeUintptrT{});
}
if (name == "usize") {
return Type(TypeSizeT{});
}
Type workaround_type;
if (AliasWorkaround(name, library, &workaround_type)) {
return workaround_type;
}
return library.TypeFromIdentifier(full_name);
}
if (!type.HasMember("kind")) {
fprintf(stderr, "type has no 'kind'\n");
return Type();
}
std::string kind = type["kind"].GetString();
if (kind == "primitive") {
const rapidjson::Value& subtype_value = type["subtype"];
ZX_ASSERT(subtype_value.IsString());
std::string subtype = subtype_value.GetString();
return PrimitiveTypeFromName(subtype).value();
} else if (kind == "identifier") {
std::string id = type["identifier"].GetString();
return library.TypeFromIdentifier(type["identifier"].GetString());
} else if (kind == "handle") {
return Type(TypeHandle(type["subtype"].GetString()));
} else if (kind == "vector") {
Type contained_type = TypeFromJson(library, type["element_type"], nullptr);
return Type(TypeVector(contained_type));
} else if (kind == "string") {
return Type(TypeString{});
}
ZX_ASSERT_MSG(false, "TODO: kind=%s", kind.c_str());
return Type();
}
} // namespace
bool Syscall::HasAttribute(const char* attrib_name) const {
return attributes_.find(attrib_name) != attributes_.end();
}
std::string Syscall::GetAttribute(const char* attrib_name) const {
ZX_ASSERT(HasAttribute(attrib_name));
return attributes_.find(attrib_name)->second;
}
// Converts from FIDL style to C/Kernel style:
// - string to pointer+size
// - vector to pointer+size
// - structs become pointer-to-struct (const on input, mutable on output)
// - etc.
bool Syscall::MapRequestResponseToKernelAbi() {
ZX_ASSERT(kernel_arguments_.empty());
// Used for input arguments, which default to const unless alread specified mutable.
auto default_to_const = [](Constness constness) {
if (constness == Constness::kUnspecified) {
return Constness::kConst;
}
return constness;
};
auto output_optionality = [](Optionality optionality) {
// If explicitly made optional then leave it alone, otherwise mark non-optional.
if (optionality == Optionality::kOutputOptional) {
return optionality;
}
return Optionality::kOutputNonOptional;
};
auto get_vector_size_name = [](const StructMember& member) {
std::string prefix, suffix;
// If it's a char* or void*, blah_size seems more natural, otherwise, num_blahs is moreso.
if ((member.type().DataAsVector().contained_type().IsChar() ||
member.type().DataAsVector().contained_type().IsVoid()) &&
(member.name() != "bytes")) {
suffix = "_size";
} else {
prefix = "num_";
}
return std::tuple<std::string, bool>(prefix + member.name() + suffix,
member.type().DataAsVector().uint32_size());
};
// Map inputs first, converting vectors, strings, and structs to their corresponding input types
// as we go.
for (const auto& m : request_.members()) {
const Type& type = m.type();
if (type.IsVector()) {
Type pointer_to_subtype(
TypePointer(type.DataAsVector().contained_type(), IsDecayedVectorTag{}),
default_to_const(type.constness()), Optionality::kInputArgument);
kernel_arguments_.emplace_back(m.name(), pointer_to_subtype, m.attributes());
auto [size_name, is_u32] = get_vector_size_name(m);
kernel_arguments_.emplace_back(size_name, is_u32 ? Type(TypeUint32{}) : Type(TypeSizeT{}),
std::map<std::string, std::string>{});
} else if (type.IsString()) {
// char*, using the same constness as the string was specified as.
kernel_arguments_.emplace_back(
m.name(), Type(TypePointer(Type(TypeChar{})), default_to_const(type.constness()),
Optionality::kInputArgument), m.attributes());
kernel_arguments_.emplace_back(m.name() + "_size", Type(TypeSizeT{}),
std::map<std::string, std::string>{});
} else if (type.IsStruct()) {
// If it's a struct, map to struct*, const unless otherwise specified. The pointer takes the
// constness of the struct.
kernel_arguments_.emplace_back(
m.name(),
Type(TypePointer(type), default_to_const(type.constness()), Optionality::kInputArgument),
m.attributes());
} else {
// Otherwise, copy it over, unchanged other than to tag it as input.
kernel_arguments_.emplace_back(m.name(),
Type(type.type_data(), default_to_const(m.type().constness()),
Optionality::kInputArgument),
m.attributes());
}
}
// Similarly for the outputs, but turning buffers into outparams, and with special handling for
// the C return value.
size_t start_at;
if (response_.members().size() == 0 || !response_.members()[0].type().IsSimpleType()) {
kernel_return_type_ = Type(TypeVoid{});
start_at = 0;
} else {
kernel_return_type_ = response_.members()[0].type();
start_at = 1;
}
for (size_t i = start_at; i < response_.members().size(); ++i) {
const StructMember& m = response_.members()[i];
const Type& type = m.type();
if (type.IsVector()) {
// TODO(syscall-fidl-transition): These vector types aren't marked as non-optional in
// abigen, but generally they probably are.
Type pointer_to_subtype(
TypePointer(type.DataAsVector().contained_type(), IsDecayedVectorTag{}),
Constness::kMutable, Optionality::kOutputOptional);
kernel_arguments_.emplace_back(m.name(), pointer_to_subtype, m.attributes());
auto [size_name, is_u32] = get_vector_size_name(m);
kernel_arguments_.emplace_back(size_name, is_u32 ? Type(TypeUint32{}) : Type(TypeSizeT{}),
std::map<std::string, std::string>{});
} else if (type.IsString()) {
kernel_arguments_.emplace_back(
m.name(),
Type(TypePointer(Type(TypeChar{})), Constness::kMutable, Optionality::kOutputOptional),
m.attributes());
kernel_arguments_.emplace_back(m.name() + "_size", Type(TypeSizeT{}),
std::map<std::string, std::string>{});
} else if (type.IsPointer()) {
kernel_arguments_.emplace_back(
m.name(), Type(type.type_data(), Constness::kMutable, Optionality::kOutputOptional),
m.attributes());
} else {
// Everything else becomes a T* (to make it an out parameter).
kernel_arguments_.emplace_back(m.name(), Type(TypePointer(type), Constness::kMutable,
output_optionality(type.optionality())),
m.attributes());
}
}
// Now that we've got all the arguments in their natural order, honor the
// "ArgReorder" attribute, which reorders arguments arbitrarily to match
// existing declaration order.
return HandleArgReorder();
}
bool Syscall::HandleArgReorder() {
constexpr const char kReorderAttribName[] = "ArgReorder";
if (HasAttribute(kReorderAttribName)) {
const std::string& target_order_string = GetAttribute(kReorderAttribName);
std::vector<std::string> target_order = SplitString(target_order_string, ',', kTrimWhitespace);
if (kernel_arguments_.size() != target_order.size()) {
fprintf(stderr,
"Attempting to reorder arguments for '%s', and there's %zu kernel arguments, but %zu "
"arguments in the reorder spec.\n",
name().c_str(), kernel_arguments_.size(), target_order.size());
return false;
}
std::vector<StructMember> new_kernel_arguments;
for (const auto& target : target_order) {
bool found = false;
for (const auto& ka : kernel_arguments_) {
if (ka.name() == target) {
new_kernel_arguments.push_back(ka);
found = true;
break;
}
}
if (!found) {
fprintf(stderr,
"Attempting to reorder arguments for '%s', but '%s' wasn't one of the kernel "
"arguments.\n",
name().c_str(), target.c_str());
return false;
}
}
kernel_arguments_ = std::move(new_kernel_arguments);
}
return true;
}
void Enum::AddMember(const std::string& member_name, EnumMember member) {
ZX_ASSERT(!HasMember(member_name));
members_[member_name] = std::move(member);
insertion_order_.push_back(member_name);
}
bool Enum::HasMember(const std::string& member_name) const {
return members_.find(member_name) != members_.end();
}
const EnumMember& Enum::ValueForMember(const std::string& member_name) const {
ZX_ASSERT(HasMember(member_name));
return members_.find(member_name)->second;
}
Type SyscallLibrary::TypeFromIdentifier(const std::string& id) const {
for (const auto& bits : bits_) {
if (bits->id() == id) {
// TODO(scottmg): Consider if we need to separate bits from enum here.
return Type(TypeEnum{bits.get()});
}
}
for (const auto& enm : enums_) {
if (enm->id() == id) {
return Type(TypeEnum{enm.get()});
}
}
for (const auto& alias : type_aliases_) {
if (alias->id() == id) {
return Type(TypeAlias(alias.get()));
}
}
for (const auto& strukt : structs_) {
if (strukt->id() == id) {
return Type(TypeStruct(strukt.get()));
}
}
// TODO: Load struct, union, usings and return one of them here!
ZX_ASSERT_MSG(false, "unhandled TypeFromIdentifier for %s", id.c_str());
return Type();
}
Type SyscallLibrary::TypeFromName(const std::string& name) const {
if (auto primitive = PrimitiveTypeFromName(name); primitive.has_value()) {
return primitive.value();
}
return TypeFromIdentifier(name);
}
void SyscallLibrary::FilterSyscalls(const std::set<std::string>& attributes_to_exclude) {
std::vector<std::unique_ptr<Syscall>> filtered;
for (auto& syscall : syscalls_) {
if (std::any_of(
attributes_to_exclude.begin(), attributes_to_exclude.end(),
[&syscall](const std::string& x) { return syscall->HasAttribute(x.c_str()); })) {
continue;
}
filtered.push_back(std::move(syscall));
}
syscalls_ = std::move(filtered);
}
// static
bool SyscallLibraryLoader::FromJson(const std::string& json_ir, SyscallLibrary* library) {
rapidjson::Document document;
document.Parse(json_ir);
// Maybe do schema validation here, though we rely on fidlc for many details
// and general sanity, so probably only in a diagnostic mode.
if (!document.IsObject()) {
fprintf(stderr, "Root of json wasn't object.\n");
return false;
}
library->name_ = document["name"].GetString();
if (library->name_ != "zx" && library->name_ != "zxio") {
fprintf(stderr, "Library name %s wasn't zx or zxio as expected.\n", library->name_.c_str());
return false;
}
ZX_ASSERT(library->syscalls_.empty());
// The order of these loads is significant. For example, enums must be loaded to be able to be
// referred to by interface methods.
if (!LoadBits(document, library)) {
return false;
}
if (!LoadEnums(document, library)) {
return false;
}
if (!LoadTypeAliases(document, library)) {
return false;
}
if (!LoadStructs(document, library)) {
return false;
}
if (!LoadTables(document, library)) {
return false;
}
if (!LoadInterfaces(document, library)) {
return false;
}
return true;
}
// 'bits' are currently handled the same as enums, so just use Enum for now as the underlying
// data storage.
//
// static
std::unique_ptr<Enum> SyscallLibraryLoader::ConvertBitsOrEnumMember(const rapidjson::Value& json) {
auto obj = std::make_unique<Enum>();
std::string full_name = json["name"].GetString();
obj->id_ = full_name;
std::string stripped = StripLibraryName(full_name);
obj->original_name_ = stripped;
obj->base_name_ = CamelToSnake(stripped);
std::string doc_attribute = GetDocAttribute(json);
obj->description_ = GetCleanDocAttribute(doc_attribute);
const rapidjson::Value& type = json["type"];
if (type.IsString()) {
// Enum
obj->underlying_type_ = PrimitiveTypeFromName(type.GetString()).value();
} else {
ZX_ASSERT(type.IsObject());
// Bits
ZX_ASSERT_MSG(type["kind"].GetString() == std::string("primitive"),
"Enum %s not backed by primitive type", full_name.c_str());
const rapidjson::Value& subtype_value = type["subtype"];
ZX_ASSERT(subtype_value.IsString());
std::string subtype = subtype_value.GetString();
obj->underlying_type_ = PrimitiveTypeFromName(subtype).value();
}
for (const auto& member : json["members"].GetArray()) {
ZX_ASSERT_MSG(member["value"]["kind"] == "literal", "TODO: More complex value expressions");
uint64_t member_value;
std::string decimal = member["value"]["literal"]["value"].GetString();
if (obj->underlying_type().IsUnsignedInt()) {
member_value = StringToUInt(decimal);
} else if (obj->underlying_type().IsSignedInt()) {
member_value = StringToInt(decimal);
} else {
ZX_PANIC("Unreachable");
}
std::string doc_attribute = GetDocAttribute(member);
obj->AddMember(
member["name"].GetString(),
EnumMember{.value = member_value, .description = GetCleanDocAttribute(doc_attribute)});
}
return obj;
}
// static
bool SyscallLibraryLoader::LoadBits(const rapidjson::Document& document, SyscallLibrary* library) {
for (const auto& bits_json : document["bits_declarations"].GetArray()) {
library->bits_.push_back(ConvertBitsOrEnumMember(bits_json));
}
return true;
}
// static
bool SyscallLibraryLoader::LoadEnums(const rapidjson::Document& document, SyscallLibrary* library) {
for (const auto& enum_json : document["enum_declarations"].GetArray()) {
library->enums_.push_back(ConvertBitsOrEnumMember(enum_json));
}
return true;
}
// static
bool SyscallLibraryLoader::LoadInterfaces(const rapidjson::Document& document,
SyscallLibrary* library) {
for (const auto& interface : document["interface_declarations"].GetArray()) {
if (!ValidateTransport(interface)) {
fprintf(stderr, "Expected Transport to be Syscall.\n");
return false;
}
std::string interface_name = interface["name"].GetString();
std::string category = GetCategory(interface, interface_name);
for (const auto& method : interface["methods"].GetArray()) {
auto syscall = std::make_unique<Syscall>();
syscall->id_ = interface_name;
syscall->original_name_ = method["name"].GetString();
syscall->category_ = category;
std::string snake_name = CamelToSnake(method["name"].GetString());
if (!StartsWith(snake_name, category)) {
snake_name = category + (category.empty() ? "" : "_") + snake_name;
}
syscall->name_ = snake_name;
syscall->is_noreturn_ = !method["has_response"].GetBool();
const auto doc_attribute = GetDocAttribute(method);
syscall->short_description_ = GetShortDescriptionFromDocAttribute(doc_attribute);
syscall->rights_specs_ = GetRightsSpecsFromDocAttribute(doc_attribute);
if (method.HasMember("maybe_attributes")) {
for (const auto& attrib : method["maybe_attributes"].GetArray()) {
syscall->attributes_[attrib["name"].GetString()] = attrib["value"].GetString();
}
}
ZX_ASSERT(method["has_request"].GetBool()); // Events are not expected in syscalls.
auto add_struct_members = [&library](Struct* strukt, const rapidjson::Value& arg) {
const auto* type_alias = arg.HasMember("experimental_maybe_from_type_alias")
? &arg["experimental_maybe_from_type_alias"]
: nullptr;
strukt->members_.emplace_back(arg["name"].GetString(),
TypeFromJson(*library, arg["type"], type_alias),
std::map<std::string, std::string>{});
if (arg.HasMember("maybe_attributes")) {
for (const auto& attrib : arg["maybe_attributes"].GetArray()) {
strukt->members_.back().attributes_[attrib["name"].GetString()] =
attrib["value"].GetString();
}
}
};
Struct& req = syscall->request_;
req.id_ = syscall->original_name_ + "#request";
for (const auto& arg : method["maybe_request"].GetArray()) {
add_struct_members(&req, arg);
}
if (method["has_response"].GetBool()) {
Struct& resp = syscall->response_;
resp.id_ = syscall->original_name_ + "#response";
for (const auto& arg : method["maybe_response"].GetArray()) {
add_struct_members(&resp, arg);
}
}
if (!syscall->MapRequestResponseToKernelAbi()) {
return false;
}
library->syscalls_.push_back(std::move(syscall));
}
}
return true;
}
// static
bool SyscallLibraryLoader::LoadTypeAliases(const rapidjson::Document& document,
SyscallLibrary* library) {
for (const auto& type_alias_json : document["type_alias_declarations"].GetArray()) {
auto obj = std::make_unique<Alias>();
std::string full_name = type_alias_json["name"].GetString();
obj->id_ = full_name;
std::string stripped = StripLibraryName(full_name);
obj->original_name_ = stripped;
obj->base_name_ = CamelToSnake(stripped);
const rapidjson::Value& partial_type_ctor = type_alias_json["partial_type_ctor"];
ZX_ASSERT(partial_type_ctor.IsObject());
obj->partial_type_ctor_ = partial_type_ctor["name"].GetString();
std::string doc_attribute = GetDocAttribute(type_alias_json);
obj->description_ = GetCleanDocAttribute(doc_attribute);
library->type_aliases_.push_back(std::move(obj));
}
return true;
}
// static
bool SyscallLibraryLoader::LoadStructs(const rapidjson::Document& document,
SyscallLibrary* library) {
// TODO(scottmg): In transition, we're still relying on the existing Zircon headers to define all
// these structures. So we only load their names for the time being, which is enough for now to
// know that there's something in the .fidl file where the struct is declared. Note also that
// interface parsing fills out request/response "structs", so that code should likely be shared
// when this is implemented.
for (const auto& struct_json : document["struct_declarations"].GetArray()) {
auto obj = std::make_unique<Struct>();
std::string full_name = struct_json["name"].GetString();
obj->id_ = full_name;
std::string stripped = StripLibraryName(full_name);
obj->original_name_ = stripped;
obj->base_name_ = CamelToSnake(stripped);
library->structs_.push_back(std::move(obj));
}
return true;
}
// static
bool SyscallLibraryLoader::LoadTables(const rapidjson::Document& document,
SyscallLibrary* library) {
for (const auto& json : document["table_declarations"].GetArray()) {
auto obj = std::make_unique<Table>();
std::string full_name = json["name"].GetString();
obj->id_ = full_name;
std::string stripped = StripLibraryName(full_name);
obj->original_name_ = stripped;
obj->base_name_ = CamelToSnake(stripped);
std::string doc_attribute = GetDocAttribute(json);
obj->description_ = GetCleanDocAttribute(doc_attribute);
std::vector<TableMember> members;
for (const auto& member : json["members"].GetArray()) {
std::string name = member["name"].GetString();
const auto* type_alias = member.HasMember("experimental_maybe_from_type_alias")
? &member["experimental_maybe_from_type_alias"]
: nullptr;
Type type = TypeFromJson(*library, member["type"], type_alias);
Required required = GetRequiredAttribute(member);
std::string doc_attribute = GetDocAttribute(member);
std::vector<std::string> description = GetCleanDocAttribute(doc_attribute);
members.emplace_back(std::move(name), std::move(type), std::move(description), required);
}
obj->members_ = std::move(members);
library->tables_.push_back(std::move(obj));
}
return true;
}