mirror of
https://github.com/JakeHillion/object-introspection.git
synced 2024-11-12 21:56:54 +00:00
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:
parent
a0418b6881
commit
bd919ae4e4
@ -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)
|
||||
|
212
oi/type_graph/AddChildren.cpp
Normal file
212
oi/type_graph/AddChildren.cpp
Normal 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
|
68
oi/type_graph/AddChildren.h
Normal file
68
oi/type_graph/AddChildren.h
Normal 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
|
101
oi/type_graph/AddPadding.cpp
Normal file
101
oi/type_graph/AddPadding.cpp
Normal 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
|
52
oi/type_graph/AddPadding.h
Normal file
52
oi/type_graph/AddPadding.h
Normal 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
|
74
oi/type_graph/AlignmentCalc.cpp
Normal file
74
oi/type_graph/AlignmentCalc.cpp
Normal 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
|
47
oi/type_graph/AlignmentCalc.h
Normal file
47
oi/type_graph/AlignmentCalc.h
Normal 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
|
21
oi/type_graph/CMakeLists.txt
Normal file
21
oi/type_graph/CMakeLists.txt
Normal 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
|
||||
)
|
442
oi/type_graph/DrgnParser.cpp
Normal file
442
oi/type_graph/DrgnParser.cpp
Normal 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
|
99
oi/type_graph/DrgnParser.h
Normal file
99
oi/type_graph/DrgnParser.h
Normal 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
162
oi/type_graph/Flattener.cpp
Normal 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
49
oi/type_graph/Flattener.h
Normal 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
109
oi/type_graph/NameGen.cpp
Normal 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
46
oi/type_graph/NameGen.h
Normal 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
|
74
oi/type_graph/PassManager.cpp
Normal file
74
oi/type_graph/PassManager.cpp
Normal 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
|
62
oi/type_graph/PassManager.h
Normal file
62
oi/type_graph/PassManager.h
Normal 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
203
oi/type_graph/Printer.cpp
Normal 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
61
oi/type_graph/Printer.h
Normal 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
|
45
oi/type_graph/RemoveTopLevelPointer.cpp
Normal file
45
oi/type_graph/RemoveTopLevelPointer.cpp
Normal 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
|
44
oi/type_graph/RemoveTopLevelPointer.h
Normal file
44
oi/type_graph/RemoveTopLevelPointer.h
Normal 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
|
98
oi/type_graph/TopoSorter.cpp
Normal file
98
oi/type_graph/TopoSorter.cpp
Normal 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
|
54
oi/type_graph/TopoSorter.h
Normal file
54
oi/type_graph/TopoSorter.h
Normal 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
62
oi/type_graph/TypeGraph.h
Normal 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
|
106
oi/type_graph/TypeIdentifier.cpp
Normal file
106
oi/type_graph/TypeIdentifier.cpp
Normal 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
|
49
oi/type_graph/TypeIdentifier.h
Normal file
49
oi/type_graph/TypeIdentifier.h
Normal 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
125
oi/type_graph/Types.cpp
Normal 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
424
oi/type_graph/Types.h
Normal 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
135
oi/type_graph/Visitor.h
Normal 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
|
@ -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
|
||||
|
@ -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
86
test/test_add_padding.cpp
Normal 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
|
92
test/test_alignment_calc.cpp
Normal file
92
test/test_alignment_calc.cpp
Normal 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
499
test/test_drgn_parser.cpp
Normal 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
689
test/test_flattener.cpp
Normal 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
267
test/test_name_gen.cpp
Normal 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>");
|
||||
}
|
68
test/test_remove_top_level_pointer.cpp
Normal file
68
test/test_remove_top_level_pointer.cpp
Normal 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
239
test/test_topo_sorter.cpp
Normal 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
|
||||
)");
|
||||
}
|
7
test/test_type_identifier.cpp
Normal file
7
test/test_type_identifier.cpp
Normal file
@ -0,0 +1,7 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "oi/type_graph/TypeIdentifier.h"
|
||||
|
||||
using namespace type_graph;
|
||||
|
||||
// TODO tests!!!
|
Loading…
Reference in New Issue
Block a user