TypeGraph: Add core code

This code mostly works, but is obviously not complete. This commit just
adds the code and tests, but does not enable it in OID or OIL.
This commit is contained in:
Alastair Robertson 2023-05-26 05:52:09 -07:00 committed by Alastair Robertson
parent a0418b6881
commit bd919ae4e4
38 changed files with 5029 additions and 5 deletions

View File

@ -21,18 +21,29 @@ target_link_libraries(symbol_service
dw
)
add_library(codegen
add_library(container_info
ContainerInfo.cpp
)
target_link_libraries(container_info
drgn_utils # This shouldn't be needed! Clean up Commoh.h!
glog::glog
tomlplusplus::tomlplusplus
)
add_library(codegen
Features.cpp
FuncGen.cpp
OICodeGen.cpp
)
target_link_libraries(codegen
container_info
symbol_service
Boost::headers
${Boost_LIBRARIES}
folly_headers
glog::glog
tomlplusplus::tomlplusplus
)
add_subdirectory(type_graph)

View File

@ -0,0 +1,212 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#include "AddChildren.h"
#include <cassert>
#include "DrgnParser.h"
#include "TypeGraph.h"
#include "oi/DrgnUtils.h"
#include "oi/SymbolService.h"
template <typename T>
using ref = std::reference_wrapper<T>;
namespace type_graph {
Pass AddChildren::createPass(DrgnParser& drgnParser, SymbolService& symbols) {
auto fn = [&drgnParser, &symbols](TypeGraph& typeGraph) {
AddChildren pass(typeGraph, drgnParser);
pass.enumerateChildClasses(symbols);
for (auto& type : typeGraph.rootTypes()) {
pass.visit(type);
}
};
return Pass("AddChildren", fn);
}
void AddChildren::visit(Type& type) {
if (visited_.count(&type) != 0)
return;
visited_.insert(&type);
type.accept(*this);
}
void AddChildren::visit(Class& c) {
for (auto& param : c.templateParams) {
visit(*param.type);
}
for (auto& member : c.members) {
visit(*member.type);
}
if (!c.isDynamic()) {
return;
}
auto it = childClasses_.find(c.name());
if (it == childClasses_.end()) {
return;
}
const auto& drgnChildren = it->second;
for (drgn_type* drgnChild : drgnChildren) {
Type* childType = drgnParser_.parse(drgnChild);
auto* childClass =
dynamic_cast<Class*>(childType); // TODO don't use dynamic_cast
if (!childClass) // TODO dodgy error handling
abort();
c.children.push_back(*childClass);
// // Add recursive children to this class as well
// enumerateClassChildren(drgnChild, children);
}
// Recurse to find children-of-children
for (auto& child : c.children) {
visit(child);
}
}
// TODO how to flatten children of children?
// void AddChildren::enumerateClassChildren(struct drgn_type *type,
// std::vector<std::reference_wrapper<Class>> &children) {
// // This function is called recursively to find children-of-children, so the
// // "children" vector argument will not necessarily be empty.
//
// const char* tag = drgn_type_tag(type);
// if (tag == nullptr) {
// return;
// }
// auto it = childClasses_.find(tag);
// if (it == childClasses_.end()) {
// return;
// }
//
// const auto& drgnChildren = it->second;
// for (drgn_type* drgnChild : drgnChildren) {
// // TODO there shouldn't be any need for a dynamic cast here...
// Type *ttt = enumerateClass(drgnChild);
// auto *child = dynamic_cast<Class*>(ttt);
// if (!child)
// abort();
// children.push_back(*child);
//
// // Add recursive children to this class as well
// enumerateClassChildren(drgnChild, children);
// }
//}
void AddChildren::recordChildren(drgn_type* type) {
drgn_type_template_parameter* parents = drgn_type_parents(type);
for (size_t i = 0; i < drgn_type_num_parents(type); i++) {
drgn_qualified_type t{};
if (auto* err = drgn_template_parameter_type(&parents[i], &t);
err != nullptr) {
// TODO useful error:
// LOG(ERROR) << "Error when looking up parent class for type " <<
// type
// << " err " << err->code << " " << err->message;
drgn_error_destroy(err);
continue;
}
drgn_type* parent = drgn_utils::underlyingType(t.type);
if (!drgn_utils::isSizeComplete(parent)) {
// VLOG(1) << "Incomplete size for parent class (" <<
// drgn_type_tag(parent)
// << ") of " << drgn_type_tag(type);
continue;
}
const char* parentName = drgn_type_tag(parent);
if (parentName == nullptr) {
// VLOG(1) << "No name for parent class (" << parent << ") of "
// << drgn_type_tag(type);
continue;
}
/*
* drgn pointers are not stable, so use string representation for reverse
* mapping for now. We need to find a better way of creating this
* childClasses map - ideally drgn would do this for us.
*/
childClasses_[parentName].push_back(type);
// VLOG(1) << drgn_type_tag(type) << "(" << type << ") is a child of "
// << drgn_type_tag(parent) << "(" << parent << ")";
}
}
/*
* Build a mapping of Class -> Children
*
* drgn only gives us the mapping Class -> Parents, so we must iterate over all
* types in the program to build the reverse mapping.
*/
void AddChildren::enumerateChildClasses(SymbolService& symbols) {
if ((setenv("DRGN_ENABLE_TYPE_ITERATOR", "1", 1)) < 0) {
// LOG(ERROR)
// << "Could not set DRGN_ENABLE_TYPE_ITERATOR environment variable";
abort();
}
drgn_type_iterator* typesIterator;
auto* prog = symbols.getDrgnProgram();
drgn_error* err = drgn_type_iterator_create(prog, &typesIterator);
if (err) {
// LOG(ERROR) << "Error initialising drgn_type_iterator: " << err->code
// << ", "
// << err->message;
drgn_error_destroy(err);
abort();
}
int i = 0;
int j = 0;
while (true) {
i++;
drgn_qualified_type* t;
err = drgn_type_iterator_next(typesIterator, &t);
if (err) {
// TODO usful error:
// LOG(ERROR) << "Error from drgn_type_iterator_next: " << err->code
// << ", "
// << err->message;
drgn_error_destroy(err);
continue;
}
if (!t) {
break;
}
j++;
auto kind = drgn_type_kind(t->type);
if (kind != DRGN_TYPE_CLASS && kind != DRGN_TYPE_STRUCT) {
continue;
}
recordChildren(t->type);
}
drgn_type_iterator_destroy(typesIterator);
}
} // namespace type_graph

View File

@ -0,0 +1,68 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#pragma once
#include <functional>
#include <unordered_set>
#include <vector>
#include "PassManager.h"
#include "Types.h"
#include "Visitor.h"
class SymbolService;
namespace type_graph {
class DrgnParser;
class TypeGraph;
/*
* AddChildren
*
* TODO
* what about children which inherit through a typedef? don't think that'll
* work yet
*/
class AddChildren final : public RecursiveVisitor {
public:
static Pass createPass(DrgnParser& drgnParser, SymbolService& symbols);
AddChildren(TypeGraph& typeGraph, DrgnParser& drgnParser)
: typeGraph_(typeGraph), drgnParser_(drgnParser) {
}
void visit(Type& type) override;
void visit(Class& c) override;
private:
void enumerateChildClasses(SymbolService& symbols);
void enumerateClassChildren(
struct drgn_type* type,
std::vector<std::reference_wrapper<Class>>& children);
void recordChildren(drgn_type* type);
std::unordered_set<Type*> visited_;
TypeGraph& typeGraph_;
DrgnParser& drgnParser_;
// Mapping of parent classes to child classes, using names for keys, as drgn
// pointers returned from a type iterator will not match those returned from
// enumerating types in the normal way.
std::unordered_map<std::string, std::vector<drgn_type*>> childClasses_;
};
} // namespace type_graph

View File

@ -0,0 +1,101 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#include "AddPadding.h"
#include <cassert>
#include "TypeGraph.h"
template <typename T>
using ref = std::reference_wrapper<T>;
namespace type_graph {
Pass AddPadding::createPass() {
auto fn = [](TypeGraph& typeGraph) {
AddPadding pass(typeGraph);
for (auto& type : typeGraph.rootTypes()) {
pass.visit(type);
}
};
return Pass("AddPadding", fn);
}
void AddPadding::visit(Type& type) {
if (visited_.count(&type) != 0)
return;
visited_.insert(&type);
type.accept(*this);
}
// TODO normalise pass names, e.g. Flattener -> Flatten, AlignmentCalc ->
// CalcAlignment
void AddPadding::visit(Class& c) {
// AddPadding should be run after Flattener
assert(c.parents.empty());
for (auto& param : c.templateParams) {
visit(*param.type);
}
for (auto& member : c.members) {
visit(*member.type);
}
if (c.kind() == Class::Kind::Union) {
// Don't padd unions
return;
}
std::vector<Member> paddedMembers;
paddedMembers.reserve(c.members.size());
for (size_t i = 0; i < c.members.size(); i++) {
if (i >= 1) {
uint64_t prevMemberEnd =
c.members[i - 1].offset + c.members[i - 1].type->size();
size_t paddingSize = c.members[i].offset - prevMemberEnd;
if (paddingSize > 0) {
auto* primitive =
typeGraph_.make_type<Primitive>(Primitive::Kind::Int8);
auto* paddingArray =
typeGraph_.make_type<Array>(primitive, paddingSize);
paddedMembers.emplace_back(paddingArray, MemberPrefix, prevMemberEnd);
}
}
paddedMembers.push_back(c.members[i]);
}
// TODO reduce duplication with above? (put into function?)
uint64_t prevMemberEnd = 0;
if (!c.members.empty()) {
prevMemberEnd = c.members.back().offset + c.members.back().type->size();
}
size_t paddingSize = c.size() - prevMemberEnd;
if (paddingSize > 0) {
auto* primitive = typeGraph_.make_type<Primitive>(Primitive::Kind::Int8);
auto* paddingArray = typeGraph_.make_type<Array>(primitive, paddingSize);
paddedMembers.emplace_back(paddingArray, MemberPrefix, prevMemberEnd);
}
c.members = std::move(paddedMembers);
for (const auto& child : c.children) {
visit(child);
}
}
} // namespace type_graph

View File

@ -0,0 +1,52 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#pragma once
#include <functional>
#include <unordered_set>
#include "PassManager.h"
#include "TypeGraph.h"
#include "Types.h"
#include "Visitor.h"
namespace type_graph {
/*
* AddPadding
*
* Adds members to classes to represent padding. This is necessary until we have
* complete alignment information from DWARF, otherwise our classes may be
* undersized.
*/
class AddPadding final : public RecursiveVisitor {
public:
static Pass createPass();
explicit AddPadding(TypeGraph& typeGraph) : typeGraph_(typeGraph) {
}
void visit(Type& type) override;
void visit(Class& c) override;
static const inline std::string MemberPrefix = "__oid_padding";
private:
std::unordered_set<Type*> visited_;
TypeGraph& typeGraph_;
};
} // namespace type_graph

View File

@ -0,0 +1,74 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#include "AlignmentCalc.h"
#include <cassert>
#include "TypeGraph.h"
template <typename T>
using ref = std::reference_wrapper<T>;
namespace type_graph {
Pass AlignmentCalc::createPass() {
auto fn = [](TypeGraph& typeGraph) {
AlignmentCalc alignmentCalc;
alignmentCalc.calculateAlignments(typeGraph.rootTypes());
};
return Pass("AlignmentCalc", fn);
}
void AlignmentCalc::calculateAlignments(const std::vector<ref<Type>>& types) {
for (auto& type : types) {
visit(type);
}
};
void AlignmentCalc::visit(Type& type) {
if (visited_.count(&type) != 0)
return;
visited_.insert(&type);
type.accept(*this);
}
// TODO we will need to calculate alignment for c.templateParams too??
// TODO same for children. test this
void AlignmentCalc::visit(Class& c) {
// AlignmentCalc should be run after Flattener
assert(c.parents.empty());
uint64_t alignment = 1;
for (auto& member : c.members) {
if (member.align == 0) {
// If the member does not have an explicit alignment, calculate it from
// the member's type.
visit(*member.type);
member.align = member.type->align();
}
alignment = std::max(alignment, member.align);
}
c.setAlign(alignment);
if (c.size() % c.align() != 0) {
c.setPacked();
}
}
} // namespace type_graph

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#pragma once
#include <functional>
#include <unordered_set>
#include <vector>
#include "PassManager.h"
#include "Types.h"
#include "Visitor.h"
namespace type_graph {
/*
* AlignmentCalc
*
* Calculates alignment information for types TODO finish comment
*/
class AlignmentCalc final : public RecursiveVisitor {
public:
static Pass createPass();
void calculateAlignments(
const std::vector<std::reference_wrapper<Type>>& types);
void visit(Type& type) override;
void visit(Class& c) override;
private:
std::unordered_set<Type*> visited_;
};
} // namespace type_graph

View File

@ -0,0 +1,21 @@
add_library(type_graph
AddChildren.cpp
AddPadding.cpp
AlignmentCalc.cpp
DrgnParser.cpp
Flattener.cpp
NameGen.cpp
PassManager.cpp
Printer.cpp
RemoveTopLevelPointer.cpp
TopoSorter.cpp
TypeIdentifier.cpp
Types.cpp
)
add_dependencies(type_graph libdrgn)
target_link_libraries(type_graph
symbol_service
"-L${DRGN_PATH}/.libs"
drgn
)

View File

@ -0,0 +1,442 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#include "DrgnParser.h"
#include <glog/logging.h>
#include "oi/ContainerInfo.h"
#include "oi/DrgnUtils.h"
#include "oi/SymbolService.h"
extern "C" {
#include <drgn.h>
}
#include <regex>
namespace type_graph {
namespace {
uint64_t get_drgn_type_size(struct drgn_type* type) {
uint64_t size;
struct drgn_error* err = drgn_type_sizeof(type, &size);
if (err)
throw DrgnParserError{"Failed to get type size", err};
return size;
}
Primitive::Kind primitiveIntKind(struct drgn_type* type) {
auto size = get_drgn_type_size(type);
bool is_signed = type->_private.is_signed;
switch (size) {
case 1:
return is_signed ? Primitive::Kind::Int8 : Primitive::Kind::UInt8;
case 2:
return is_signed ? Primitive::Kind::Int16 : Primitive::Kind::UInt16;
case 4:
return is_signed ? Primitive::Kind::Int32 : Primitive::Kind::UInt32;
case 8:
return is_signed ? Primitive::Kind::Int64 : Primitive::Kind::UInt64;
default:
throw DrgnParserError{"Invalid integer size: " + std::to_string(size)};
}
}
Primitive::Kind primitiveFloatKind(struct drgn_type* type) {
auto size = get_drgn_type_size(type);
switch (size) {
case 4:
return Primitive::Kind::Float32;
case 8:
return Primitive::Kind::Float64;
case 16:
return Primitive::Kind::Float128;
default:
throw DrgnParserError{"Invalid float size: " + std::to_string(size)};
}
}
} // namespace
// TODO type stubs
Type* DrgnParser::parse(struct drgn_type* root) {
depth_ = 0;
return enumerateType(root);
}
Type* DrgnParser::enumerateType(struct drgn_type* type) {
// Avoid re-enumerating an already-processsed type
if (auto it = drgn_types_.find(type); it != drgn_types_.end())
return it->second;
if (!drgn_utils::isSizeComplete(type)) {
return make_type<Primitive>(nullptr, Primitive::Kind::Void);
}
enum drgn_type_kind kind = drgn_type_kind(type);
Type* t = nullptr;
depth_++;
switch (kind) {
case DRGN_TYPE_CLASS:
case DRGN_TYPE_STRUCT:
case DRGN_TYPE_UNION:
t = enumerateClass(type);
break;
case DRGN_TYPE_ENUM:
t = enumerateEnum(type);
break;
case DRGN_TYPE_TYPEDEF:
t = enumerateTypedef(type);
break;
case DRGN_TYPE_POINTER:
t = enumeratePointer(type);
break;
case DRGN_TYPE_ARRAY:
t = enumerateArray(type);
break;
case DRGN_TYPE_INT:
case DRGN_TYPE_BOOL:
case DRGN_TYPE_FLOAT:
case DRGN_TYPE_VOID:
t = enumeratePrimitive(type);
break;
default:
throw DrgnParserError{"Unknown drgn type kind: " + std::to_string(kind)};
}
depth_--;
return t;
}
Container* DrgnParser::enumerateContainer(struct drgn_type* type) {
char* nameStr = nullptr;
size_t length = 0;
auto* err = drgn_type_fully_qualified_name(type, &nameStr, &length);
if (err != nullptr || nameStr == nullptr) {
return nullptr;
}
std::string name{nameStr};
auto size = get_drgn_type_size(type);
for (const auto& containerInfo : containers_) {
if (!std::regex_search(nameStr, containerInfo.matcher)) {
continue;
}
VLOG(2) << "Matching container `" << containerInfo.typeName << "` from `"
<< nameStr << "`" << std::endl;
auto* c = make_type<Container>(type, containerInfo, size);
enumerateClassTemplateParams(type, c->templateParams);
return c;
}
return nullptr;
}
Type* DrgnParser::enumerateClass(struct drgn_type* type) {
auto* container = enumerateContainer(type);
if (container)
return container;
std::string name;
const char* type_tag = drgn_type_tag(type);
if (type_tag)
name = std::string(type_tag);
// else this is an anonymous type
auto size = get_drgn_type_size(type);
int virtuality = 0;
if (drgn_type_has_virtuality(type)) {
virtuality = drgn_type_virtuality(type);
}
Class::Kind kind;
switch (drgn_type_kind(type)) {
case DRGN_TYPE_CLASS:
kind = Class::Kind::Class;
break;
case DRGN_TYPE_STRUCT:
kind = Class::Kind::Struct;
break;
case DRGN_TYPE_UNION:
kind = Class::Kind::Union;
break;
default:
throw DrgnParserError{"Invalid drgn type kind for class: " +
std::to_string(drgn_type_kind(type))};
}
auto c = make_type<Class>(type, kind, name, size, virtuality);
enumerateClassTemplateParams(type, c->templateParams);
enumerateClassParents(type, c->parents);
enumerateClassMembers(type, c->members);
enumerateClassFunctions(type, c->functions);
return c;
}
void DrgnParser::enumerateClassParents(struct drgn_type* type,
std::vector<Parent>& parents) {
assert(parents.empty());
size_t num_parents = drgn_type_num_parents(type);
parents.reserve(num_parents);
struct drgn_type_template_parameter* drgn_parents = drgn_type_parents(type);
for (size_t i = 0; i < num_parents; i++) {
struct drgn_qualified_type parent_qual_type;
struct drgn_error* err =
drgn_template_parameter_type(&drgn_parents[i], &parent_qual_type);
if (err) {
throw DrgnParserError{
"Error looking up parent type (" + std::to_string(i) + ")", err};
}
auto ptype = enumerateType(parent_qual_type.type);
uint64_t poffset = drgn_parents[i].bit_offset / 8;
Parent p(ptype, poffset);
parents.push_back(p);
}
std::sort(parents.begin(), parents.end(),
[](const auto& a, const auto& b) { return a.offset < b.offset; });
}
void DrgnParser::enumerateClassMembers(struct drgn_type* type,
std::vector<Member>& members) {
assert(members.empty());
size_t num_members = drgn_type_num_members(type);
members.reserve(num_members);
struct drgn_type_member* drgn_members = drgn_type_members(type);
for (size_t i = 0; i < num_members; i++) {
struct drgn_qualified_type member_qual_type;
uint64_t bit_field_size;
struct drgn_error* err =
drgn_member_type(&drgn_members[i], &member_qual_type, &bit_field_size);
if (err) {
throw DrgnParserError{
"Error looking up member type (" + std::to_string(i) + ")", err};
}
struct drgn_type* member_type = member_qual_type.type;
// if (err || !isDrgnSizeComplete(member_qual_type.type)) {
// if (err) {
// LOG(ERROR) << "Error when looking up member type " << err->code <<
// " "
// << err->message << " " << typeName << " " <<
// drgn_members[i].name;
// }
// VLOG(1) << "Type " << typeName
// << " has an incomplete member; stubbing...";
// knownDummyTypeList.insert(type);
// isStubbed = true;
// return;
// }
std::string member_name = "";
if (drgn_members[i].name)
member_name = drgn_members[i].name;
// TODO bitfields
auto mtype = enumerateType(member_type);
uint64_t moffset = drgn_members[i].bit_offset / 8;
Member m(mtype, member_name, moffset); // TODO
members.push_back(m);
}
std::sort(members.begin(), members.end(),
[](const auto& a, const auto& b) { return a.offset < b.offset; });
}
void DrgnParser::enumerateTemplateParam(drgn_type_template_parameter* tparams,
size_t i,
std::vector<TemplateParam>& params) {
const drgn_object* obj = nullptr;
if (auto* err = drgn_template_parameter_object(&tparams[i], &obj)) {
throw DrgnParserError{"Error looking up template parameter object (" +
std::to_string(i) + ")",
err};
}
struct drgn_qualified_type tparamQualType;
if (obj == nullptr) {
// This template parameter is a typename
struct drgn_error* err =
drgn_template_parameter_type(&tparams[i], &tparamQualType);
if (err) {
throw DrgnParserError{"Error looking up template parameter type (" +
std::to_string(i) + ")",
err};
}
struct drgn_type* tparamType = tparamQualType.type;
auto ttype = enumerateType(tparamType);
params.emplace_back(ttype);
} else {
// This template parameter is a value
// TODO why do we need the type of a value?
// tparamQualType.type = obj->type;
// tparamQualType.qualifiers = obj->qualifiers;
std::string value;
if (obj->encoding == DRGN_OBJECT_ENCODING_BUFFER) {
uint64_t size = drgn_object_size(obj);
char* buf = nullptr;
if (size <= sizeof(obj->value.ibuf)) {
buf = (char*)&(obj->value.ibuf);
} else {
buf = obj->value.bufp;
}
if (buf != nullptr) {
value = std::string(buf);
}
} else if (obj->encoding == DRGN_OBJECT_ENCODING_SIGNED) {
value = std::to_string(obj->value.svalue);
} else if (obj->encoding == DRGN_OBJECT_ENCODING_UNSIGNED) {
value = std::to_string(obj->value.uvalue);
} else if (obj->encoding == DRGN_OBJECT_ENCODING_FLOAT) {
value = std::to_string(obj->value.fvalue);
} else {
throw DrgnParserError{
"Unknown template parameter object encoding format: " +
std::to_string(obj->encoding)};
}
params.emplace_back(make_type<Primitive>(nullptr, Primitive::Kind::UInt8),
value);
}
}
void DrgnParser::enumerateClassTemplateParams(
struct drgn_type* type, std::vector<TemplateParam>& params) {
assert(params.empty());
size_t numParams = drgn_type_num_template_parameters(type);
params.reserve(numParams);
struct drgn_type_template_parameter* tparams =
drgn_type_template_parameters(type);
for (size_t i = 0; i < numParams; i++) {
enumerateTemplateParam(tparams, i, params);
}
}
void DrgnParser::enumerateClassFunctions(struct drgn_type* type,
std::vector<Function>& functions) {
assert(functions.empty());
size_t num_functions = drgn_type_num_functions(type);
functions.reserve(num_functions);
drgn_type_member_function* drgn_functions = drgn_type_functions(type);
for (size_t i = 0; i < num_functions; i++) {
drgn_qualified_type t{};
if (auto* err = drgn_member_function_type(&drgn_functions[i], &t)) {
LOG(WARNING) << "Error looking up member function (" + std::to_string(i) +
"): " + std::to_string(err->code) + " " +
err->message;
drgn_error_destroy(err);
continue;
}
auto virtuality = drgn_type_virtuality(t.type);
std::string name = drgn_type_tag(t.type);
Function f(name, virtuality);
functions.push_back(f);
}
}
Enum* DrgnParser::enumerateEnum(struct drgn_type* type) {
// TODO anonymous enums
// TODO incomplete enum?
std::string name = drgn_type_tag(type);
uint64_t size = get_drgn_type_size(type);
;
return make_type<Enum>(type, name, size);
}
Typedef* DrgnParser::enumerateTypedef(struct drgn_type* type) {
std::string name = drgn_type_name(type);
// TODO anonymous typedefs?
struct drgn_type* underlyingType = drgn_type_type(type).type;
auto t = enumerateType(underlyingType);
return make_type<Typedef>(type, name, t);
}
Type* DrgnParser::enumeratePointer(struct drgn_type* type) {
if (!chasePointer()) {
// TODO dodgy nullptr - primitives should be handled as singletons
return make_type<Primitive>(nullptr, Primitive::Kind::UIntPtr);
}
struct drgn_type* pointeeType = drgn_type_type(type).type;
// TODO why was old CodeGen following funciton pointers?
Type* t = enumerateType(pointeeType);
return make_type<Pointer>(type, t);
}
Array* DrgnParser::enumerateArray(struct drgn_type* type) {
struct drgn_type* elementType = drgn_type_type(type).type;
uint64_t len = drgn_type_length(type);
auto t = enumerateType(elementType);
return make_type<Array>(type, t, len);
}
// TODO deduplication of primitive types (also remember they're not only created
// here)
Primitive* DrgnParser::enumeratePrimitive(struct drgn_type* type) {
Primitive::Kind kind;
switch (drgn_type_kind(type)) {
case DRGN_TYPE_INT:
kind = primitiveIntKind(type);
break;
case DRGN_TYPE_FLOAT:
kind = primitiveFloatKind(type);
break;
case DRGN_TYPE_BOOL:
kind = Primitive::Kind::Bool;
break;
case DRGN_TYPE_VOID:
kind = Primitive::Kind::Void;
break;
default:
throw DrgnParserError{"Invalid drgn type kind for primitive: " +
std::to_string(drgn_type_kind(type))};
}
return make_type<Primitive>(type, kind);
}
bool DrgnParser::chasePointer() const {
// Always chase top-level pointers
if (depth_ == 1)
return true;
return chaseRawPointers_;
}
DrgnParserError::~DrgnParserError() {
drgn_error_destroy(err_);
}
} // namespace type_graph

View File

@ -0,0 +1,99 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#pragma once
#include <unordered_map>
#include <vector>
#include "TypeGraph.h"
#include "Types.h"
struct drgn_type;
struct drgn_type_template_parameter;
struct ContainerInfo;
namespace type_graph {
class DrgnParser {
public:
DrgnParser(TypeGraph& typeGraph,
const std::vector<ContainerInfo>& containers,
bool chaseRawPointers)
: typeGraph_(typeGraph),
containers_(containers),
chaseRawPointers_(chaseRawPointers) {
}
Type* parse(struct drgn_type* root);
private:
Type* enumerateType(struct drgn_type* type);
Container* enumerateContainer(struct drgn_type* type);
Type* enumerateClass(struct drgn_type* type);
Enum* enumerateEnum(struct drgn_type* type);
Typedef* enumerateTypedef(struct drgn_type* type);
Type* enumeratePointer(struct drgn_type* type);
Array* enumerateArray(struct drgn_type* type);
Primitive* enumeratePrimitive(struct drgn_type* type);
void enumerateTemplateParam(drgn_type_template_parameter* tparams,
size_t i,
std::vector<TemplateParam>& params);
void enumerateClassTemplateParams(struct drgn_type* type,
std::vector<TemplateParam>& params);
void enumerateClassParents(struct drgn_type* type,
std::vector<Parent>& parents);
void enumerateClassMembers(struct drgn_type* type,
std::vector<Member>& members);
void enumerateClassFunctions(struct drgn_type* type,
std::vector<Function>& functions);
// Store a mapping of drgn types to type graph nodes for deduplication during
// parsing. This stops us getting caught in cycles.
std::unordered_map<struct drgn_type*, Type*> drgn_types_;
template <typename T, typename... Args>
T* make_type(struct drgn_type* type, Args&&... args) {
auto type_unique_ptr = std::make_unique<T>(std::forward<Args>(args)...);
auto type_raw_ptr = type_unique_ptr.get();
typeGraph_.add(std::move(type_unique_ptr));
drgn_types_.insert({type, type_raw_ptr});
return type_raw_ptr;
}
bool chasePointer() const;
TypeGraph& typeGraph_;
const std::vector<ContainerInfo>& containers_;
int depth_;
bool chaseRawPointers_;
};
class DrgnParserError : public std::runtime_error {
public:
DrgnParserError(const std::string& msg) : std::runtime_error{msg} {
}
DrgnParserError(const std::string& msg, struct drgn_error* err)
: std::runtime_error{msg + ": " + std::to_string(err->code) + " " +
err->message},
err_(err) {
}
~DrgnParserError();
private:
struct drgn_error* err_ = nullptr;
};
} // namespace type_graph

162
oi/type_graph/Flattener.cpp Normal file
View File

@ -0,0 +1,162 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#include "Flattener.h"
#include "TypeGraph.h"
namespace type_graph {
Pass Flattener::createPass() {
auto fn = [](TypeGraph& typeGraph) {
Flattener flattener;
flattener.flatten(typeGraph.rootTypes());
// TODO should flatten just operate on a single type and we do the looping
// here?
};
return Pass("Flattener", fn);
}
void Flattener::flatten(std::vector<std::reference_wrapper<Type>>& types) {
for (auto& type : types) {
visit(type);
}
}
void Flattener::visit(Type& type) {
if (visited_.count(&type) != 0)
return;
visited_.insert(&type);
type.accept(*this);
}
// TODO this function is a massive hack. don't do it like this please
Class& stripTypedefs(Type& type) {
Type* t = &type;
while (const Typedef* td = dynamic_cast<Typedef*>(t)) {
t = td->underlyingType();
}
return dynamic_cast<Class&>(*t);
}
void Flattener::visit(Class& c) {
// Members of a base class will be contiguous, but it's possible for derived
// class members to be intersperced between embedded parent classes.
//
// This will happen when vptrs are present.
//
// e.g. Givin the original C++ classes:
// class Parent {
// int x;
// int y;
// };
// class Child : Parent {
// int a;
// int b;
// };
//
// The in memory (flattened) representation could be:
// class Child {
// int a;
// int x;
// int y;
// int b;
// };
// TODO comment about virtual inheritance
// TODO alignment of parent classes
//
// TODO flatten template parameters??? ### TEST THIS ###
// Flatten types referenced by members and parents
for (const auto& member : c.members) {
visit(*member.type);
}
for (const auto& parent : c.parents) {
visit(*parent.type);
}
// Pull in functions from flattened parents
for (const auto& parent : c.parents) {
const Class& parentClass = stripTypedefs(*parent.type);
c.functions.insert(c.functions.end(), parentClass.functions.begin(),
parentClass.functions.end());
}
// Pull member variables from flattened parents into this class
std::vector<Member> flattenedMembers;
std::size_t member_idx = 0;
std::size_t parent_idx = 0;
while (member_idx < c.members.size() && parent_idx < c.parents.size()) {
auto member_offset = c.members[member_idx].offset;
auto parent_offset = c.parents[parent_idx].offset;
if (member_offset < parent_offset) {
// Add our own member
const auto& member = c.members[member_idx++];
flattenedMembers.push_back(member);
} else {
// Add parent's members
// If member_offset == parent_offset then the parent is empty. Also take
// this path.
const auto& parent = c.parents[parent_idx++];
const Class& parentClass = stripTypedefs(*parent.type);
for (size_t i = 0; i < parentClass.members.size(); i++) {
const auto& member = parentClass.members[i];
flattenedMembers.push_back(member);
flattenedMembers.back().offset += parent.offset;
if (i == 0) {
flattenedMembers.back().align =
std::max(flattenedMembers.back().align, parentClass.align());
}
}
}
}
while (member_idx < c.members.size()) {
const auto& member = c.members[member_idx++];
flattenedMembers.push_back(member);
}
while (parent_idx < c.parents.size()) {
const auto& parent = c.parents[parent_idx++];
const Class& parentClass = stripTypedefs(*parent.type);
for (const auto& member : parentClass.members) {
flattenedMembers.push_back(member);
flattenedMembers.back().offset += parent.offset;
}
}
c.parents.clear();
c.members = std::move(flattenedMembers);
// Flatten types referenced by children.
// This must be run after flattening the current class in order to respect
// the changes we have made here.
for (const auto& child : c.children) {
visit(child);
}
}
void Flattener::visit(Container& c) {
// Containers themselves don't need to be flattened, but their template
// parameters might need to be
for (const auto& templateParam : c.templateParams) {
visit(*templateParam.type);
}
}
} // namespace type_graph

49
oi/type_graph/Flattener.h Normal file
View File

@ -0,0 +1,49 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#pragma once
#include <unordered_set>
#include <vector>
#include "PassManager.h"
#include "Types.h"
#include "Visitor.h"
namespace type_graph {
/*
* Flattener
*
* Flattens classes by removing parents and adding their members directly into
* derived classes.
*/
class Flattener : public RecursiveVisitor {
public:
static Pass createPass();
void flatten(std::vector<std::reference_wrapper<Type>>& types);
void visit(Type& type) override;
void visit(Class& c) override;
void visit(Container& c) override;
private:
std::unordered_set<Type*> visited_;
std::vector<Member> flattened_members_;
std::vector<uint64_t> offset_stack_;
};
} // namespace type_graph

109
oi/type_graph/NameGen.cpp Normal file
View File

@ -0,0 +1,109 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#include "NameGen.h"
#include "TypeGraph.h"
template <typename T>
using ref = std::reference_wrapper<T>;
namespace type_graph {
Pass NameGen::createPass() {
auto fn = [](TypeGraph& typeGraph) {
NameGen nameGen;
nameGen.generateNames(typeGraph.rootTypes());
};
return Pass("NameGen", fn);
}
void NameGen::generateNames(const std::vector<ref<Type>>& types) {
for (auto& type : types) {
visit(type);
}
};
void NameGen::visit(Type& type) {
if (visited_.count(&type) != 0)
return;
visited_.insert(&type);
type.accept(*this);
}
/*
* Remove template parameters from the type name
*
* "std::vector<int>" -> "std::vector"
*/
void NameGen::removeTemplateParams(std::string& name) {
auto template_start_pos = name.find('<');
if (template_start_pos != std::string::npos)
name.erase(template_start_pos);
}
void NameGen::visit(Class& c) {
std::string name = c.name();
removeTemplateParams(name);
// Append an incrementing number to ensure we don't get duplicates
c.setName(name + "_" + std::to_string(n++));
// Deduplicate member names. Duplicates may be present after flattening.
for (size_t i = 0; i < c.members.size(); i++) {
c.members[i].name += "_" + std::to_string(i);
}
for (const auto& param : c.templateParams) {
visit(*param.type);
}
for (const auto& parent : c.parents) {
visit(*parent.type);
}
for (const auto& member : c.members) {
visit(*member.type);
}
for (const auto& child : c.children) {
visit(child);
}
}
void NameGen::visit(Container& c) {
for (const auto& template_param : c.templateParams) {
visit(*template_param.type);
}
std::string name = c.name();
removeTemplateParams(name);
name.push_back('<');
for (const auto& param : c.templateParams) {
if (param.value) {
name += *param.value;
} else {
name += param.type->name();
}
name += ", ";
}
name.pop_back();
name.pop_back();
name.push_back('>');
c.setName(name);
}
} // namespace type_graph

46
oi/type_graph/NameGen.h Normal file
View File

@ -0,0 +1,46 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#pragma once
#include <functional>
#include <unordered_set>
#include <vector>
#include "PassManager.h"
#include "Types.h"
#include "Visitor.h"
namespace type_graph {
// TODO make all final
class NameGen final : public RecursiveVisitor {
public:
static Pass createPass();
void generateNames(const std::vector<std::reference_wrapper<Type>>& types);
void visit(Class& c) override;
void visit(Container& c) override;
private:
void visit(Type& type) override;
void removeTemplateParams(std::string& name);
std::unordered_set<Type*> visited_;
int n = 0;
};
} // namespace type_graph

View File

@ -0,0 +1,74 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#include "PassManager.h"
#include <glog/logging.h>
#include <sstream>
#include "Printer.h"
#include "TypeGraph.h"
template <typename T>
using ref = std::reference_wrapper<T>;
namespace type_graph {
void Pass::run(TypeGraph& typeGraph) {
fn_(typeGraph);
}
void PassManager::addPass(Pass p) {
passes_.push_back(std::move(p));
}
namespace {
void print(const TypeGraph& typeGraph) {
if (!VLOG_IS_ON(1))
return;
// TODO: Long strings will be truncated by glog. Find another way to do this
std::stringstream out;
Printer printer{out};
for (const auto& type : typeGraph.rootTypes()) {
printer.print(type);
}
LOG(INFO) << "\n" << out.str();
}
} // namespace
const std::string separator = "----------------";
void PassManager::run(TypeGraph& typeGraph) {
VLOG(1) << separator;
VLOG(1) << "Parsed Type Graph:";
VLOG(1) << separator;
print(typeGraph);
VLOG(1) << separator;
for (size_t i = 0; i < passes_.size(); i++) {
auto& pass = passes_[i];
LOG(INFO) << "Running pass (" << i + 1 << "/" << passes_.size()
<< "): " << pass.name();
pass.run(typeGraph);
VLOG(1) << separator;
print(typeGraph);
VLOG(1) << separator;
}
}
} // namespace type_graph

View File

@ -0,0 +1,62 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#pragma once
#include <functional>
#include <string>
#include <vector>
namespace type_graph {
class TypeGraph;
class Type;
/*
* Pass
*
* TODO
*/
class Pass {
using PassFn = std::function<void(TypeGraph& typeGraph)>;
public:
Pass(std::string name, PassFn fn) : name_(std::move(name)), fn_(fn) {
}
void run(TypeGraph& typeGraph);
std::string& name() {
return name_;
};
private:
std::string name_;
PassFn fn_;
};
/*
* PassManager
*
* TODO
*/
class PassManager {
public:
void addPass(Pass p);
void run(TypeGraph& typeGraph);
private:
std::vector<Pass> passes_;
};
} // namespace type_graph

203
oi/type_graph/Printer.cpp Normal file
View File

@ -0,0 +1,203 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#include "Printer.h"
namespace type_graph {
void Printer::print(Type& type) {
depth_++;
type.accept(*this);
depth_--;
}
void Printer::visit(const Class& c) {
if (prefix(&c))
return;
std::string kind;
switch (c.kind()) {
case Class::Kind::Class:
kind = "Class";
break;
case Class::Kind::Struct:
kind = "Struct";
break;
case Class::Kind::Union:
kind = "Union";
break;
}
out_ << kind << ": " << c.name() << " (size: " << c.size()
<< align_str(c.align());
if (c.packed()) {
out_ << ", packed";
}
out_ << ")" << std::endl;
for (const auto& param : c.templateParams) {
print_param(param);
}
for (const auto& parent : c.parents) {
print_parent(parent);
}
for (const auto& member : c.members) {
print_member(member);
}
for (const auto& function : c.functions) {
print_function(function);
}
for (auto& child : c.children) {
print_child(child);
}
}
void Printer::visit(const Container& c) {
if (prefix(&c))
return;
out_ << "Container: " << c.name() << " (size: " << c.size() << ")"
<< std::endl;
for (const auto& param : c.templateParams) {
print_param(param);
}
}
void Printer::visit(const Primitive& p) {
prefix();
out_ << "Primitive: " << p.name() << std::endl;
}
void Printer::visit(const Enum& e) {
prefix();
out_ << "Enum: " << e.name() << " (size: " << e.size() << ")" << std::endl;
}
void Printer::visit(const Array& a) {
if (prefix(&a))
return;
out_ << "Array: (length: " << a.len() << ")" << std::endl;
print(*a.elementType());
}
void Printer::visit(const Typedef& td) {
if (prefix(&td))
return;
out_ << "Typedef: " << td.name() << std::endl;
print(*td.underlyingType());
}
void Printer::visit(const Pointer& p) {
if (prefix(&p))
return;
out_ << "Pointer" << std::endl;
print(*p.pointeeType());
}
void Printer::visit(const Dummy& d) {
prefix();
out_ << "Dummy (size: " << d.size() << align_str(d.align()) << ")"
<< std::endl;
}
void Printer::visit(const DummyAllocator& d) {
prefix();
out_ << "DummyAllocatorTODO (size: " << d.size() << align_str(d.align())
<< ")" << std::endl;
}
bool Printer::prefix(const Type* type) {
if (type) {
if (auto it = nodeNums_.find(type); it != nodeNums_.end()) {
// Node has already been printed - print a reference to it this time
out_ << std::string(depth_ * 2, ' ');
int node_num = it->second;
out_ << " [" << node_num << "]" << std::endl;
return true;
}
int node_num = nextNodeNum_++;
out_ << "[" << node_num << "] "; // TODO pad numbers
nodeNums_.insert({type, node_num});
} else {
// Extra padding
out_ << " "; // TODO make variable size
}
out_ << std::string(depth_ * 2, ' ');
return false;
}
void Printer::print_param(const TemplateParam& param) {
depth_++;
prefix();
out_ << "Param" << std::endl;
if (param.value) {
print_value(*param.value);
} else {
print(*param.type);
}
depth_--;
}
void Printer::print_parent(const Parent& parent) {
depth_++;
prefix();
out_ << "Parent (offset: " << parent.offset << ")" << std::endl;
print(*parent.type);
depth_--;
}
void Printer::print_member(const Member& member) {
depth_++;
prefix();
out_ << "Member: " << member.name << " (offset: " << member.offset
<< align_str(member.align) << ")" << std::endl;
print(*member.type);
depth_--;
}
void Printer::print_function(const Function& function) {
depth_++;
prefix();
out_ << "Function: " << function.name;
if (function.virtuality != 0)
out_ << " (virtual)";
out_ << std::endl;
depth_--;
}
void Printer::print_child(Type& child) {
depth_++;
prefix();
out_ << "Child:" << std::endl;
print(child);
depth_--;
}
void Printer::print_value(const std::string& value) {
depth_++;
prefix();
out_ << "Value: " << value << std::endl;
depth_--;
}
std::string Printer::align_str(uint64_t align) {
if (align == 0)
return "";
return ", align: " + std::to_string(align);
}
} // namespace type_graph

61
oi/type_graph/Printer.h Normal file
View File

@ -0,0 +1,61 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#pragma once
#include <ostream>
#include <unordered_map>
#include "Types.h"
#include "Visitor.h"
namespace type_graph {
/*
* Printer
*/
class Printer : public ConstVisitor {
public:
Printer(std::ostream& out) : out_(out) {
}
void print(Type& type);
void visit(const Class& c) override;
void visit(const Container& c) override;
void visit(const Primitive& p) override;
void visit(const Enum& e) override;
void visit(const Array& a) override;
void visit(const Typedef& td) override;
void visit(const Pointer& p) override;
void visit(const Dummy& d) override;
void visit(const DummyAllocator& d) override;
private:
bool prefix(const Type* type = nullptr);
void print_param(const TemplateParam& param);
void print_parent(const Parent& parent);
void print_member(const Member& member);
void print_function(const Function& function);
void print_child(Type& child);
void print_value(const std::string& value);
static std::string align_str(uint64_t align);
std::ostream& out_;
int depth_ = -1;
int nextNodeNum_ = 0;
std::unordered_map<const Type*, int> nodeNums_;
};
} // namespace type_graph

View File

@ -0,0 +1,45 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#include "RemoveTopLevelPointer.h"
#include "TypeGraph.h"
namespace type_graph {
Pass RemoveTopLevelPointer::createPass() {
auto fn = [](TypeGraph& typeGraph) {
RemoveTopLevelPointer pass;
pass.removeTopLevelPointers(typeGraph.rootTypes());
};
return Pass("RemoveTopLevelPointer", fn);
}
void RemoveTopLevelPointer::removeTopLevelPointers(
std::vector<std::reference_wrapper<Type>>& types) {
for (size_t i = 0; i < types.size(); i++) {
Type& type = types[i];
topLevelType_ = &type;
type.accept(*this);
types[i] = *topLevelType_;
}
}
void RemoveTopLevelPointer::visit(Pointer& p) {
topLevelType_ = p.pointeeType();
}
} // namespace type_graph

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#pragma once
#include <functional>
#include <vector>
#include "PassManager.h"
#include "Types.h"
#include "Visitor.h"
namespace type_graph {
/*
* RemoveTopLevelPointer
*
* If the top type node is a pointer, remove it from the graph and instead have
* the pointee type as the top-level node.
*/
class RemoveTopLevelPointer : public LazyVisitor {
public:
static Pass createPass();
void removeTopLevelPointers(std::vector<std::reference_wrapper<Type>>& types);
void visit(Pointer& p) override;
private:
Type* topLevelType_ = nullptr;
};
} // namespace type_graph

View File

@ -0,0 +1,98 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#include "TopoSorter.h"
#include "TypeGraph.h"
template <typename T>
using ref = std::reference_wrapper<T>;
namespace type_graph {
Pass TopoSorter::createPass() {
auto fn = [](TypeGraph& typeGraph) {
TopoSorter sorter;
sorter.sort(typeGraph.rootTypes());
typeGraph.finalTypes = std::move(sorter.sortedTypes());
};
return Pass("TopoSorter", fn);
}
void TopoSorter::sort(const std::vector<ref<Type>>& types) {
for (const auto& type : types) {
typesToSort_.push(type);
}
while (!typesToSort_.empty()) {
visit(typesToSort_.front());
typesToSort_.pop();
}
}
const std::vector<ref<Type>>& TopoSorter::sortedTypes() const {
return sortedTypes_;
}
void TopoSorter::visit(Type& type) {
if (visited_.count(&type) != 0)
return;
visited_.insert(&type);
type.accept(*this);
}
void TopoSorter::visit(Class& c) {
for (const auto& param : c.templateParams) {
visit(*param.type);
}
for (const auto& parent : c.parents) {
visit(*parent.type);
}
for (const auto& mem : c.members) {
visit(*mem.type);
}
sortedTypes_.push_back(c);
// Same as pointers, child do not create a dependency so are delayed until the
// end
for (const auto& child : c.children) {
typesToSort_.push(child);
}
}
void TopoSorter::visit(Container& c) {
for (const auto& param : c.templateParams) {
visit(*param.type);
}
sortedTypes_.push_back(c);
}
void TopoSorter::visit(Enum& e) {
sortedTypes_.push_back(e);
}
void TopoSorter::visit(Typedef& td) {
visit(*td.underlyingType());
sortedTypes_.push_back(td);
}
void TopoSorter::visit(Pointer& p) {
// Pointers do not create a dependency, but we do still care about the types
// they point to, so delay them until the end.
typesToSort_.push(*p.pointeeType());
}
} // namespace type_graph

View File

@ -0,0 +1,54 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#pragma once
#include <queue>
#include <unordered_set>
#include <vector>
#include "PassManager.h"
#include "Types.h"
#include "Visitor.h"
namespace type_graph {
/*
* TopoSorter
*
* Topologically sorts a list of types so that dependencies appear before
* dependent types.
*/
class TopoSorter : public RecursiveVisitor {
public:
static Pass createPass();
void sort(const std::vector<std::reference_wrapper<Type>>& types);
const std::vector<std::reference_wrapper<Type>>& sortedTypes() const;
void visit(Type& type) override;
void visit(Class& c) override;
void visit(Container& c) override;
void visit(Enum& e) override;
void visit(Typedef& td) override;
void visit(Pointer& p) override;
private:
std::unordered_set<Type*> visited_;
std::vector<std::reference_wrapper<Type>> sortedTypes_;
std::queue<std::reference_wrapper<Type>> typesToSort_;
};
} // namespace type_graph

62
oi/type_graph/TypeGraph.h Normal file
View File

@ -0,0 +1,62 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#pragma once
#include <functional>
#include <memory>
#include <vector>
#include "Types.h"
namespace type_graph {
class TypeGraph {
public:
// TODO provide iterator instead of direct vector access?
std::vector<std::reference_wrapper<Type>>& rootTypes() {
return rootTypes_;
}
const std::vector<std::reference_wrapper<Type>>& rootTypes() const {
return rootTypes_;
}
void addRoot(Type& type) {
rootTypes_.push_back(type);
}
template <typename T, typename... Args>
T* make_type(Args&&... args) {
auto type_unique_ptr = std::make_unique<T>(std::forward<Args>(args)...);
auto type_raw_ptr = type_unique_ptr.get();
types_.push_back(std::move(type_unique_ptr));
return type_raw_ptr;
}
void add(std::unique_ptr<Type> type) {
types_.push_back(std::move(type));
}
// TODO dodgy (use a getter instead to allow returning a const vector):
std::vector<std::reference_wrapper<Type>> finalTypes;
private:
std::vector<std::reference_wrapper<Type>> rootTypes_;
// Store all type objects in vectors for ownership. Order is not significant.
std::vector<std::unique_ptr<Type>> types_;
};
} // namespace type_graph

View File

@ -0,0 +1,106 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#include "TypeIdentifier.h"
#include "TypeGraph.h"
#include "oi/ContainerInfo.h"
namespace type_graph {
Pass TypeIdentifier::createPass() {
auto fn = [](TypeGraph& typeGraph) {
TypeIdentifier typeId{typeGraph};
for (auto& type : typeGraph.rootTypes()) {
typeId.visit(type);
}
};
return Pass("TypeIdentifier", fn);
}
void TypeIdentifier::visit(Type& type) {
if (visited_.count(&type) != 0)
return;
visited_.insert(&type);
type.accept(*this);
}
namespace {
bool isAllocator(Type* t) {
auto* c = dynamic_cast<Class*>(t);
if (!c)
return false;
// Maybe add more checks for an allocator.
// For now, just test for the presence of an "allocate" function
for (const auto& func : c->functions) {
if (func.name == "allocate") {
return true;
}
}
return false;
}
} // namespace
void TypeIdentifier::visit(Container& c) {
// TODO will containers exist at this point?
// maybe don't need this function
// auto *c = make_type<Container>(containerInfo);
const auto& stubParams = c.containerInfo_.stubTemplateParams;
// TODO these two arrays could be looped over in sync for better performance
for (size_t i = 0; i < c.templateParams.size(); i++) {
if (std::find(stubParams.begin(), stubParams.end(), i) !=
stubParams.end()) {
const auto& param = c.templateParams[i];
if (isAllocator(param.type)) {
// auto *allocator = dynamic_cast<Class*>(param.type); // TODO
// please don't do this... auto &typeToAllocate =
// *allocator->templateParams.at(0).type; auto *dummy =
// make_type<DummyAllocator>(typeToAllocate, param.type->size(),
// param.type->align()); c.templateParams[i] = dummy;
// TODO allocators are tricky... just remove them entirely for now
// The problem is a std::map<int, int> requires an allocator of type
// std::allocator<std::pair<const int, int>>, but we do not record
// constness of types.
if (i != c.templateParams.size() - 1) {
throw std::runtime_error("Unsupported allocator parameter");
}
c.templateParams.erase(c.templateParams.begin() + i,
c.templateParams.end());
} else {
size_t size = param.type->size();
if (size == 1) { // TODO this is a hack
size = 0;
}
auto* dummy = typeGraph_.make_type<Dummy>(size, param.type->align());
c.templateParams[i] = dummy;
}
}
}
// TODO replace current node with "c" (if we want to transform classes into
// containers here)
for (const auto& param : c.templateParams) {
visit(*param.type);
}
}
} // namespace type_graph

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#pragma once
#include <unordered_set>
#include <vector>
#include "PassManager.h"
#include "Types.h"
#include "Visitor.h"
namespace type_graph {
class TypeGraph;
/*
* TODO Pass Name
*
* TODO description
*/
class TypeIdentifier : public RecursiveVisitor {
public:
static Pass createPass();
TypeIdentifier(TypeGraph& typeGraph) : typeGraph_(typeGraph) {
}
void visit(Type& type) override;
void visit(Container& c) override;
private:
std::unordered_set<Type*> visited_;
TypeGraph& typeGraph_;
};
} // namespace type_graph

125
oi/type_graph/Types.cpp Normal file
View File

@ -0,0 +1,125 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#include "Types.h"
#include "Visitor.h"
namespace type_graph {
#define X(OI_TYPE_NAME) \
void OI_TYPE_NAME::accept(Visitor& v) { \
v.visit(*this); \
} \
void OI_TYPE_NAME::accept(ConstVisitor& v) const { \
v.visit(*this); \
}
OI_TYPE_LIST
#undef X
std::string Primitive::name() const {
switch (kind_) {
case Kind::Int8:
return "int8_t";
case Kind::Int16:
return "int16_t";
case Kind::Int32:
return "int32_t";
case Kind::Int64:
return "int64_t";
case Kind::UInt8:
return "uint8_t";
case Kind::UInt16:
return "uint16_t";
case Kind::UInt32:
return "uint32_t";
case Kind::UInt64:
return "uint64_t";
case Kind::Float32:
return "float";
case Kind::Float64:
return "double";
case Kind::Float80:
abort();
case Kind::Float128:
return "long double";
case Kind::Bool:
return "bool";
case Kind::UIntPtr:
return "uintptr_t";
case Kind::Void:
return "void";
}
}
std::size_t Primitive::size() const {
switch (kind_) {
case Kind::Int8:
return 1;
case Kind::Int16:
return 2;
case Kind::Int32:
return 4;
case Kind::Int64:
return 8;
case Kind::UInt8:
return 1;
case Kind::UInt16:
return 2;
case Kind::UInt32:
return 4;
case Kind::UInt64:
return 8;
case Kind::Float32:
return 4;
case Kind::Float64:
return 8;
case Kind::Float80:
abort();
case Kind::Float128:
return 16;
case Kind::Bool:
return 1;
case Kind::UIntPtr:
return sizeof(uintptr_t);
case Kind::Void:
return 0;
}
}
/*
* Returns true if the provided class is "dynamic".
*
* From the Itanium C++ ABI, a dynamic class is defined as:
* A class requiring a virtual table pointer (because it or its bases have
* one or more virtual member functions or virtual base classes).
*/
bool Class::isDynamic() const {
if (virtuality() != 0 /*DW_VIRTUALITY_none*/) {
// Virtual class - not fully supported by OI yet
return true;
}
for (const auto& func : functions) {
if (func.virtuality != 0 /*DW_VIRTUALITY_none*/) {
// Virtual function
return true;
}
}
return false;
}
} // namespace type_graph

424
oi/type_graph/Types.h Normal file
View File

@ -0,0 +1,424 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#pragma once
#include <cstddef>
#include <optional>
#include <string>
#include <vector>
#include "oi/ContainerInfo.h"
#define OI_TYPE_LIST \
X(Class) \
X(Container) \
X(Primitive) \
X(Enum) \
X(Array) \
X(Typedef) \
X(Pointer) \
X(Dummy) \
X(DummyAllocator)
struct ContainerInfo;
namespace type_graph {
class Visitor;
class ConstVisitor;
#define DECLARE_ACCEPT \
void accept(Visitor& v) override; \
void accept(ConstVisitor& v) const override;
// TODO delete copy and move ctors
// TODO make types hold references instead of pointers
// TODO type qualifiers are needed for some stuff?
class Type {
public:
virtual ~Type() = default;
virtual void accept(Visitor& v) = 0;
virtual void accept(ConstVisitor& v) const = 0;
// TODO don't always return a copy for name()
virtual std::string name() const = 0;
virtual size_t size() const = 0;
virtual uint64_t align() const = 0;
};
struct Member {
Member(Type* type,
const std::string& name,
uint64_t offset,
uint64_t align = 0)
: type(type), name(name), offset(offset), align(align) {
}
Type* type;
std::string name; // TODO make optional?
uint64_t offset;
uint64_t align = 0;
};
struct Function {
Function(const std::string& name, int virtuality = 0)
: name(name), virtuality(virtuality) {
}
std::string name;
int virtuality;
};
class Class;
struct Parent {
Parent(Type* type, uint64_t offset) : type(type), offset(offset) {
}
Type* type;
uint64_t offset;
};
struct TemplateParam {
// TODO make ctors explicit?
TemplateParam(Type* type) : type(type) {
}
TemplateParam(Type* type, std::string value)
: type(type), value(std::move(value)) {
}
Type* type;
std::optional<std::string>
value; // TODO is there any reason not to store all values as strings?
};
class Class : public Type {
public:
enum class Kind {
Class,
Struct,
Union,
};
Class(Kind kind, const std::string& name, size_t size, int virtuality = 0)
: kind_(kind), name_(name), size_(size), virtuality_(virtuality) {
}
DECLARE_ACCEPT
Kind kind() const {
return kind_;
}
virtual std::string name() const override {
return name_;
}
void setName(std::string name) {
name_ = std::move(name);
}
virtual size_t size() const override {
return size_;
}
virtual uint64_t align() const override {
return align_;
}
void setAlign(uint64_t alignment) {
align_ = alignment;
}
int virtuality() const {
return virtuality_;
}
bool packed() const {
return packed_;
}
void setPacked() {
packed_ = true;
}
bool isDynamic() const;
std::vector<TemplateParam> templateParams;
std::vector<Parent> parents; // Sorted by offset
std::vector<Member> members; // Sorted by offset
std::vector<Function> functions;
std::vector<std::reference_wrapper<Type>>
children; // Only for dynamic classes
private:
Kind kind_;
std::string name_;
size_t size_;
int virtuality_;
uint64_t align_ = 0;
bool packed_ = false;
};
class Container : public Type {
public:
Container(const ContainerInfo& containerInfo, size_t size)
: containerInfo_(containerInfo),
name_(containerInfo.typeName),
size_(size) {
}
DECLARE_ACCEPT
const std::string& containerName() const {
return containerInfo_.typeName;
}
virtual std::string name() const override {
return name_;
}
void setName(std::string name) {
name_ = std::move(name);
}
virtual size_t size() const override {
return size_;
}
virtual uint64_t align() const override {
return 8; // TODO not needed for containers?
}
std::vector<TemplateParam> templateParams;
const ContainerInfo& containerInfo_;
private:
std::string name_;
size_t size_;
};
class Enum : public Type {
public:
explicit Enum(const std::string& name, size_t size)
: name_(name), size_(size) {
}
DECLARE_ACCEPT
virtual std::string name() const override {
return name_;
}
virtual size_t size() const override {
return size_;
}
virtual uint64_t align() const override {
return size();
}
private:
std::string name_;
size_t size_;
};
class Array : public Type {
public:
Array(Type* elementType, size_t len) : elementType_(elementType), len_(len) {
}
DECLARE_ACCEPT
virtual std::string name() const override {
return "OIArray<" + elementType_->name() + ", " + std::to_string(len_) +
">";
}
virtual size_t size() const override {
return len_ * elementType_->size();
}
virtual uint64_t align() const override {
return elementType_->size();
}
Type* elementType() const {
return elementType_;
}
size_t len() const {
return len_;
}
private:
Type* elementType_;
size_t len_;
};
class Primitive : public Type {
public:
enum class Kind {
Int8,
Int16,
Int32,
Int64,
UInt8,
UInt16,
UInt32,
UInt64,
Float32,
Float64,
Float80, // TODO worth including?
Float128, // TODO can we generate this?
Bool,
UIntPtr, // Really an alias, but useful to have as its own primitive
Void,
};
explicit Primitive(Kind kind) : kind_(kind) {
}
DECLARE_ACCEPT
virtual std::string name() const override;
virtual size_t size() const override;
virtual uint64_t align() const override {
return size();
}
private:
Kind kind_;
};
class Typedef : public Type {
public:
explicit Typedef(const std::string& name, Type* underlyingType)
: name_(name), underlyingType_(underlyingType) {
}
DECLARE_ACCEPT
virtual std::string name() const override {
return name_;
}
virtual size_t size() const override {
return underlyingType_->size();
}
virtual uint64_t align() const override {
return underlyingType_->align();
}
Type* underlyingType() const {
return underlyingType_;
}
private:
std::string name_;
Type* underlyingType_;
};
class Pointer : public Type {
public:
explicit Pointer(Type* pointeeType) : pointeeType_(pointeeType) {
}
DECLARE_ACCEPT
virtual std::string name() const override {
return pointeeType_->name() + "*";
}
virtual size_t size() const override {
return sizeof(uintptr_t);
}
virtual uint64_t align() const override {
return size();
}
Type* pointeeType() const {
return pointeeType_;
}
private:
Type* pointeeType_;
};
class Dummy : public Type {
public:
explicit Dummy(size_t size, uint64_t align) : size_(size), align_(align) {
}
DECLARE_ACCEPT
virtual std::string name() const override {
return "DummySizedOperator<" + std::to_string(size_) + ", " +
std::to_string(align_) + ">";
}
virtual size_t size() const override {
return size_;
}
virtual uint64_t align() const override {
return align_;
}
private:
size_t size_;
uint64_t align_;
};
class DummyAllocator : public Type {
public:
explicit DummyAllocator(Type& type, size_t size, uint64_t align)
: type_(type), size_(size), align_(align) {
}
DECLARE_ACCEPT
virtual std::string name() const override {
return "std::allocator<" + type_.name() + ">";
// TODO custom sized allocators:
// return "DummyAllocator<" + type_.name() + ", " + std::to_string(size_)
// + "," + std::to_string(align_) + ">";
}
virtual size_t size() const override {
return size_;
}
virtual uint64_t align() const override {
return align_;
}
Type& allocType() const {
return type_;
}
private:
Type& type_;
size_t size_;
uint64_t align_;
};
} // namespace type_graph
#undef DECLARE_ACCEPT

135
oi/type_graph/Visitor.h Normal file
View File

@ -0,0 +1,135 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#pragma once
#include "Types.h"
namespace type_graph {
/*
* Visitor
*
* Abstract visitor base class.
*/
class Visitor {
public:
virtual ~Visitor() = default;
#define X(OI_TYPE_NAME) virtual void visit(OI_TYPE_NAME&) = 0;
OI_TYPE_LIST
#undef X
};
/*
* LazyVisitor
*
* Visitor base class which takes no action by default.
*/
class LazyVisitor : public Visitor {
public:
virtual ~LazyVisitor() = default;
#define X(OI_TYPE_NAME) \
virtual void visit(OI_TYPE_NAME&) { \
}
OI_TYPE_LIST
#undef X
};
/*
* RecursiveVisitor
*
* Visitor base class which recurses into types by default.
*/
class RecursiveVisitor : public Visitor {
public:
virtual ~RecursiveVisitor() = default;
virtual void visit(Type&) = 0;
virtual void visit(Class& c) {
for (const auto& param : c.templateParams) {
visit(*param.type);
}
for (const auto& parent : c.parents) {
visit(*parent.type);
}
for (const auto& mem : c.members) {
visit(*mem.type);
}
for (const auto& child : c.children) {
visit(child);
}
}
virtual void visit(Container& c) {
for (const auto& param : c.templateParams) {
visit(*param.type);
}
}
virtual void visit(Primitive&) {
}
virtual void visit(Enum&) {
}
virtual void visit(Array& a) {
visit(*a.elementType());
}
virtual void visit(Typedef& td) {
visit(*td.underlyingType());
}
virtual void visit(Pointer& p) {
visit(*p.pointeeType());
}
virtual void visit(Dummy&) {
}
virtual void visit(DummyAllocator& d) {
visit(d.allocType());
}
};
/*
* ConstVisitor
*
* Abstract visitor base class for walking a type graph without modifying it.
*/
class ConstVisitor {
public:
virtual ~ConstVisitor() = default;
#define X(OI_TYPE_NAME) virtual void visit(const OI_TYPE_NAME&) = 0;
OI_TYPE_LIST
#undef X
};
/*
* LazyConstVisitor
*
* Const visitor base class which takes no action by default.
*/
class LazyConstVisitor : public ConstVisitor {
public:
virtual ~LazyConstVisitor() = default;
// TODO work out how to get rid of this "2"
void visit2(const Type& type) {
type.accept(*this);
}
#define X(OI_TYPE_NAME) \
virtual void visit(const OI_TYPE_NAME&) { \
}
OI_TYPE_LIST
#undef X
};
} // namespace type_graph

View File

@ -46,6 +46,30 @@ add_executable(integration_sleepy
target_link_libraries(integration_sleepy folly_headers)
# Unit tests
add_executable(test_type_graph
test_add_padding.cpp
test_alignment_calc.cpp
test_drgn_parser.cpp
test_flattener.cpp
test_name_gen.cpp
test_remove_top_level_pointer.cpp
test_topo_sorter.cpp
test_type_identifier.cpp
)
add_dependencies(test_type_graph integration_test_target)
target_compile_definitions(test_type_graph PRIVATE
TARGET_EXE_PATH="${CMAKE_CURRENT_BINARY_DIR}/integration/integration_test_target"
)
target_link_libraries(test_type_graph
container_info
type_graph
${GMOCK_MAIN_LIBS}
)
include(GoogleTest)
gtest_discover_tests(test_type_graph)
cpp_unittest(
NAME test_parser
SRCS test_parser.cpp

View File

@ -1,17 +1,18 @@
includes = ["vector"]
definitions = '''
typedef uint64_t UInt64;
typedef uint64_t TdUInt64;
using UsingUInt64 = uint64_t;
using IntVector = std::vector<int>;
'''
[cases]
[cases.c_style]
param_types = ["UInt64"]
param_types = ["TdUInt64"]
setup = "return {};"
expect_json = '''[{
"staticSize":8,
"dynamicSize":0,
"isTypedef":true,
"typeName":"UInt64",
"typeName":"TdUInt64",
"members":[
{
"staticSize":8,
@ -22,6 +23,23 @@ definitions = '''
}
]}]'''
[cases.using]
param_types = ["UsingUInt64"]
setup = "return {};"
expect_json = '''[{
"staticSize":8,
"dynamicSize":0,
"isTypedef":true,
"typeName":"UsingUInt64",
"members":[
{
"staticSize":8,
"dynamicSize":0,
"isTypedef":false,
"typeName":"uint64_t",
"NOT":"members"
}
]}]'''
[cases.container]
param_types = ["const IntVector&"]
setup = "return {};"
expect_json = '''[{

86
test/test_add_padding.cpp Normal file
View File

@ -0,0 +1,86 @@
#include <gtest/gtest.h>
#include "oi/type_graph/AddPadding.h"
#include "oi/type_graph/PassManager.h"
#include "oi/type_graph/Printer.h"
#include "oi/type_graph/TypeGraph.h"
#include "oi/type_graph/Types.h"
using namespace type_graph;
void test(Pass pass,
std::vector<std::reference_wrapper<Type>> types,
std::string_view expected) {
TypeGraph typeGraph;
for (const auto& type : types) {
typeGraph.addRoot(type);
}
pass.run(typeGraph);
std::stringstream out;
Printer printer(out);
for (const auto& type : types) {
printer.print(type);
}
expected.remove_prefix(1); // Remove initial '\n'
EXPECT_EQ(expected, out.str());
}
TEST(AddPaddingTest, BetweenMembers) {
auto myclass = std::make_unique<Class>(Class::Kind::Class, "MyClass", 16);
auto myint8 = std::make_unique<Primitive>(Primitive::Kind::Int8);
auto myint64 = std::make_unique<Primitive>(Primitive::Kind::Int64);
myclass->members.push_back(Member(myint8.get(), "n1", 0));
myclass->members.push_back(Member(myint64.get(), "n2", 8));
test(AddPadding::createPass(), {*myclass}, R"(
[0] Class: MyClass (size: 16)
Member: n1 (offset: 0)
Primitive: int8_t
Member: __oid_padding (offset: 1)
[1] Array: (length: 7)
Primitive: int8_t
Member: n2 (offset: 8)
Primitive: int64_t
)");
}
TEST(AddPaddingTest, AtEnd) {
auto myclass = std::make_unique<Class>(Class::Kind::Struct, "MyStruct", 16);
auto myint8 = std::make_unique<Primitive>(Primitive::Kind::Int8);
auto myint64 = std::make_unique<Primitive>(Primitive::Kind::Int64);
myclass->members.push_back(Member(myint64.get(), "n1", 0));
myclass->members.push_back(Member(myint8.get(), "n2", 8));
test(AddPadding::createPass(), {*myclass}, R"(
[0] Struct: MyStruct (size: 16)
Member: n1 (offset: 0)
Primitive: int64_t
Member: n2 (offset: 8)
Primitive: int8_t
Member: __oid_padding (offset: 9)
[1] Array: (length: 7)
Primitive: int8_t
)");
}
TEST(AddPaddingTest, UnionNotPadded) {
auto myclass = std::make_unique<Class>(Class::Kind::Union, "MyUnion", 8);
auto myint8 = std::make_unique<Primitive>(Primitive::Kind::Int8);
auto myint64 = std::make_unique<Primitive>(Primitive::Kind::Int64);
myclass->members.push_back(Member(myint64.get(), "n1", 0));
myclass->members.push_back(Member(myint8.get(), "n2", 0));
test(AddPadding::createPass(), {*myclass}, R"(
[0] Union: MyUnion (size: 8)
Member: n1 (offset: 0)
Primitive: int64_t
Member: n2 (offset: 0)
Primitive: int8_t
)");
}
// TODO test we follow class members, templates, children

View File

@ -0,0 +1,92 @@
#include <gtest/gtest.h>
#include "oi/type_graph/AlignmentCalc.h"
using namespace type_graph;
// TODO put in header:
void test(Pass pass,
std::vector<std::reference_wrapper<Type>> types,
std::string_view expected);
TEST(AlignmentCalcTest, PrimitiveMembers) {
auto myclass = std::make_unique<Class>(Class::Kind::Class, "MyClass", 16);
auto myint8 = std::make_unique<Primitive>(Primitive::Kind::Int8);
auto myint64 = std::make_unique<Primitive>(Primitive::Kind::Int64);
myclass->members.push_back(Member(myint8.get(), "n", 0));
myclass->members.push_back(Member(myint64.get(), "n", 8));
test(AlignmentCalc::createPass(), {*myclass}, R"(
[0] Class: MyClass (size: 16, align: 8)
Member: n (offset: 0, align: 1)
Primitive: int8_t
Member: n (offset: 8, align: 8)
Primitive: int64_t
)");
}
TEST(AlignmentCalcTest, StructMembers) {
auto mystruct = std::make_unique<Class>(Class::Kind::Struct, "MyStruct", 8);
auto myint32 = std::make_unique<Primitive>(Primitive::Kind::Int32);
mystruct->members.push_back(Member(myint32.get(), "n1", 0));
mystruct->members.push_back(Member(myint32.get(), "n2", 4));
auto myclass = std::make_unique<Class>(Class::Kind::Class, "MyClass", 12);
auto myint8 = std::make_unique<Primitive>(Primitive::Kind::Int8);
myclass->members.push_back(Member(myint8.get(), "n", 0));
myclass->members.push_back(Member(mystruct.get(), "s", 4));
test(AlignmentCalc::createPass(), {*myclass}, R"(
[0] Class: MyClass (size: 12, align: 4)
Member: n (offset: 0, align: 1)
Primitive: int8_t
Member: s (offset: 4, align: 4)
[1] Struct: MyStruct (size: 8, align: 4)
Member: n1 (offset: 0, align: 4)
Primitive: int32_t
Member: n2 (offset: 4, align: 4)
Primitive: int32_t
)");
}
TEST(AlignmentCalcTest, StructInContainer) {
auto myclass = std::make_unique<Class>(Class::Kind::Class, "MyClass", 16);
auto myint8 = std::make_unique<Primitive>(Primitive::Kind::Int8);
auto myint64 = std::make_unique<Primitive>(Primitive::Kind::Int64);
myclass->members.push_back(Member(myint8.get(), "n", 0));
myclass->members.push_back(Member(myint64.get(), "n", 8));
auto mycontainer = std::make_unique<Container>(ContainerInfo{}, 8);
mycontainer->templateParams.push_back(myclass.get());
AlignmentCalc calc;
calc.calculateAlignments({*mycontainer});
EXPECT_EQ(myclass->align(), 8);
test(AlignmentCalc::createPass(), {*mycontainer}, R"(
[0] Container: (size: 8)
Param
[1] Class: MyClass (size: 16, align: 8)
Member: n (offset: 0, align: 1)
Primitive: int8_t
Member: n (offset: 8, align: 8)
Primitive: int64_t
)");
}
TEST(AlignmentCalcTest, Packed) {
auto mystruct = std::make_unique<Class>(Class::Kind::Struct, "MyStruct", 9);
auto myint8 = std::make_unique<Primitive>(Primitive::Kind::Int8);
auto myint64 = std::make_unique<Primitive>(Primitive::Kind::Int64);
mystruct->members.push_back(Member(myint8.get(), "n1", 0));
mystruct->members.push_back(Member(myint64.get(), "n2", 1));
test(AlignmentCalc::createPass(), {*mystruct}, R"(
[0] Struct: MyStruct (size: 9, align: 8, packed)
Member: n1 (offset: 0, align: 1)
Primitive: int8_t
Member: n2 (offset: 1, align: 8)
Primitive: int64_t
)");
}

499
test/test_drgn_parser.cpp Normal file
View File

@ -0,0 +1,499 @@
#include <gtest/gtest.h>
#include <regex>
#include "oi/SymbolService.h"
// TODO needed?:
#include "oi/ContainerInfo.h"
#include "oi/OIParser.h"
#include "oi/type_graph/DrgnParser.h"
#include "oi/type_graph/Printer.h"
#include "oi/type_graph/TypeGraph.h"
#include "oi/type_graph/Types.h"
using namespace type_graph;
// TODO setup google logging for tests so it doesn't appear on terminal by
// default
class DrgnParserTest : public ::testing::Test {
protected:
static void SetUpTestSuite() {
symbols_ = new SymbolService{TARGET_EXE_PATH};
}
static void TearDownTestSuite() {
delete symbols_;
}
void test(std::string_view function,
std::string_view expected,
bool chaseRawPointers = true);
void testMultiCompiler(std::string_view function,
std::string_view expectedClang,
std::string_view expectedGcc,
bool chaseRawPointers = true);
static SymbolService* symbols_;
};
SymbolService* DrgnParserTest::symbols_ = nullptr;
void DrgnParserTest::test(std::string_view function,
std::string_view expected,
bool chaseRawPointers) {
irequest req{"entry", std::string{function}, "arg0"};
auto drgnRoot = symbols_->getRootType(req);
TypeGraph typeGraph;
// TODO more container types, with various template parameter options
ContainerInfo std_vector{"std::vector", SEQ_TYPE, "vector"};
std_vector.stubTemplateParams = {1};
std::vector<ContainerInfo> containers;
containers.emplace_back(std::move(std_vector));
DrgnParser drgnParser(typeGraph, containers, chaseRawPointers);
Type* type = drgnParser.parse(drgnRoot->type.type);
std::stringstream out;
Printer printer(out);
printer.print(*type);
// TODO standardise expected-actual order
expected.remove_prefix(1); // Remove initial '\n'
EXPECT_EQ(expected, out.str());
}
void DrgnParserTest::testMultiCompiler(
std::string_view function,
[[maybe_unused]] std::string_view expectedClang,
[[maybe_unused]] std::string_view expectedGcc,
bool chaseRawPointers) {
#if defined(__clang__)
test(function, expectedClang, chaseRawPointers);
#else
test(function, expectedGcc, chaseRawPointers);
#endif
}
TEST_F(DrgnParserTest, SimpleStruct) {
test("oid_test_case_simple_struct", R"(
[0] Pointer
[1] Struct: SimpleStruct (size: 16)
Member: a (offset: 0)
Primitive: int32_t
Member: b (offset: 4)
Primitive: int8_t
Member: c (offset: 8)
Primitive: int64_t
)");
}
TEST_F(DrgnParserTest, SimpleClass) {
test("oid_test_case_simple_class", R"(
[0] Pointer
[1] Class: SimpleClass (size: 16)
Member: a (offset: 0)
Primitive: int32_t
Member: b (offset: 4)
Primitive: int8_t
Member: c (offset: 8)
Primitive: int64_t
)");
}
TEST_F(DrgnParserTest, SimpleUnion) {
test("oid_test_case_simple_union", R"(
[0] Pointer
[1] Union: SimpleUnion (size: 8)
Member: a (offset: 0)
Primitive: int32_t
Member: b (offset: 0)
Primitive: int8_t
Member: c (offset: 0)
Primitive: int64_t
)");
}
TEST_F(DrgnParserTest, Inheritance) {
test("oid_test_case_inheritance_access_public", R"(
[0] Pointer
[1] Class: Public (size: 8)
Parent (offset: 0)
[2] Class: Base (size: 4)
Member: base_int (offset: 0)
Primitive: int32_t
Member: public_int (offset: 4)
Primitive: int32_t
)");
}
TEST_F(DrgnParserTest, InheritanceMultiple) {
test("oid_test_case_inheritance_multiple_a", R"(
[0] Pointer
[1] Struct: Derived_2 (size: 24)
Parent (offset: 0)
[2] Struct: Base_1 (size: 4)
Member: a (offset: 0)
Primitive: int32_t
Parent (offset: 4)
[3] Struct: Derived_1 (size: 12)
Parent (offset: 0)
[4] Struct: Base_2 (size: 4)
Member: b (offset: 0)
Primitive: int32_t
Parent (offset: 4)
[5] Struct: Base_3 (size: 4)
Member: c (offset: 0)
Primitive: int32_t
Member: d (offset: 8)
Primitive: int32_t
Parent (offset: 16)
[6] Struct: Base_4 (size: 4)
Member: e (offset: 0)
Primitive: int32_t
Member: f (offset: 20)
Primitive: int32_t
)");
}
TEST_F(DrgnParserTest, Container) {
testMultiCompiler("oid_test_case_std_vector_int_empty", R"(
[0] Pointer
[1] Container: std::vector (size: 24)
Param
Primitive: int32_t
Param
[2] Class: allocator<int> (size: 1)
Param
Primitive: int32_t
Parent (offset: 0)
[3] Typedef: __allocator_base<int>
[4] Class: new_allocator<int> (size: 1)
Param
Primitive: int32_t
Function: new_allocator
Function: new_allocator
Function: allocate
Function: deallocate
Function: _M_max_size
Function: allocator
Function: allocator
Function: operator=
Function: ~allocator
Function: allocate
Function: deallocate
)",
R"(
[0] Pointer
[1] Container: std::vector (size: 24)
Param
Primitive: int32_t
Param
[2] Class: allocator<int> (size: 1)
Parent (offset: 0)
[3] Class: new_allocator<int> (size: 1)
Param
Primitive: int32_t
Function: new_allocator
Function: new_allocator
Function: allocate
Function: deallocate
Function: _M_max_size
Function: allocator
Function: allocator
Function: operator=
Function: ~allocator
Function: allocate
Function: deallocate
)");
}
// TODO test vector with custom allocator
TEST_F(DrgnParserTest, Enum) {
test("oid_test_case_enums_scoped", R"(
Enum: ScopedEnum (size: 4)
)");
}
TEST_F(DrgnParserTest, EnumInt8) {
test("oid_test_case_enums_scoped_int8", R"(
Enum: ScopedEnumInt8 (size: 1)
)");
}
TEST_F(DrgnParserTest, UnscopedEnum) {
test("oid_test_case_enums_unscoped", R"(
Enum: UNSCOPED_ENUM (size: 4)
)");
}
TEST_F(DrgnParserTest, Typedef) {
test("oid_test_case_typedefs_c_style", R"(
[0] Typedef: TdUInt64
[1] Typedef: uint64_t
[2] Typedef: __uint64_t
Primitive: uint64_t
)");
}
TEST_F(DrgnParserTest, Using) {
test("oid_test_case_typedefs_using", R"(
[0] Typedef: UsingUInt64
[1] Typedef: uint64_t
[2] Typedef: __uint64_t
Primitive: uint64_t
)");
}
TEST_F(DrgnParserTest, ArrayMember) {
test("oid_test_case_arrays_member_int10", R"(
[0] Pointer
[1] Struct: Foo10 (size: 40)
Member: arr (offset: 0)
[2] Array: (length: 10)
Primitive: int32_t
)");
}
TEST_F(DrgnParserTest, ArrayRef) {
test("oid_test_case_arrays_ref_int10", R"(
[0] Pointer
[1] Array: (length: 10)
Primitive: int32_t
)");
}
TEST_F(DrgnParserTest, ArrayDirect) {
test("oid_test_case_arrays_direct_int10", R"(
[0] Pointer
Primitive: int32_t
)");
}
TEST_F(DrgnParserTest, Pointer) {
test("oid_test_case_pointers_struct_primitive_ptrs", R"(
[0] Pointer
[1] Struct: PrimitivePtrs (size: 24)
Member: a (offset: 0)
Primitive: int32_t
Member: b (offset: 8)
[2] Pointer
Primitive: int32_t
Member: c (offset: 16)
[3] Pointer
Primitive: void
)");
}
TEST_F(DrgnParserTest, PointerNoFollow) {
test("oid_test_case_pointers_struct_primitive_ptrs", R"(
[0] Pointer
[1] Struct: PrimitivePtrs (size: 24)
Member: a (offset: 0)
Primitive: int32_t
Member: b (offset: 8)
Primitive: uintptr_t
Member: c (offset: 16)
Primitive: uintptr_t
)",
false);
}
TEST_F(DrgnParserTest, PointerIncomplete) {
test("oid_test_case_pointers_incomplete_raw", R"(
[0] Pointer
Primitive: void
)");
}
TEST_F(DrgnParserTest, Cycle) {
test("oid_test_case_cycles_raw_ptr", R"(
[0] Pointer
[1] Struct: RawNode (size: 16)
Member: value (offset: 0)
Primitive: int32_t
Member: next (offset: 8)
[2] Pointer
[1]
)");
}
TEST_F(DrgnParserTest, ClassTemplateInt) {
test("oid_test_case_templates_int", R"(
[0] Pointer
[1] Class: TemplatedClass1<int> (size: 4)
Param
Primitive: int32_t
Member: val (offset: 0)
Primitive: int32_t
)");
}
TEST_F(DrgnParserTest, ClassTemplateVector) {
testMultiCompiler("oid_test_case_templates_vector", R"(
[0] Pointer
[1] Class: TemplatedClass1<std::vector<int, std::allocator<int> > > (size: 24)
Param
[2] Container: std::vector (size: 24)
Param
Primitive: int32_t
Param
[3] Class: allocator<int> (size: 1)
Param
Primitive: int32_t
Parent (offset: 0)
[4] Typedef: __allocator_base<int>
[5] Class: new_allocator<int> (size: 1)
Param
Primitive: int32_t
Function: new_allocator
Function: new_allocator
Function: allocate
Function: deallocate
Function: _M_max_size
Function: allocator
Function: allocator
Function: operator=
Function: ~allocator
Function: allocate
Function: deallocate
Member: val (offset: 0)
[2]
Function: ~TemplatedClass1
Function: TemplatedClass1
)",
R"(
[0] Pointer
[1] Class: TemplatedClass1<std::vector<int, std::allocator<int> > > (size: 24)
Param
[2] Container: std::vector (size: 24)
Param
Primitive: int32_t
Param
[3] Class: allocator<int> (size: 1)
Parent (offset: 0)
[4] Class: new_allocator<int> (size: 1)
Param
Primitive: int32_t
Function: new_allocator
Function: new_allocator
Function: allocate
Function: deallocate
Function: _M_max_size
Function: allocator
Function: allocator
Function: operator=
Function: ~allocator
Function: allocate
Function: deallocate
Member: val (offset: 0)
[2]
Function: TemplatedClass1
Function: ~TemplatedClass1
)");
}
TEST_F(DrgnParserTest, ClassTemplateTwo) {
test("oid_test_case_templates_two", R"(
[0] Pointer
[1] Class: TemplatedClass2<ns_templates::Foo, int> (size: 12)
Param
[2] Struct: Foo (size: 8)
Member: a (offset: 0)
Primitive: int32_t
Member: b (offset: 4)
Primitive: int32_t
Param
Primitive: int32_t
Member: tc1 (offset: 0)
[3] Class: TemplatedClass1<ns_templates::Foo> (size: 8)
Param
[2]
Member: val (offset: 0)
[2]
Member: val2 (offset: 8)
Primitive: int32_t
)");
}
TEST_F(DrgnParserTest, ClassTemplateValue) {
test("oid_test_case_templates_value", R"(
[0] Pointer
[1] Struct: TemplatedClassVal<3> (size: 12)
Param
Value: 3
Member: arr (offset: 0)
[2] Array: (length: 3)
Primitive: int32_t
)");
}
// TODO
// TEST_F(DrgnParserTest, ClassFunctions) {
// test("TestClassFunctions", R"(
//[0] Pointer
//[1] Class: ClassFunctions (size: 4)
// Member: memberA (offset: 0)
// Primitive: int32_t
// Function: foo (virtuality: 0)
// Function: bar (virtuality: 0)
//)");
//}
TEST_F(DrgnParserTest, StructAlignment) {
GTEST_SKIP() << "Alignment not reported by drgn yet";
test("oid_test_case_alignment_struct", R"(
[0] Pointer
[1] Struct: Align16 (size: 16, align: 16)
Member: c (offset: 0)
Primitive: int8_t
)");
}
TEST_F(DrgnParserTest, MemberAlignment) {
GTEST_SKIP() << "Alignment not reported by drgn yet";
test("oid_test_case_alignment_member_alignment", R"(
[0] Pointer
[1] Struct: MemberAlignment (size: 64)
Member: c (offset: 0)
Primitive: int8_t
Member: c32 (offset: 32, align: 32)
Primitive: int8_t
)");
}
TEST_F(DrgnParserTest, VirtualFunctions) {
testMultiCompiler("oid_test_case_inheritance_polymorphic_a_as_a", R"(
[0] Pointer
[1] Class: A (size: 16)
Member: _vptr$A (offset: 0)
[2] Pointer
[3] Pointer
Primitive: void
Member: int_a (offset: 8)
Primitive: int32_t
Function: ~A (virtual)
Function: myfunc (virtual)
Function: A
Function: A
)",
R"(
[0] Pointer
[1] Class: A (size: 16)
Member: _vptr.A (offset: 0)
[2] Pointer
[3] Pointer
Primitive: void
Member: int_a (offset: 8)
Primitive: int32_t
Function: operator=
Function: A
Function: A
Function: ~A (virtual)
Function: myfunc (virtual)
)");
}
// TODO test virtual classes

689
test/test_flattener.cpp Normal file
View File

@ -0,0 +1,689 @@
#include <gtest/gtest.h>
#include "oi/type_graph/Flattener.h"
#include "oi/type_graph/Printer.h"
#include "oi/type_graph/Types.h"
using namespace type_graph;
Container getVector(); // TODO put in a header
namespace {
void test(std::vector<std::reference_wrapper<Type>> types,
std::string_view expected) {
Flattener flattener;
flattener.flatten(types);
std::stringstream out;
Printer printer(out);
for (const auto& type : types) {
printer.print(type);
}
expected.remove_prefix(1); // Remove initial '\n'
EXPECT_EQ(expected, out.str());
}
} // namespace
TEST(FlattenerTest, NoParents) {
// Original and flattened:
// struct MyStruct { int n0; };
// class MyClass {
// int n;
// MyEnum e;
// MyStruct mystruct;
// };
auto myint = std::make_unique<Primitive>(Primitive::Kind::Int32);
auto myenum = std::make_unique<Enum>("MyEnum", 4);
auto mystruct = std::make_unique<Class>(Class::Kind::Struct, "MyStruct", 4);
auto myclass = std::make_unique<Class>(Class::Kind::Class, "MyClass", 12);
mystruct->members.push_back(Member(myint.get(), "n0", 0));
myclass->members.push_back(Member(myint.get(), "n", 0));
myclass->members.push_back(Member(myenum.get(), "e", 4));
myclass->members.push_back(Member(mystruct.get(), "mystruct", 8));
test({*myclass}, R"(
[0] Class: MyClass (size: 12)
Member: n (offset: 0)
Primitive: int32_t
Member: e (offset: 4)
Enum: MyEnum (size: 4)
Member: mystruct (offset: 8)
[1] Struct: MyStruct (size: 4)
Member: n0 (offset: 0)
Primitive: int32_t
)");
}
TEST(FlattenerTest, OnlyParents) {
// Original:
// class C { int c; };
// class B { int b; };
// class A : B, C { };
//
// Flattened:
// class A {
// int b;
// int c;
// };
auto myint = std::make_unique<Primitive>(Primitive::Kind::Int32);
auto classA = std::make_unique<Class>(Class::Kind::Class, "ClassA", 8);
auto classB = std::make_unique<Class>(Class::Kind::Class, "ClassB", 4);
auto classC = std::make_unique<Class>(Class::Kind::Class, "ClassC", 4);
classC->members.push_back(Member(myint.get(), "c", 0));
classB->members.push_back(Member(myint.get(), "b", 0));
classA->parents.push_back(Parent(classB.get(), 0));
classA->parents.push_back(Parent(classC.get(), 4));
test({*classA}, R"(
[0] Class: ClassA (size: 8)
Member: b (offset: 0)
Primitive: int32_t
Member: c (offset: 4)
Primitive: int32_t
)");
}
TEST(FlattenerTest, ParentsFirst) {
// Original:
// class C { int c; };
// class B { int b; };
// class A : B, C { int a; };
//
// Flattened:
// class A {
// int b;
// int c;
// int a;
// };
auto myint = std::make_unique<Primitive>(Primitive::Kind::Int32);
auto classA = std::make_unique<Class>(Class::Kind::Class, "ClassA", 12);
auto classB = std::make_unique<Class>(Class::Kind::Class, "ClassB", 4);
auto classC = std::make_unique<Class>(Class::Kind::Class, "ClassC", 4);
classC->members.push_back(Member(myint.get(), "c", 0));
classB->members.push_back(Member(myint.get(), "b", 0));
classA->parents.push_back(Parent(classB.get(), 0));
classA->parents.push_back(Parent(classC.get(), 4));
classA->members.push_back(Member(myint.get(), "a", 8));
test({*classA}, R"(
[0] Class: ClassA (size: 12)
Member: b (offset: 0)
Primitive: int32_t
Member: c (offset: 4)
Primitive: int32_t
Member: a (offset: 8)
Primitive: int32_t
)");
}
TEST(FlattenerTest, MembersFirst) {
// Original:
// class C { int c; };
// class B { int b; };
// class A : B, C { int a; };
//
// Flattened:
// class A {
// int a;
// int b;
// int c;
// };
auto myint = std::make_unique<Primitive>(Primitive::Kind::Int32);
auto classA = std::make_unique<Class>(Class::Kind::Class, "ClassA", 12);
auto classB = std::make_unique<Class>(Class::Kind::Class, "ClassB", 4);
auto classC = std::make_unique<Class>(Class::Kind::Class, "ClassC", 4);
classC->members.push_back(Member(myint.get(), "c", 0));
classB->members.push_back(Member(myint.get(), "b", 0));
classA->members.push_back(Member(myint.get(), "a", 0));
classA->parents.push_back(Parent(classB.get(), 4));
classA->parents.push_back(Parent(classC.get(), 8));
test({*classA}, R"(
[0] Class: ClassA (size: 12)
Member: a (offset: 0)
Primitive: int32_t
Member: b (offset: 4)
Primitive: int32_t
Member: c (offset: 8)
Primitive: int32_t
)");
}
TEST(FlattenerTest, MixedMembersAndParents) {
// Original:
// class C { int c; };
// class B { int b; };
// class A : B, C { int a1; int a2; };
//
// Flattened:
// class A {
// int b;
// int a1;
// int a2;
// int c;
// };
auto myint = std::make_unique<Primitive>(Primitive::Kind::Int32);
auto classA = std::make_unique<Class>(Class::Kind::Class, "ClassA", 16);
auto classB = std::make_unique<Class>(Class::Kind::Class, "ClassB", 4);
auto classC = std::make_unique<Class>(Class::Kind::Class, "ClassC", 4);
classC->members.push_back(Member(myint.get(), "c", 0));
classB->members.push_back(Member(myint.get(), "b", 0));
classA->parents.push_back(Parent(classB.get(), 0));
classA->members.push_back(Member(myint.get(), "a1", 4));
classA->members.push_back(Member(myint.get(), "a2", 8));
classA->parents.push_back(Parent(classC.get(), 12));
test({*classA}, R"(
[0] Class: ClassA (size: 16)
Member: b (offset: 0)
Primitive: int32_t
Member: a1 (offset: 4)
Primitive: int32_t
Member: a2 (offset: 8)
Primitive: int32_t
Member: c (offset: 12)
Primitive: int32_t
)");
}
TEST(FlattenerTest, EmptyParent) {
// Original:
// class C { int c; };
// class B { };
// class A : B, C { int a1; int a2; };
//
// Flattened:
// class A {
// int c;
// int a1;
// int a2;
// };
auto myint = std::make_unique<Primitive>(Primitive::Kind::Int32);
auto classA = std::make_unique<Class>(Class::Kind::Class, "ClassA", 12);
auto classB = std::make_unique<Class>(Class::Kind::Class, "ClassB", 0);
auto classC = std::make_unique<Class>(Class::Kind::Class, "ClassC", 4);
classC->members.push_back(Member(myint.get(), "c", 0));
classA->members.push_back(Member(myint.get(), "a1", 4));
classA->members.push_back(Member(myint.get(), "a2", 8));
classA->parents.push_back(Parent(classB.get(), 4));
classA->parents.push_back(Parent(classC.get(), 0));
test({*classA}, R"(
[0] Class: ClassA (size: 12)
Member: c (offset: 0)
Primitive: int32_t
Member: a1 (offset: 4)
Primitive: int32_t
Member: a2 (offset: 8)
Primitive: int32_t
)");
}
TEST(FlattenerTest, TwoDeep) {
// Original:
// class D { int d; };
// class C { int c; };
// class B : D { int b; };
// class A : B, C { int a; };
//
// Flattened:
// class A {
// int d;
// int b;
// int c;
// int a;
// };
auto myint = std::make_unique<Primitive>(Primitive::Kind::Int32);
auto classA = std::make_unique<Class>(Class::Kind::Class, "ClassA", 16);
auto classB = std::make_unique<Class>(Class::Kind::Class, "ClassB", 8);
auto classC = std::make_unique<Class>(Class::Kind::Class, "ClassC", 4);
auto classD = std::make_unique<Class>(Class::Kind::Class, "ClassD", 4);
classD->members.push_back(Member(myint.get(), "d", 0));
classC->members.push_back(Member(myint.get(), "c", 0));
classB->parents.push_back(Parent(classD.get(), 0));
classB->members.push_back(Member(myint.get(), "b", 4));
classA->parents.push_back(Parent(classB.get(), 0));
classA->parents.push_back(Parent(classC.get(), 8));
classA->members.push_back(Member(myint.get(), "a", 12));
test({*classA}, R"(
[0] Class: ClassA (size: 16)
Member: d (offset: 0)
Primitive: int32_t
Member: b (offset: 4)
Primitive: int32_t
Member: c (offset: 8)
Primitive: int32_t
Member: a (offset: 12)
Primitive: int32_t
)");
}
TEST(FlattenerTest, DiamondInheritance) {
// Original:
// class C { int c; };
// class B : C { int b; };
// class A : B, C { int a; };
//
// Flattened:
// class A {
// int c0;
// int b;
// int c1;
// int a;
// };
auto myint = std::make_unique<Primitive>(Primitive::Kind::Int32);
auto classA = std::make_unique<Class>(Class::Kind::Class, "ClassA", 16);
auto classB = std::make_unique<Class>(Class::Kind::Class, "ClassB", 8);
auto classC = std::make_unique<Class>(Class::Kind::Class, "ClassC", 4);
classC->members.push_back(Member(myint.get(), "c", 0));
classB->parents.push_back(Parent(classC.get(), 0));
classB->members.push_back(Member(myint.get(), "b", 4));
classA->parents.push_back(Parent(classB.get(), 0));
classA->parents.push_back(Parent(classC.get(), 8));
classA->members.push_back(Member(myint.get(), "a", 12));
test({*classA}, R"(
[0] Class: ClassA (size: 16)
Member: c (offset: 0)
Primitive: int32_t
Member: b (offset: 4)
Primitive: int32_t
Member: c (offset: 8)
Primitive: int32_t
Member: a (offset: 12)
Primitive: int32_t
)");
}
TEST(FlattenerTest, Member) {
// Original:
// class C { int c; };
// class B : C { int b; };
// class A { int a; B b; };
//
// Flattened:
// class B { int c; int b; };
// Class A { int a; B b; };
auto myint = std::make_unique<Primitive>(Primitive::Kind::Int32);
auto classA = std::make_unique<Class>(Class::Kind::Class, "ClassA", 12);
auto classB = std::make_unique<Class>(Class::Kind::Class, "ClassB", 8);
auto classC = std::make_unique<Class>(Class::Kind::Class, "ClassC", 4);
classC->members.push_back(Member(myint.get(), "c", 0));
classB->parents.push_back(Parent(classC.get(), 0));
classB->members.push_back(Member(myint.get(), "b", 4));
classA->members.push_back(Member(myint.get(), "a", 0));
classA->members.push_back(Member(classB.get(), "b", 4));
test({*classA}, R"(
[0] Class: ClassA (size: 12)
Member: a (offset: 0)
Primitive: int32_t
Member: b (offset: 4)
[1] Class: ClassB (size: 8)
Member: c (offset: 0)
Primitive: int32_t
Member: b (offset: 4)
Primitive: int32_t
)");
}
TEST(FlattenerTest, MemberOfParent) {
// Original:
// class C { int c; };
// class B { int b; C c; };
// class A : B { int a; };
//
// Flattened:
// class C { int c; };
// class A { int b; C c; int a; };
auto myint = std::make_unique<Primitive>(Primitive::Kind::Int32);
auto classA = std::make_unique<Class>(Class::Kind::Class, "ClassA", 12);
auto classB = std::make_unique<Class>(Class::Kind::Class, "ClassB", 8);
auto classC = std::make_unique<Class>(Class::Kind::Class, "ClassC", 4);
classC->members.push_back(Member(myint.get(), "c", 0));
classB->members.push_back(Member(myint.get(), "b", 0));
classB->members.push_back(Member(classC.get(), "c", 4));
classA->parents.push_back(Parent(classB.get(), 0));
classA->members.push_back(Member(myint.get(), "a", 8));
test({*classA}, R"(
[0] Class: ClassA (size: 12)
Member: b (offset: 0)
Primitive: int32_t
Member: c (offset: 4)
[1] Class: ClassC (size: 4)
Member: c (offset: 0)
Primitive: int32_t
Member: a (offset: 8)
Primitive: int32_t
)");
}
TEST(FlattenerTest, ContainerParam) {
// Original:
// class B { int b; };
// class A : B { int a; };
// std::vector<A, int>
//
// Flattened:
// class A { int b; int a; };
// std::vector<A, int>
auto myint = std::make_unique<Primitive>(Primitive::Kind::Int32);
auto classA = std::make_unique<Class>(Class::Kind::Class, "ClassA", 8);
auto classB = std::make_unique<Class>(Class::Kind::Class, "ClassB", 4);
auto container = getVector();
classB->members.push_back(Member(myint.get(), "b", 0));
classA->parents.push_back(Parent(classB.get(), 0));
classA->members.push_back(Member(myint.get(), "a", 4));
container.templateParams.push_back(TemplateParam(classA.get()));
container.templateParams.push_back(TemplateParam(myint.get()));
test({container}, R"(
[0] Container: std::vector (size: 24)
Param
[1] Class: ClassA (size: 8)
Member: b (offset: 0)
Primitive: int32_t
Member: a (offset: 4)
Primitive: int32_t
Param
Primitive: int32_t
)");
}
TEST(FlattenerTest, Array) {
// Original:
// class B { int b; };
// class A : B { int a; };
// A[5]
auto myint = std::make_unique<Primitive>(Primitive::Kind::Int32);
auto classB = std::make_unique<Class>(Class::Kind::Class, "ClassB", 4);
classB->members.push_back(Member(myint.get(), "b", 0));
auto classA = std::make_unique<Class>(Class::Kind::Class, "ClassA", 8);
classA->parents.push_back(Parent(classB.get(), 0));
classA->members.push_back(Member(myint.get(), "a", 4));
auto arrayA = std::make_unique<Array>(classA.get(), 5);
test({*arrayA}, R"(
[0] Array: (length: 5)
[1] Class: ClassA (size: 8)
Member: b (offset: 0)
Primitive: int32_t
Member: a (offset: 4)
Primitive: int32_t
)");
}
TEST(FlattenerTest, Typedef) {
// Original:
// class B { int b; };
// class A : B { int a; };
// using aliasA = A;
auto myint = std::make_unique<Primitive>(Primitive::Kind::Int32);
auto classB = std::make_unique<Class>(Class::Kind::Class, "ClassB", 4);
classB->members.push_back(Member(myint.get(), "b", 0));
auto classA = std::make_unique<Class>(Class::Kind::Class, "ClassA", 8);
classA->parents.push_back(Parent(classB.get(), 0));
classA->members.push_back(Member(myint.get(), "a", 4));
auto aliasA = std::make_unique<Typedef>("aliasA", classA.get());
test({*aliasA}, R"(
[0] Typedef: aliasA
[1] Class: ClassA (size: 8)
Member: b (offset: 0)
Primitive: int32_t
Member: a (offset: 4)
Primitive: int32_t
)");
}
TEST(FlattenerTest, TypedefParent) {
// Original:
// class B { int b; };
// using aliasB = B;
// class A : aliasB { int a; };
auto myint = std::make_unique<Primitive>(Primitive::Kind::Int32);
auto classB = std::make_unique<Class>(Class::Kind::Class, "ClassB", 4);
classB->members.push_back(Member(myint.get(), "b", 0));
auto aliasB = std::make_unique<Typedef>("aliasB", classB.get());
auto classA = std::make_unique<Class>(Class::Kind::Class, "ClassA", 8);
classA->parents.push_back(Parent(aliasB.get(), 0));
classA->members.push_back(Member(myint.get(), "a", 4));
test({*classA}, R"(
[0] Class: ClassA (size: 8)
Member: b (offset: 0)
Primitive: int32_t
Member: a (offset: 4)
Primitive: int32_t
)");
}
TEST(FlattenerTest, Pointer) {
// Original:
// class B { int b; };
// class A : B { int a; };
// class C { A *a; };
auto myint = std::make_unique<Primitive>(Primitive::Kind::Int32);
auto classB = std::make_unique<Class>(Class::Kind::Class, "ClassB", 4);
classB->members.push_back(Member(myint.get(), "b", 0));
auto classA = std::make_unique<Class>(Class::Kind::Class, "ClassA", 8);
classA->parents.push_back(Parent(classB.get(), 0));
classA->members.push_back(Member(myint.get(), "a", 4));
auto ptrA = std::make_unique<Pointer>(classA.get());
auto classC = std::make_unique<Class>(Class::Kind::Class, "ClassC", 8);
classC->members.push_back(Member(ptrA.get(), "a", 0));
test({*classC}, R"(
[0] Class: ClassC (size: 8)
Member: a (offset: 0)
[1] Pointer
[2] Class: ClassA (size: 8)
Member: b (offset: 0)
Primitive: int32_t
Member: a (offset: 4)
Primitive: int32_t
)");
}
TEST(FlattenerTest, PointerCycle) {
// Original:
// class B { A* a };
// class A { B b; };
//
// Flattened:
// No change
auto classA = std::make_unique<Class>(Class::Kind::Class, "ClassA", 69);
auto classB = std::make_unique<Class>(Class::Kind::Class, "ClassB", 69);
auto ptrA = std::make_unique<Pointer>(classA.get());
classA->members.push_back(Member(classB.get(), "b", 0));
classB->members.push_back(Member(ptrA.get(), "a", 0));
test({*classA, *classB}, R"(
[0] Class: ClassA (size: 69)
Member: b (offset: 0)
[1] Class: ClassB (size: 69)
Member: a (offset: 0)
[2] Pointer
[0]
[1]
)");
}
TEST(FlattenerTest, Alignment) {
// Original:
// class alignas(16) C { int c; };
// class B { alignas(8) int b; };
// class A : B, C { int a; };
auto myint = std::make_unique<Primitive>(Primitive::Kind::Int32);
auto classA = std::make_unique<Class>(Class::Kind::Class, "ClassA", 12);
auto classB = std::make_unique<Class>(Class::Kind::Class, "ClassB", 4);
auto classC = std::make_unique<Class>(Class::Kind::Class, "ClassC", 4);
classC->setAlign(16);
classC->members.push_back(Member(myint.get(), "c", 0));
classB->members.push_back(Member(myint.get(), "b", 0, 8));
classA->parents.push_back(Parent(classB.get(), 0));
classA->parents.push_back(Parent(classC.get(), 4));
classA->members.push_back(Member(myint.get(), "a", 8));
test({*classA}, R"(
[0] Class: ClassA (size: 12)
Member: b (offset: 0, align: 8)
Primitive: int32_t
Member: c (offset: 4, align: 16)
Primitive: int32_t
Member: a (offset: 8)
Primitive: int32_t
)");
}
TEST(FlattenerTest, Functions) {
// Original:
// class C { void funcC(); };
// class B : C { void funcB(); };
// class A : B { void funcA(); };
auto classA = std::make_unique<Class>(Class::Kind::Class, "ClassA", 0);
auto classB = std::make_unique<Class>(Class::Kind::Class, "ClassB", 0);
auto classC = std::make_unique<Class>(Class::Kind::Class, "ClassC", 0);
classA->parents.push_back(Parent(classB.get(), 0));
classB->parents.push_back(Parent(classC.get(), 0));
classA->functions.push_back(Function{"funcA"});
classB->functions.push_back(Function{"funcB"});
classC->functions.push_back(Function{"funcC"});
test({*classA}, R"(
[0] Class: ClassA (size: 0)
Function: funcA
Function: funcB
Function: funcC
)");
}
TEST(FlattenerTest, Children) {
// Original:
// class C { int c; };
// class B { int b; };
// class A : B, C { };
auto myint = std::make_unique<Primitive>(Primitive::Kind::Int32);
auto classA = std::make_unique<Class>(Class::Kind::Class, "ClassA", 8);
auto classB = std::make_unique<Class>(Class::Kind::Class, "ClassB", 4);
auto classC = std::make_unique<Class>(Class::Kind::Class, "ClassC", 4);
classC->members.push_back(Member(myint.get(), "c", 0));
classB->members.push_back(Member(myint.get(), "b", 0));
classA->parents.push_back(Parent(classB.get(), 0));
classA->parents.push_back(Parent(classC.get(), 4));
classB->children.push_back(*classA);
classC->children.push_back(*classA);
test({*classB}, R"(
[0] Class: ClassB (size: 4)
Member: b (offset: 0)
Primitive: int32_t
Child:
[1] Class: ClassA (size: 8)
Member: b (offset: 0)
Primitive: int32_t
Member: c (offset: 4)
Primitive: int32_t
)");
}
TEST(FlattenerTest, ChildrenTwoDeep) {
// Original:
// class D { int d; };
// class C { int c; };
// class B : D { int b; };
// class A : B, C { int a; };
auto myint = std::make_unique<Primitive>(Primitive::Kind::Int32);
auto classA = std::make_unique<Class>(Class::Kind::Class, "ClassA", 16);
auto classB = std::make_unique<Class>(Class::Kind::Class, "ClassB", 8);
auto classC = std::make_unique<Class>(Class::Kind::Class, "ClassC", 4);
auto classD = std::make_unique<Class>(Class::Kind::Class, "ClassD", 4);
classD->members.push_back(Member(myint.get(), "d", 0));
classC->members.push_back(Member(myint.get(), "c", 0));
classB->parents.push_back(Parent(classD.get(), 0));
classB->members.push_back(Member(myint.get(), "b", 4));
classA->parents.push_back(Parent(classB.get(), 0));
classA->parents.push_back(Parent(classC.get(), 8));
classA->members.push_back(Member(myint.get(), "a", 12));
classD->children.push_back(*classB);
classB->children.push_back(*classA);
classC->children.push_back(*classA);
test({*classD}, R"(
[0] Class: ClassD (size: 4)
Member: d (offset: 0)
Primitive: int32_t
Child:
[1] Class: ClassB (size: 8)
Member: d (offset: 0)
Primitive: int32_t
Member: b (offset: 4)
Primitive: int32_t
Child:
[2] Class: ClassA (size: 16)
Member: d (offset: 0)
Primitive: int32_t
Member: b (offset: 4)
Primitive: int32_t
Member: c (offset: 8)
Primitive: int32_t
Member: a (offset: 12)
Primitive: int32_t
)");
}

267
test/test_name_gen.cpp Normal file
View File

@ -0,0 +1,267 @@
#include <gtest/gtest.h>
#include "oi/ContainerInfo.h"
#include "oi/type_graph/NameGen.h"
#include "oi/type_graph/Types.h"
using namespace type_graph;
Container getVector() {
ContainerInfo info{"std::vector", SEQ_TYPE, "vector"};
return Container{info, 24};
}
TEST(NameGenTest, ClassParams) {
auto myparam1 = std::make_unique<Class>(Class::Kind::Struct, "MyParam", 13);
auto myparam2 = std::make_unique<Class>(Class::Kind::Struct, "MyParam", 13);
auto myclass = std::make_unique<Class>(Class::Kind::Struct,
"MyClass<MyParam, MyParam>", 13);
myclass->templateParams.push_back(myparam1.get());
myclass->templateParams.push_back(myparam2.get());
NameGen nameGen;
nameGen.generateNames({*myclass});
EXPECT_EQ(myclass->name(), "MyClass_0");
EXPECT_EQ(myparam1->name(), "MyParam_1");
EXPECT_EQ(myparam2->name(), "MyParam_2");
}
TEST(NameGenTest, ClassContainerParam) {
auto myint = std::make_unique<Primitive>(Primitive::Kind::Int32);
auto myparam = getVector();
myparam.templateParams.push_back(myint.get());
auto myclass = std::make_unique<Class>(Class::Kind::Struct, "MyClass", 13);
myclass->templateParams.push_back(&myparam);
NameGen nameGen;
nameGen.generateNames({*myclass});
EXPECT_EQ(myclass->name(), "MyClass_0");
EXPECT_EQ(myparam.name(), "std::vector<int32_t>");
}
TEST(NameGenTest, ClassParents) {
auto myparent1 = std::make_unique<Class>(Class::Kind::Struct, "MyParent", 13);
auto myparent2 = std::make_unique<Class>(Class::Kind::Struct, "MyParent", 13);
auto myclass = std::make_unique<Class>(Class::Kind::Struct, "MyClass", 13);
myclass->parents.push_back(Parent{myparent1.get(), 0});
myclass->parents.push_back(Parent{myparent2.get(), 0});
NameGen nameGen;
nameGen.generateNames({*myclass});
EXPECT_EQ(myclass->name(), "MyClass_0");
EXPECT_EQ(myparent1->name(), "MyParent_1");
EXPECT_EQ(myparent2->name(), "MyParent_2");
}
TEST(NameGenTest, ClassMembers) {
auto mymember1 = std::make_unique<Class>(Class::Kind::Struct, "MyMember", 13);
auto mymember2 = std::make_unique<Class>(Class::Kind::Struct, "MyMember", 13);
auto myclass = std::make_unique<Class>(Class::Kind::Struct, "MyClass", 13);
// A class may end up with members sharing a name after flattening
myclass->members.push_back(Member{mymember1.get(), "mem", 0});
myclass->members.push_back(Member{mymember2.get(), "mem", 0});
NameGen nameGen;
nameGen.generateNames({*myclass});
EXPECT_EQ(myclass->name(), "MyClass_0");
EXPECT_EQ(myclass->members[0].name, "mem_0");
EXPECT_EQ(myclass->members[1].name, "mem_1");
EXPECT_EQ(mymember1->name(), "MyMember_1");
EXPECT_EQ(mymember2->name(), "MyMember_2");
}
TEST(NameGenTest, ClassChildren) {
auto mychild1 = std::make_unique<Class>(Class::Kind::Struct, "MyChild", 13);
auto mychild2 = std::make_unique<Class>(Class::Kind::Struct, "MyChild", 13);
auto myclass = std::make_unique<Class>(Class::Kind::Struct, "MyClass", 13);
myclass->children.push_back(*mychild1);
myclass->children.push_back(*mychild2);
NameGen nameGen;
nameGen.generateNames({*myclass});
EXPECT_EQ(myclass->name(), "MyClass_0");
EXPECT_EQ(mychild1->name(), "MyChild_1");
EXPECT_EQ(mychild2->name(), "MyChild_2");
}
TEST(NameGenTest, ContainerParams) {
auto myparam1 = std::make_unique<Class>(Class::Kind::Struct, "MyParam", 13);
auto myparam2 = std::make_unique<Class>(Class::Kind::Struct, "MyParam", 13);
auto mycontainer = getVector();
mycontainer.templateParams.push_back(myparam1.get());
mycontainer.templateParams.push_back(myparam2.get());
NameGen nameGen;
nameGen.generateNames({mycontainer});
EXPECT_EQ(myparam1->name(), "MyParam_0");
EXPECT_EQ(myparam2->name(), "MyParam_1");
EXPECT_EQ(mycontainer.name(), "std::vector<MyParam_0, MyParam_1>");
}
TEST(NameGenTest, ContainerParamsDuplicates) {
auto myparam = std::make_unique<Class>(Class::Kind::Struct, "MyParam", 13);
auto mycontainer = getVector();
mycontainer.templateParams.push_back(myparam.get());
mycontainer.templateParams.push_back(myparam.get());
NameGen nameGen;
nameGen.generateNames({mycontainer});
EXPECT_EQ(myparam->name(), "MyParam_0");
EXPECT_EQ(mycontainer.name(), "std::vector<MyParam_0, MyParam_0>");
}
TEST(NameGenTest, ContainerParamsDuplicatesDeep) {
auto myparam = std::make_unique<Class>(Class::Kind::Struct, "MyParam", 13);
auto mycontainer1 = getVector();
mycontainer1.templateParams.push_back(myparam.get());
auto mycontainer2 = getVector();
mycontainer2.templateParams.push_back(myparam.get());
mycontainer2.templateParams.push_back(&mycontainer1);
NameGen nameGen;
nameGen.generateNames({mycontainer2});
EXPECT_EQ(myparam->name(), "MyParam_0");
EXPECT_EQ(mycontainer1.name(), "std::vector<MyParam_0>");
EXPECT_EQ(mycontainer2.name(),
"std::vector<MyParam_0, std::vector<MyParam_0>>");
}
TEST(NameGenTest, ContainerParamsDuplicatesAcrossContainers) {
auto myparam1 = std::make_unique<Class>(Class::Kind::Struct, "MyParam", 13);
auto myparam2 = std::make_unique<Class>(Class::Kind::Struct, "MyParam", 13);
auto myparam3 = std::make_unique<Class>(Class::Kind::Struct, "MyParam", 13);
auto mycontainer1 = getVector();
mycontainer1.templateParams.push_back(myparam1.get());
mycontainer1.templateParams.push_back(myparam2.get());
auto mycontainer2 = getVector();
mycontainer2.templateParams.push_back(myparam2.get());
mycontainer2.templateParams.push_back(myparam3.get());
NameGen nameGen;
nameGen.generateNames({mycontainer1, mycontainer2});
EXPECT_EQ(myparam1->name(), "MyParam_0");
EXPECT_EQ(myparam2->name(), "MyParam_1");
EXPECT_EQ(myparam3->name(), "MyParam_2");
EXPECT_EQ(mycontainer1.name(), "std::vector<MyParam_0, MyParam_1>");
EXPECT_EQ(mycontainer2.name(), "std::vector<MyParam_1, MyParam_2>");
}
TEST(NameGenTest, Array) {
auto myparam1 = std::make_unique<Class>(Class::Kind::Struct, "MyParam", 13);
auto myparam2 = std::make_unique<Class>(Class::Kind::Struct, "MyParam", 13);
auto mycontainer = getVector();
mycontainer.templateParams.push_back(myparam1.get());
mycontainer.templateParams.push_back(myparam2.get());
auto myarray = std::make_unique<Array>(&mycontainer, 5);
NameGen nameGen;
nameGen.generateNames({*myarray});
EXPECT_EQ(myparam1->name(), "MyParam_0");
EXPECT_EQ(myparam2->name(), "MyParam_1");
EXPECT_EQ(mycontainer.name(), "std::vector<MyParam_0, MyParam_1>");
EXPECT_EQ(myarray->name(), "OIArray<std::vector<MyParam_0, MyParam_1>, 5>");
}
TEST(NameGenTest, Typedef) {
auto myparam1 = std::make_unique<Class>(Class::Kind::Struct, "MyParam", 13);
auto myparam2 = std::make_unique<Class>(Class::Kind::Struct, "MyParam", 13);
auto mycontainer = getVector();
mycontainer.templateParams.push_back(myparam1.get());
mycontainer.templateParams.push_back(myparam2.get());
auto mytypedef = std::make_unique<Typedef>("MyTypedef", &mycontainer);
NameGen nameGen;
nameGen.generateNames({*mytypedef});
EXPECT_EQ(myparam1->name(), "MyParam_0");
EXPECT_EQ(myparam2->name(), "MyParam_1");
EXPECT_EQ(mycontainer.name(), "std::vector<MyParam_0, MyParam_1>");
EXPECT_EQ(mytypedef->name(), "MyTypedef");
}
TEST(NameGenTest, Pointer) {
auto myparam1 = std::make_unique<Class>(Class::Kind::Struct, "MyParam", 13);
auto myparam2 = std::make_unique<Class>(Class::Kind::Struct, "MyParam", 13);
auto mycontainer = getVector();
mycontainer.templateParams.push_back(myparam1.get());
mycontainer.templateParams.push_back(myparam2.get());
auto mypointer = std::make_unique<Pointer>(&mycontainer);
NameGen nameGen;
nameGen.generateNames({*mypointer});
EXPECT_EQ(myparam1->name(), "MyParam_0");
EXPECT_EQ(myparam2->name(), "MyParam_1");
EXPECT_EQ(mycontainer.name(), "std::vector<MyParam_0, MyParam_1>");
EXPECT_EQ(mypointer->name(), "std::vector<MyParam_0, MyParam_1>*");
}
TEST(NameGenTest, DummyAllocator) {
auto myparam1 = std::make_unique<Class>(Class::Kind::Struct, "MyParam", 13);
auto myparam2 = std::make_unique<Class>(Class::Kind::Struct, "MyParam", 13);
auto mycontainer = getVector();
mycontainer.templateParams.push_back(myparam1.get());
mycontainer.templateParams.push_back(myparam2.get());
auto myalloc = std::make_unique<DummyAllocator>(mycontainer, 0, 0);
NameGen nameGen;
nameGen.generateNames({*myalloc});
EXPECT_EQ(myparam1->name(), "MyParam_0");
EXPECT_EQ(myparam2->name(), "MyParam_1");
EXPECT_EQ(mycontainer.name(), "std::vector<MyParam_0, MyParam_1>");
EXPECT_EQ(myalloc->name(),
"std::allocator<std::vector<MyParam_0, MyParam_1>>");
}
TEST(NameGenTest, Cycle) {
auto classA = std::make_unique<Class>(Class::Kind::Class, "ClassA", 69);
auto classB = std::make_unique<Class>(Class::Kind::Class, "ClassB", 69);
auto ptrA = std::make_unique<Pointer>(classA.get());
classA->members.push_back(Member(classB.get(), "b", 0));
classB->members.push_back(Member(ptrA.get(), "a", 0));
NameGen nameGen;
nameGen.generateNames({*classA});
EXPECT_EQ(classA->name(), "ClassA_0");
EXPECT_EQ(classB->name(), "ClassB_1");
}
TEST(NameGenTest, ContainerCycle) {
auto container = getVector();
auto myclass = std::make_unique<Class>(Class::Kind::Class, "MyClass", 69);
myclass->members.push_back(Member(&container, "c", 0));
container.templateParams.push_back(TemplateParam(myclass.get()));
NameGen nameGen;
nameGen.generateNames({*myclass});
EXPECT_EQ(myclass->name(), "MyClass_0");
EXPECT_EQ(container.name(), "std::vector<MyClass_0>");
}

View File

@ -0,0 +1,68 @@
#include <gtest/gtest.h>
#include "oi/type_graph/Printer.h"
#include "oi/type_graph/RemoveTopLevelPointer.h"
#include "oi/type_graph/Types.h"
using namespace type_graph;
namespace {
void test(std::vector<std::reference_wrapper<Type>> types,
std::string_view expected) {
RemoveTopLevelPointer pass;
pass.removeTopLevelPointers(types);
std::stringstream out;
Printer printer(out);
for (const auto& type : types) {
printer.print(type);
}
expected.remove_prefix(1); // Remove initial '\n'
EXPECT_EQ(expected, out.str());
}
} // namespace
TEST(RemoveTopLevelPointerTest, TopLevelPointerRemoved) {
auto myint = std::make_unique<Primitive>(Primitive::Kind::Int32);
auto myclass = std::make_unique<Class>(Class::Kind::Class, "MyClass", 4);
myclass->members.push_back(Member(myint.get(), "n", 0));
auto ptrA = std::make_unique<Pointer>(myclass.get());
test({*ptrA}, R"(
[0] Class: MyClass (size: 4)
Member: n (offset: 0)
Primitive: int32_t
)");
}
TEST(RemoveTopLevelPointerTest, TopLevelClassUntouched) {
auto myint = std::make_unique<Primitive>(Primitive::Kind::Int32);
auto myclass = std::make_unique<Class>(Class::Kind::Class, "MyClass", 4);
myclass->members.push_back(Member(myint.get(), "n", 0));
test({*myclass}, R"(
[0] Class: MyClass (size: 4)
Member: n (offset: 0)
Primitive: int32_t
)");
}
TEST(RemoveTopLevelPointerTest, IntermediatePointerUntouched) {
auto myint = std::make_unique<Primitive>(Primitive::Kind::Int32);
auto ptrInt = std::make_unique<Pointer>(myint.get());
auto myclass = std::make_unique<Class>(Class::Kind::Class, "MyClass", 4);
myclass->members.push_back(Member(ptrInt.get(), "n", 0));
test({*myclass}, R"(
[0] Class: MyClass (size: 4)
Member: n (offset: 0)
[1] Pointer
Primitive: int32_t
)");
}

239
test/test_topo_sorter.cpp Normal file
View File

@ -0,0 +1,239 @@
#include <gtest/gtest.h>
#include <boost/algorithm/string.hpp>
#include "oi/type_graph/TopoSorter.h"
#include "oi/type_graph/Types.h"
using namespace type_graph;
Container getVector(); // TODO put in a header
template <typename T>
using ref = std::reference_wrapper<T>;
void test(const std::vector<ref<Type>> input, std::string expected) {
TopoSorter topo;
topo.sort(input);
std::string output;
for (const Type& type : topo.sortedTypes()) {
output += type.name();
output.push_back('\n');
}
boost::trim(expected);
boost::trim(output);
EXPECT_EQ(expected, output);
}
TEST(TopoSorterTest, SingleType) {
auto myenum = std::make_unique<Enum>("MyEnum", 4);
test({*myenum}, "MyEnum");
}
TEST(TopoSorterTest, UnrelatedTypes) {
auto mystruct = std::make_unique<Class>(Class::Kind::Struct, "MyStruct", 13);
auto myenum = std::make_unique<Enum>("MyEnum", 4);
auto myclass = std::make_unique<Class>(Class::Kind::Class, "MyClass", 69);
// Try the same input in a few different orders and ensure they output order
// matches the input order
test({*mystruct, *myenum, *myclass}, R"(
MyStruct
MyEnum
MyClass
)");
test({*myenum, *mystruct, *myclass}, R"(
MyEnum
MyStruct
MyClass
)");
test({*myclass, *myenum, *mystruct}, R"(
MyClass
MyEnum
MyStruct
)");
}
TEST(TopoSorterTest, ClassMembers) {
auto mystruct = std::make_unique<Class>(Class::Kind::Struct, "MyStruct", 13);
auto myenum = std::make_unique<Enum>("MyEnum", 4);
auto myclass = std::make_unique<Class>(Class::Kind::Class, "MyClass", 69);
myclass->members.push_back(Member(mystruct.get(), "n", 0));
myclass->members.push_back(Member(myenum.get(), "e", 4));
test({*myclass}, R"(
MyStruct
MyEnum
MyClass
)");
}
TEST(TopoSorterTest, Parents) {
auto myparent = std::make_unique<Class>(Class::Kind::Struct, "MyParent", 13);
auto myclass = std::make_unique<Class>(Class::Kind::Class, "MyClass", 69);
myclass->parents.push_back(Parent(myparent.get(), 0));
test({*myclass}, R"(
MyParent
MyClass
)");
}
TEST(TopoSorterTest, TemplateParams) {
auto myparam = std::make_unique<Class>(Class::Kind::Struct, "MyParam", 13);
auto myclass = std::make_unique<Class>(Class::Kind::Class, "MyClass", 69);
myclass->templateParams.push_back(TemplateParam(myparam.get()));
test({*myclass}, R"(
MyParam
MyClass
)");
}
TEST(TopoSorterTest, Children) {
auto mymember = std::make_unique<Class>(Class::Kind::Struct, "MyMember", 13);
auto mychild = std::make_unique<Class>(Class::Kind::Struct, "MyChild", 13);
mychild->members.push_back(Member(mymember.get(), "mymember", 0));
auto myclass = std::make_unique<Class>(Class::Kind::Class, "MyClass", 69);
mychild->parents.push_back(Parent(myclass.get(), 0));
myclass->children.push_back(*mychild);
std::vector<std::vector<ref<Type>>> inputs = {
{*myclass},
{*mychild},
};
// Same as for pointer cycles, outputs must be in the same order no matter
// which type we start the sort on.
for (const auto& input : inputs) {
test(input, R"(
MyClass
MyMember
MyChild
)");
}
}
TEST(TopoSorterTest, ChildrenCycle) {
// class MyParent {};
// class ClassA {
// MyParent p;
// };
// class MyChild : MyParent {
// A a;
// };
auto myparent = std::make_unique<Class>(Class::Kind::Class, "MyParent", 69);
auto classA = std::make_unique<Class>(Class::Kind::Struct, "ClassA", 5);
auto mychild = std::make_unique<Class>(Class::Kind::Struct, "MyChild", 13);
mychild->parents.push_back(Parent(myparent.get(), 0));
myparent->children.push_back(*mychild);
mychild->members.push_back(Member(classA.get(), "a", 0));
classA->members.push_back(Member(myparent.get(), "p", 0));
std::vector<std::vector<ref<Type>>> inputs = {
{*myparent},
{*classA},
{*mychild},
};
// Same as for pointer cycles, outputs must be in the same order no matter
// which type we start the sort on.
for (const auto& input : inputs) {
test(input, R"(
MyParent
ClassA
MyChild
)");
}
}
TEST(TopoSorterTest, Containers) {
auto myparam = std::make_unique<Class>(Class::Kind::Struct, "MyParam", 13);
auto mycontainer = getVector();
mycontainer.templateParams.push_back((myparam.get()));
test({mycontainer}, "MyParam\nstd::vector");
}
TEST(TopoSorterTest, Arrays) {
auto myclass = std::make_unique<Class>(Class::Kind::Class, "MyClass", 69);
auto myarray = std::make_unique<Array>(myclass.get(), 10);
test({*myarray}, "MyClass\n");
}
TEST(TopoSorterTest, Typedef) {
auto classA = std::make_unique<Class>(Class::Kind::Class, "ClassA", 8);
auto aliasA = std::make_unique<Typedef>("aliasA", classA.get());
test({*aliasA}, R"(
ClassA
aliasA
)");
}
TEST(TopoSorterTest, Pointers) {
// Pointers do not require pointee types to be defined first
auto myclass = std::make_unique<Class>(Class::Kind::Class, "MyClass", 69);
auto mypointer = std::make_unique<Pointer>(myclass.get());
test({*mypointer}, "MyClass");
}
TEST(TopoSorterTest, PointerCycle) {
auto classA = std::make_unique<Class>(Class::Kind::Class, "ClassA", 69);
auto classB = std::make_unique<Class>(Class::Kind::Class, "ClassB", 69);
auto ptrA = std::make_unique<Pointer>(classA.get());
classA->members.push_back(Member(classB.get(), "b", 0));
classB->members.push_back(Member(ptrA.get(), "a", 0));
std::vector<std::vector<ref<Type>>> inputs = {
{*classA},
{*classB},
{*ptrA},
};
// No matter which node we start the topological sort with, we must always get
// the same sorted order for ClassA and ClassB.
for (const auto& input : inputs) {
test(input, R"(
ClassB
ClassA
)");
}
}
TEST(TopoSorterTest, TwoDeep) {
auto myunion = std::make_unique<Class>(Class::Kind::Union, "MyUnion", 7);
auto mystruct = std::make_unique<Class>(Class::Kind::Struct, "MyStruct", 13);
auto myclass = std::make_unique<Class>(Class::Kind::Class, "MyClass", 69);
myclass->members.push_back(Member(mystruct.get(), "mystruct", 0));
mystruct->members.push_back(Member(myunion.get(), "myunion", 0));
test({*myclass}, R"(
MyUnion
MyStruct
MyClass
)");
}
TEST(TopoSorterTest, MultiplePaths) {
auto myunion = std::make_unique<Class>(Class::Kind::Union, "MyUnion", 7);
auto mystruct = std::make_unique<Class>(Class::Kind::Struct, "MyStruct", 13);
auto myclass = std::make_unique<Class>(Class::Kind::Class, "MyClass", 69);
myclass->members.push_back(Member(mystruct.get(), "mystruct", 0));
myclass->members.push_back(Member(myunion.get(), "myunion1", 0));
mystruct->members.push_back(Member(myunion.get(), "myunion2", 0));
test({*myclass}, R"(
MyUnion
MyStruct
MyClass
)");
}

View File

@ -0,0 +1,7 @@
#include <gtest/gtest.h>
#include "oi/type_graph/TypeIdentifier.h"
using namespace type_graph;
// TODO tests!!!