| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // -*- mode: C++ -*- |
| // |
| // Copyright 2021-2022 Google LLC |
| // |
| // Licensed under the Apache License v2.0 with LLVM Exceptions (the |
| // "License"); you may not use this file except in compliance with the |
| // License. You may obtain a copy of the License at |
| // |
| // https://llvm.org/LICENSE.txt |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| // Author: Giuliano Procida |
| // Author: Ignes Simeonova |
| |
| #include "abigail_reader.h" |
| |
| #include <fcntl.h> |
| #include <unistd.h> |
| |
| #include <cerrno> |
| #include <cstddef> |
| #include <cstdint> |
| #include <cstring> |
| #include <functional> |
| #include <iostream> |
| #include <map> |
| #include <string> |
| #include <type_traits> |
| #include <utility> |
| |
| #include <libxml/parser.h> |
| #include "crc.h" |
| #include "error.h" |
| |
| namespace stg { |
| namespace abixml { |
| |
| namespace { |
| |
| const char* FromLibxml(const xmlChar* str) { |
| return reinterpret_cast<const char*>(str); |
| } |
| |
| const xmlChar* ToLibxml(const char* str) { |
| return reinterpret_cast<const xmlChar*>(str); |
| } |
| |
| std::string GetElementName(xmlNodePtr element) { |
| return std::string(FromLibxml(element->name)); |
| } |
| |
| void CheckElementName(const char* name, xmlNodePtr element) { |
| const auto element_name = FromLibxml(element->name); |
| if (strcmp(element_name, name) != 0) |
| Die() << "expected element '" << name |
| << "' but got '" << element_name << "'"; |
| } |
| |
| xmlNodePtr GetOnlyChild(const std::string& name, xmlNodePtr element) { |
| xmlNodePtr child = xmlFirstElementChild(element); |
| Check(child && !xmlNextElementSibling(child)) |
| << name << " with not exactly one child element"; |
| return child; |
| } |
| |
| std::optional<std::string> GetAttribute(xmlNodePtr node, const char* name) { |
| std::optional<std::string> result; |
| xmlChar* attribute = xmlGetProp(node, ToLibxml(name)); |
| if (attribute) { |
| result = {FromLibxml(attribute)}; |
| xmlFree(attribute); |
| } |
| return result; |
| } |
| |
| std::string GetAttributeOrDie(xmlNodePtr node, const char* name) { |
| xmlChar* attribute = xmlGetProp(node, ToLibxml(name)); |
| if (!attribute) |
| Die() << "element '" << FromLibxml(node->name) |
| << "' missing attribute '" << name << "'"; |
| std::string result = FromLibxml(attribute); |
| xmlFree(attribute); |
| return result; |
| } |
| |
| template <typename T> |
| std::optional<T> Parse(const std::string& value) { |
| T result; |
| std::istringstream is(value); |
| is >> std::noskipws >> result; |
| if (!is || !is.eof()) |
| return {}; |
| else |
| return {result}; |
| } |
| |
| template <> |
| std::optional<bool> Parse<bool>(const std::string& value) { |
| if (value == "yes") |
| return {true}; |
| if (value == "no") |
| return {false}; |
| return {}; |
| } |
| |
| template <> |
| std::optional<ElfSymbol::SymbolType> Parse<ElfSymbol::SymbolType>( |
| const std::string& value) { |
| if (value == "object-type") |
| return {ElfSymbol::SymbolType::OBJECT}; |
| if (value == "func-type") |
| return {ElfSymbol::SymbolType::FUNCTION}; |
| if (value == "common-type") |
| return {ElfSymbol::SymbolType::COMMON}; |
| if (value == "tls-type") |
| return {ElfSymbol::SymbolType::TLS}; |
| return {}; |
| } |
| |
| template <> |
| std::optional<ElfSymbol::Binding> Parse<ElfSymbol::Binding>( |
| const std::string& value) { |
| if (value == "global-binding") |
| return {ElfSymbol::Binding::GLOBAL}; |
| if (value == "local-binding") |
| return {ElfSymbol::Binding::LOCAL}; |
| if (value == "weak-binding") |
| return {ElfSymbol::Binding::WEAK}; |
| if (value == "gnu-unique-binding") |
| return {ElfSymbol::Binding::GNU_UNIQUE}; |
| return {}; |
| } |
| |
| template <> |
| std::optional<ElfSymbol::Visibility> Parse<ElfSymbol::Visibility>( |
| const std::string& value) { |
| if (value == "default-visibility") |
| return {ElfSymbol::Visibility::DEFAULT}; |
| if (value == "protected-visibility") |
| return {ElfSymbol::Visibility::PROTECTED}; |
| if (value == "hidden-visibility") |
| return {ElfSymbol::Visibility::HIDDEN}; |
| if (value == "internal-visibility") |
| return {ElfSymbol::Visibility::INTERNAL}; |
| return {}; |
| } |
| |
| template <> |
| std::optional<CRC> Parse<CRC>(const std::string& value) { |
| CRC result; |
| std::istringstream is(value); |
| is >> std::noskipws >> std::hex >> result.number; |
| if (!is || !is.eof()) |
| return {}; |
| else |
| return {result}; |
| } |
| |
| template <typename T> |
| T GetParsedValueOrDie(xmlNodePtr element, const char* name, |
| const std::string& value, const std::optional<T>& parse) { |
| if (!parse) |
| Die() << "element '" << FromLibxml(element->name) |
| << "' has attribute '" << name |
| << "' with bad value '" << value << "'"; |
| return *parse; |
| } |
| |
| template <typename T> |
| T ReadAttributeOrDie(xmlNodePtr element, const char* name) { |
| const auto value = GetAttributeOrDie(element, name); |
| return GetParsedValueOrDie(element, name, value, Parse<T>(value)); |
| } |
| |
| template <typename T> |
| std::optional<T> ReadAttribute(xmlNodePtr element, const char* name) { |
| const auto value = GetAttribute(element, name); |
| if (!value) |
| return {}; |
| return {GetParsedValueOrDie(element, name, *value, Parse<T>(*value))}; |
| } |
| |
| template <typename T> |
| T ReadAttribute(xmlNodePtr element, const char* name, T default_value) { |
| const auto value = GetAttribute(element, name); |
| if (!value) |
| return default_value; |
| return GetParsedValueOrDie(element, name, *value, Parse<T>(*value)); |
| } |
| |
| template <typename T> |
| T ReadAttribute(xmlNodePtr element, const char* name, |
| std::function<std::optional<T>(const std::string&)> parse) { |
| const auto value = GetAttributeOrDie(element, name); |
| return GetParsedValueOrDie(element, name, value, parse(value)); |
| } |
| |
| std::optional<uint64_t> ParseLength(const std::string& value) { |
| if (value == "infinite") |
| return {0}; |
| return Parse<uint64_t>(value); |
| } |
| |
| std::optional<PointerReference::Kind> ParseReferenceKind( |
| const std::string& value) { |
| if (value == "lvalue") |
| return {PointerReference::Kind::LVALUE_REFERENCE}; |
| if (value == "rvalue") |
| return {PointerReference::Kind::RVALUE_REFERENCE}; |
| return {}; |
| } |
| |
| class PushScopeName { |
| public: |
| PushScopeName(std::string& scope_name, const std::string& name) |
| : scope_name_(scope_name), old_size_(scope_name.size()) { |
| scope_name_ += name; |
| scope_name_ += "::"; |
| } |
| PushScopeName(const PushScopeName& other) = delete; |
| PushScopeName& operator=(const PushScopeName& other) = delete; |
| ~PushScopeName() { scope_name_.resize(old_size_); } |
| |
| private: |
| std::string& scope_name_; |
| const size_t old_size_; |
| }; |
| |
| } // namespace |
| |
| Typing::Typing(Graph& graph, bool verbose) |
| : graph_(graph), verbose_(verbose) { } |
| |
| Id Typing::GetNode(const std::string& type_id) { |
| const auto [it, inserted] = type_ids_.insert({type_id, Id(0)}); |
| if (inserted) |
| it->second = graph_.Allocate(); |
| return it->second; |
| } |
| |
| Id Typing::GetEdge(xmlNodePtr element) { |
| return GetNode(GetAttributeOrDie(element, "type-id")); |
| } |
| |
| Id Typing::GetVariadic() { |
| if (!variadic_) { |
| variadic_ = {graph_.Add(Make<Variadic>())}; |
| if (verbose_) |
| std::cerr << *variadic_ << " variadic parameter\n"; |
| } |
| return *variadic_; |
| } |
| |
| Corpus::Corpus(Graph& graph, bool verbose, Typing& typing) |
| : graph_(graph), verbose_(verbose), typing_(typing) { } |
| |
| std::unique_ptr<Node> Corpus::MakeFunctionType(xmlNodePtr function) { |
| std::vector<Parameter> parameters; |
| std::optional<Id> return_type; |
| for (auto child = xmlFirstElementChild(function); child; |
| child = xmlNextElementSibling(child)) { |
| const auto child_name = GetElementName(child); |
| if (return_type) |
| Die() << "unexpected element after return-type"; |
| if (child_name == "parameter") { |
| const auto is_variadic = ReadAttribute<bool>(child, "is-variadic", false); |
| if (is_variadic) { |
| const auto type = typing_.GetVariadic(); |
| Parameter parameter{.name = std::string(), .type_id = type}; |
| parameters.push_back(std::move(parameter)); |
| } else { |
| const auto name = GetAttribute(child, "name"); |
| const auto type = typing_.GetEdge(child); |
| Parameter parameter{.name = name ? *name : std::string(), |
| .type_id = type}; |
| parameters.push_back(std::move(parameter)); |
| } |
| } else if (child_name == "return") { |
| return_type = {typing_.GetEdge(child)}; |
| } else { |
| Die() << "unrecognised " << FromLibxml(function->name) |
| << " child element '" << child_name << "'"; |
| } |
| } |
| if (!return_type) |
| Die() << "missing return-type"; |
| if (verbose_) { |
| std::cerr << " made function type ("; |
| bool comma = false; |
| for (const auto& p : parameters) { |
| if (comma) |
| std::cerr << ", "; |
| std::cerr << p.type_id; |
| comma = true; |
| } |
| std::cerr << ") -> " << *return_type << "\n"; |
| } |
| return Make<Function>(*return_type, parameters); |
| } |
| |
| std::map<std::string, Id> Corpus::ProcessCorpus(xmlNodePtr corpus) { |
| for (auto element = xmlFirstElementChild(corpus); element; |
| element = xmlNextElementSibling(element)) { |
| const auto name = GetElementName(element); |
| if (name == "elf-function-symbols" || name == "elf-variable-symbols") { |
| ProcessSymbols(element); |
| } else if (name == "elf-needed") { |
| // ignore this |
| } else if (name == "abi-instr") { |
| ProcessInstr(element); |
| } else { |
| Die() << "unrecognised abi-corpus child element '" << name << "'"; |
| } |
| } |
| return BuildSymbols(); |
| } |
| |
| void Corpus::ProcessSymbols(xmlNodePtr symbols) { |
| for (auto element = xmlFirstElementChild(symbols); element; |
| element = xmlNextElementSibling(element)) { |
| CheckElementName("elf-symbol", element); |
| ProcessSymbol(element); |
| } |
| } |
| |
| void Corpus::ProcessSymbol(xmlNodePtr symbol) { |
| // Symbol processing is done in two parts. In this first part, we parse just |
| // enough XML attributes to generate a symbol id and determine any aliases. |
| // Symbol ids in this format can be found in elf-symbol alias attributes and |
| // in {var,function}-decl elf-symbol-id attributes. |
| const auto name = GetAttributeOrDie(symbol, "name"); |
| const auto version = |
| ReadAttribute<std::string>(symbol, "version", std::string()); |
| const bool is_default_version = |
| ReadAttribute<bool>(symbol, "is-default-version", false); |
| const auto alias = GetAttribute(symbol, "alias"); |
| |
| std::string elf_symbol_id = name; |
| if (!version.empty()) { |
| elf_symbol_id += '@'; |
| if (is_default_version) |
| elf_symbol_id += '@'; |
| elf_symbol_id += version; |
| } |
| |
| const SymbolInfo info{name, version, is_default_version, symbol}; |
| Check(symbol_info_map_.emplace(elf_symbol_id, std::move(info)).second) |
| << "multiple symbols with id " << elf_symbol_id; |
| |
| if (alias) { |
| std::istringstream is(*alias); |
| std::string item; |
| while (std::getline(is, item, ',')) |
| Check(alias_to_main_.insert({item, elf_symbol_id}).second) |
| << "multiple aliases with id " << elf_symbol_id; |
| } |
| } |
| |
| bool Corpus::ProcessUserDefinedType(const std::string& name, Id id, |
| xmlNodePtr decl) { |
| if (name == "typedef-decl") { |
| ProcessTypedef(id, decl); |
| } else if (name == "class-decl") { |
| ProcessStructUnion(id, true, decl); |
| } else if (name == "union-decl") { |
| ProcessStructUnion(id, false, decl); |
| } else if (name == "enum-decl") { |
| ProcessEnum(id, decl); |
| } else { |
| return false; |
| } |
| return true; |
| } |
| |
| void Corpus::ProcessScope(xmlNodePtr scope) { |
| for (auto element = xmlFirstElementChild(scope); element; |
| element = xmlNextElementSibling(element)) { |
| const auto name = GetElementName(element); |
| const auto type_id = GetAttribute(element, "id"); |
| // all type elements have "id", all non-types do not |
| if (type_id) { |
| const auto id = typing_.GetNode(*type_id); |
| if (graph_.Is(id)) { |
| std::cerr << "duplicate definition of type '" << *type_id << "'\n"; |
| continue; |
| } |
| if (name == "function-type") { |
| ProcessFunctionType(id, element); |
| } else if (name == "pointer-type-def") { |
| ProcessPointer(id, true, element); |
| } else if (name == "reference-type-def") { |
| ProcessPointer(id, false, element); |
| } else if (name == "qualified-type-def") { |
| ProcessQualified(id, element); |
| } else if (name == "array-type-def") { |
| ProcessArray(id, element); |
| } else if (name == "type-decl") { |
| ProcessTypeDecl(id, element); |
| } else if (!ProcessUserDefinedType(name, id, element)) { |
| Die() << "bad abi-instr type child element '" << name << "'"; |
| } |
| } else { |
| if (name == "var-decl") { |
| ProcessDecl(true, element); |
| } else if (name == "function-decl") { |
| ProcessDecl(false, element); |
| } else if (name == "namespace-decl") { |
| ProcessNamespace(element); |
| } else { |
| Die() << "bad abi-instr non-type child element '" << name << "'"; |
| } |
| } |
| } |
| } |
| |
| void Corpus::ProcessInstr(xmlNodePtr instr) { ProcessScope(instr); } |
| |
| void Corpus::ProcessNamespace(xmlNodePtr scope) { |
| const auto name = GetAttributeOrDie(scope, "name"); |
| PushScopeName push_scope_name(scope_name_, name); |
| ProcessScope(scope); |
| } |
| |
| Id Corpus::ProcessDecl(bool is_variable, xmlNodePtr decl) { |
| const auto name = scope_name_ + GetAttributeOrDie(decl, "name"); |
| const auto symbol_id = GetAttribute(decl, "elf-symbol-id"); |
| const auto type = is_variable ? typing_.GetEdge(decl) |
| : graph_.Add(MakeFunctionType(decl)); |
| if (verbose_ && !is_variable) |
| std::cerr << type << " function type for function " << name << "\n"; |
| if (symbol_id) { |
| // There's a link to an ELF symbol. |
| if (verbose_) |
| std::cerr << "ELF symbol " << *symbol_id << " of " << type << "\n"; |
| const auto [it, inserted] = symbol_id_and_full_name_.emplace( |
| *symbol_id, std::make_pair(type, name)); |
| if (!inserted && it->second.first != type) |
| Die() << "conflicting types for '" << *symbol_id << "'"; |
| } |
| return type; |
| } |
| |
| void Corpus::ProcessFunctionType(Id id, xmlNodePtr function) { |
| graph_.Set(id, MakeFunctionType(function)); |
| } |
| |
| void Corpus::ProcessTypedef(Id id, xmlNodePtr type_definition) { |
| const auto name = scope_name_ + GetAttributeOrDie(type_definition, "name"); |
| const auto type = typing_.GetEdge(type_definition); |
| graph_.Set(id, Make<Typedef>(name, type)); |
| if (verbose_) |
| std::cerr << id << " typedef " << name << " of " << type << "\n"; |
| } |
| |
| void Corpus::ProcessPointer(Id id, bool is_pointer, xmlNodePtr pointer) { |
| const auto type = typing_.GetEdge(pointer); |
| const auto kind = is_pointer ? PointerReference::Kind::POINTER |
| : ReadAttribute<PointerReference::Kind>( |
| pointer, "kind", &ParseReferenceKind); |
| graph_.Set(id, Make<PointerReference>(kind, type)); |
| if (verbose_) |
| std::cerr << id << " " << kind << " to " << type << "\n"; |
| } |
| |
| void Corpus::ProcessQualified(Id id, xmlNodePtr qualified) { |
| std::vector<Qualifier> qualifiers; |
| // Do these in reverse order so we get CVR ordering. |
| if (ReadAttribute<bool>(qualified, "restrict", false)) |
| qualifiers.push_back(Qualifier::RESTRICT); |
| if (ReadAttribute<bool>(qualified, "volatile", false)) |
| qualifiers.push_back(Qualifier::VOLATILE); |
| if (ReadAttribute<bool>(qualified, "const", false)) |
| qualifiers.push_back(Qualifier::CONST); |
| Check(!qualifiers.empty()) << "qualified-type-def has no qualifiers"; |
| // Handle multiple qualifiers by unconditionally adding as new nodes all but |
| // the last qualifier which is set into place. |
| if (verbose_) |
| std::cerr << id << " qualified"; |
| auto type = typing_.GetEdge(qualified); |
| auto count = qualifiers.size(); |
| for (auto qualifier : qualifiers) { |
| --count; |
| auto node = Make<Qualified>(qualifier, type); |
| if (count) |
| type = graph_.Add(std::move(node)); |
| else |
| graph_.Set(id, std::move(node)); |
| if (verbose_) |
| std::cerr << ' ' << qualifier; |
| } |
| if (verbose_) |
| std::cerr << " of " << id << "\n"; |
| } |
| |
| void Corpus::ProcessArray(Id id, xmlNodePtr array) { |
| std::vector<size_t> dimensions; |
| for (auto child = xmlFirstElementChild(array); child; |
| child = xmlNextElementSibling(child)) { |
| CheckElementName("subrange", child); |
| const auto length = ReadAttribute<uint64_t>(child, "length", &ParseLength); |
| dimensions.push_back(length); |
| } |
| Check(!dimensions.empty()) << "array-type-def element has no children"; |
| // int[M][N] means array[M] of array[N] of int |
| // |
| // We need to chain a bunch of types together: |
| // |
| // id = array[n] of id = ... = array[n] of id |
| // |
| // where the first id is the new type in slot ix |
| // and the last id is the old type in slot type |
| // |
| // Use the same approach as for qualifiers. |
| if (verbose_) |
| std::cerr << id << " array"; |
| auto type = typing_.GetEdge(array); |
| auto count = dimensions.size(); |
| for (auto it = dimensions.crbegin(); it != dimensions.crend(); ++it) { |
| --count; |
| const auto size = *it; |
| auto node = Make<Array>(size, type); |
| if (count) |
| type = graph_.Add(std::move(node)); |
| else |
| graph_.Set(id, std::move(node)); |
| if (verbose_) |
| std::cerr << ' ' << size; |
| } |
| if (verbose_) |
| std::cerr << " of " << id << "\n"; |
| } |
| |
| void Corpus::ProcessTypeDecl(Id id, xmlNodePtr type_decl) { |
| const auto name = scope_name_ + GetAttributeOrDie(type_decl, "name"); |
| const auto bits = ReadAttribute<size_t>(type_decl, "size-in-bits", 0); |
| const auto bytes = (bits + 7) / 8; |
| |
| if (name == "void") { |
| graph_.Set(id, Make<Void>()); |
| } else if (name == "bool") { |
| // TODO: improve terrible INT representation |
| graph_.Set( |
| id, Make<Integer>(name, Integer::Encoding::BOOLEAN, bits, bytes)); |
| } else { |
| // TODO: What about plain char's signedness? |
| bool is_signed = name.find("unsigned") == name.npos; |
| bool is_char = name.find("char") != name.npos; |
| Integer::Encoding encoding = |
| is_char ? is_signed ? Integer::Encoding::SIGNED_CHARACTER |
| : Integer::Encoding::UNSIGNED_CHARACTER |
| : is_signed ? Integer::Encoding::SIGNED_INTEGER |
| : Integer::Encoding::UNSIGNED_INTEGER; |
| graph_.Set(id, Make<Integer>(name, encoding, bits, bytes)); |
| } |
| if (verbose_) |
| std::cerr << id << " " << name << "\n"; |
| } |
| |
| void Corpus::ProcessStructUnion(Id id, bool is_struct, |
| xmlNodePtr struct_union) { |
| // TODO(b/236675648) |
| // Libabigail is reporting wrong information for is-declaration-only so it is |
| // not reliable. We are looking at the children of the element instead. |
| // It can be removed once the bug is fixed. |
| const bool forward = |
| ReadAttribute<bool>(struct_union, "is-declaration-only", false) |
| && !xmlFirstElementChild(struct_union); |
| const auto kind = is_struct |
| ? (ReadAttribute<bool>(struct_union, "is-struct", false) |
| ? StructUnion::Kind::STRUCT |
| : StructUnion::Kind::CLASS) |
| : StructUnion::Kind::UNION; |
| const auto name = ReadAttribute<bool>(struct_union, "is-anonymous", false) |
| ? std::string() |
| : GetAttributeOrDie(struct_union, "name"); |
| const auto full_name = name.empty() ? std::string() : scope_name_ + name; |
| PushScopeName push_scope_name( |
| scope_name_, |
| name.empty() ? (std::ostringstream() << "<unnamed " << kind << ">").str() |
| : name); |
| if (forward) { |
| graph_.Set(id, Make<StructUnion>(kind, full_name)); |
| if (verbose_) |
| std::cerr << id << " " << kind << " (forward-declared) " << full_name |
| << "\n"; |
| return; |
| } |
| const auto bits = ReadAttribute<size_t>(struct_union, "size-in-bits", 0); |
| const auto bytes = (bits + 7) / 8; |
| |
| std::vector<Id> base_classes; |
| std::vector<Id> methods; |
| std::vector<Id> members; |
| for (xmlNodePtr child = xmlFirstElementChild(struct_union); child; |
| child = xmlNextElementSibling(child)) { |
| const auto child_name = GetElementName(child); |
| if (child_name == "data-member") { |
| if (const auto member = ProcessDataMember(is_struct, child)) |
| members.push_back(*member); |
| } else if (child_name == "member-type") { |
| ProcessMemberType(child); |
| } else if (child_name == "base-class") { |
| base_classes.push_back(ProcessBaseClass(child)); |
| } else if (child_name == "member-function") { |
| methods.push_back(ProcessMemberFunction(child)); |
| } else { |
| Die() << "unrecognised " << kind << "-decl child element '" << child_name |
| << "'"; |
| } |
| } |
| |
| graph_.Set(id, Make<StructUnion>(kind, full_name, bytes, base_classes, |
| methods, members)); |
| if (verbose_) |
| std::cerr << id << " " << kind << " " << full_name << "\n"; |
| } |
| |
| void Corpus::ProcessEnum(Id id, xmlNodePtr enumeration) { |
| bool forward = ReadAttribute<bool>(enumeration, "is-declaration-only", false); |
| const auto name = ReadAttribute<bool>(enumeration, "is-anonymous", false) |
| ? std::string() |
| : scope_name_ + GetAttributeOrDie(enumeration, "name"); |
| if (forward) { |
| graph_.Set(id, Make<Enumeration>(name)); |
| if (verbose_) |
| std::cerr << id << " enum (forward-declared) " << name << "\n"; |
| return; |
| } |
| |
| xmlNodePtr underlying = xmlFirstElementChild(enumeration); |
| Check(underlying) << "enum-decl has no child elements"; |
| CheckElementName("underlying-type", underlying); |
| const auto type = typing_.GetEdge(underlying); |
| // TODO: decision on underlying type vs size |
| (void)type; |
| |
| std::vector<std::pair<std::string, int64_t>> enumerators; |
| for (xmlNodePtr enumerator = xmlNextElementSibling(underlying); enumerator; |
| enumerator = xmlNextElementSibling(enumerator)) { |
| CheckElementName("enumerator", enumerator); |
| const auto enumerator_name = GetAttributeOrDie(enumerator, "name"); |
| // libabigail currently supports anything that fits in an int64_t |
| const auto enumerator_value = |
| ReadAttributeOrDie<int64_t>(enumerator, "value"); |
| enumerators.emplace_back(enumerator_name, enumerator_value); |
| } |
| |
| graph_.Set(id, Make<Enumeration>(name, 0, enumerators)); |
| if (verbose_) |
| std::cerr << id << " enum " << name << "\n"; |
| } |
| |
| Id Corpus::ProcessBaseClass(xmlNodePtr base_class) { |
| const auto& type = typing_.GetEdge(base_class); |
| const auto offset = |
| ReadAttributeOrDie<size_t>(base_class, "layout-offset-in-bits"); |
| const auto inheritance = ReadAttribute<bool>(base_class, "is-virtual", false) |
| ? BaseClass::Inheritance::VIRTUAL |
| : BaseClass::Inheritance::NON_VIRTUAL; |
| return graph_.Add(Make<BaseClass>(type, offset, inheritance)); |
| } |
| |
| std::optional<Id> Corpus::ProcessDataMember(bool is_struct, |
| xmlNodePtr data_member) { |
| xmlNodePtr decl = GetOnlyChild("data-member", data_member); |
| CheckElementName("var-decl", decl); |
| if (ReadAttribute<bool>(data_member, "static", false)) { |
| ProcessDecl(true, decl); |
| return {}; |
| } |
| |
| size_t offset = is_struct |
| ? ReadAttributeOrDie<size_t>(data_member, "layout-offset-in-bits") |
| : 0; |
| const auto name = GetAttributeOrDie(decl, "name"); |
| const auto type = typing_.GetEdge(decl); |
| |
| // Note: libabigail does not model member size, yet |
| return {graph_.Add(Make<Member>(name, type, offset, 0))}; |
| } |
| |
| Id Corpus::ProcessMemberFunction(xmlNodePtr method) { |
| xmlNodePtr decl = GetOnlyChild("member-function", method); |
| CheckElementName("function-decl", decl); |
| const auto mangled_name = GetAttributeOrDie(decl, "mangled-name"); |
| const auto name = GetAttributeOrDie(decl, "name"); |
| const auto type = ProcessDecl(false, decl); |
| const auto vtable_offset = ReadAttribute<uint64_t>(method, "vtable-offset"); |
| const auto kind = vtable_offset |
| ? Method::Kind::VIRTUAL |
| : ReadAttribute<bool>(method, "static", false) |
| ? Method::Kind::STATIC |
| : Method::Kind::NON_VIRTUAL; |
| return graph_.Add( |
| Make<Method>(mangled_name, name, kind, vtable_offset, type)); |
| } |
| |
| void Corpus::ProcessMemberType(xmlNodePtr member_type) { |
| xmlNodePtr decl = GetOnlyChild("member-type", member_type); |
| const auto type_id = GetAttributeOrDie(decl, "id"); |
| const auto id = typing_.GetNode(type_id); |
| if (graph_.Is(id)) { |
| std::cerr << "duplicate definition of type '" << type_id << "'\n"; |
| return; |
| } |
| const auto name = GetElementName(decl); |
| if (!ProcessUserDefinedType(name, id, decl)) |
| Die() << "unrecognised member-type child element '" << name << "'"; |
| } |
| |
| Id Corpus::BuildSymbol(const SymbolInfo& info, |
| std::optional<Id> type_id, |
| const std::optional<std::string>& name) { |
| const xmlNodePtr symbol = info.node; |
| const bool is_defined = ReadAttributeOrDie<bool>(symbol, "is-defined"); |
| const auto crc = ReadAttribute<CRC>(symbol, "crc"); |
| const auto type = ReadAttributeOrDie<ElfSymbol::SymbolType>(symbol, "type"); |
| const auto binding = |
| ReadAttributeOrDie<ElfSymbol::Binding>(symbol, "binding"); |
| const auto visibility = |
| ReadAttributeOrDie<ElfSymbol::Visibility>(symbol, "visibility"); |
| |
| return graph_.Add(Make<ElfSymbol>( |
| info.name, info.version, info.is_default_version, |
| is_defined, type, binding, visibility, crc, type_id, name)); |
| } |
| |
| std::map<std::string, Id> Corpus::BuildSymbols() { |
| // Libabigail's model is (approximately): |
| // |
| // (alias)* -> main symbol <- some decl -> type |
| // |
| // which we turn into: |
| // |
| // symbol / alias -> type |
| // |
| std::map<std::string, Id> symbols; |
| for (const auto& [alias, main] : alias_to_main_) |
| Check(!alias_to_main_.count(main)) |
| << "found main symbol and alias with id " << main; |
| // Build final symbol table, tying symbols to their types. |
| for (const auto& [id, symbol_info] : symbol_info_map_) { |
| const auto main = alias_to_main_.find(id); |
| const auto lookup = main != alias_to_main_.end() ? main->second : id; |
| const auto type_id_and_name_it = symbol_id_and_full_name_.find(lookup); |
| std::optional<Id> type_id; |
| std::optional<std::string> name; |
| if (type_id_and_name_it != symbol_id_and_full_name_.end()) { |
| const auto& type_id_and_name = type_id_and_name_it->second; |
| type_id = {type_id_and_name.first}; |
| name = {type_id_and_name.second}; |
| } |
| symbols.insert({id, BuildSymbol(symbol_info, type_id, name)}); |
| } |
| return symbols; |
| } |
| |
| Abigail::Abigail(Graph& graph, bool verbose) |
| : graph_(graph), verbose_(verbose) { } |
| |
| Id Abigail::ProcessRoot(xmlNodePtr root) { |
| Typing typing(graph_, verbose_); |
| std::map<SymbolKey, Id> symbols; |
| auto merge = [&](const std::string& path, |
| const std::map<std::string, Id>& corpus_symbols) { |
| for (const auto& [name, id] : corpus_symbols) { |
| const SymbolKey key{path, name}; |
| Check(symbols.insert({key, id}).second) |
| << "found duplicate symbol '" << key << "'"; |
| } |
| }; |
| const auto name = GetElementName(root); |
| if (name == "abi-corpus-group") { |
| for (auto child = xmlFirstElementChild(root); child; |
| child = xmlNextElementSibling(child)) { |
| CheckElementName("abi-corpus", child); |
| const auto path = ReadAttribute(child, "path", std::string()); |
| merge(path, Corpus(graph_, verbose_, typing).ProcessCorpus(child)); |
| } |
| } else if (name == "abi-corpus") { |
| merge(std::string(), Corpus(graph_, verbose_, typing).ProcessCorpus(root)); |
| } else { |
| Die() << "unrecognised root element '" << name << "'"; |
| } |
| return graph_.Add(Make<Symbols>(symbols)); |
| } |
| |
| Id Read(Graph& graph, const std::string& path, bool verbose) { |
| // Open input for reading. |
| const int fd = open(path.c_str(), O_RDONLY); |
| if (fd < 0) |
| Die() << "could not open '" << path << "' for reading: " << strerror(errno); |
| xmlParserCtxtPtr parser_context = xmlNewParserCtxt(); |
| |
| // Read the XML. |
| const auto document = |
| std::unique_ptr<std::remove_pointer<xmlDocPtr>::type, void(*)(xmlDocPtr)>( |
| xmlCtxtReadFd(parser_context, fd, nullptr, nullptr, 0), xmlFreeDoc); |
| |
| // Close input. |
| xmlFreeParserCtxt(parser_context); |
| close(fd); |
| |
| // Get the root element. |
| Check(document != nullptr) << "failed to parse input as XML"; |
| xmlNodePtr root = xmlDocGetRootElement(document.get()); |
| Check(root) << "XML document has no root element"; |
| |
| return Abigail(graph, verbose).ProcessRoot(root); |
| } |
| |
| } // namespace abixml |
| } // namespace stg |