#include "ClangSession.h"
#include <cassert>
#include <deque>
#include <filesystem>
#include <iostream>
#include <optional>
#include <clang-c/Index.h>
#include <clang/AST/DeclBase.h>
#include <clang/AST/DeclObjC.h>
#include <clang/Basic/Version.h>
#include "FatalException.h"
#include "InputFile.h"
#include "Logging.h"
#include "Strings.h"
#include "Universe.h"
static bool operator==(const CXType& lhs, const CXType& rhs) noexcept
{
return !!clang_equalTypes(lhs, rhs);
}
static bool operator==(const CXCursor& lhs, const CXCursor& rhs) noexcept
{
return !!clang_equalCursors(lhs, rhs);
}
namespace objcgen {
struct CXTypeHash {
size_t operator()(const CXType& x) const noexcept
{
constexpr unsigned hashBase = 31;
return std::hash<void*>()(x.data[0]) * hashBase + std::hash<void*>()(x.data[1]);
}
};
struct CXCursorHash {
size_t operator()(const CXCursor& x) const noexcept
{
return clang_hashCursor(x);
}
};
class ClangVisitor {
static CXChildVisitResult visit(CXCursor cursor, CXCursor parent, void* data)
{
return static_cast<ClangVisitor*>(data)->visit_impl(cursor, parent);
}
public:
virtual ~ClangVisitor() = default;
bool visit(CXCursor cursor)
{
return clang_visitChildren(cursor, visit, this) != 0;
}
protected:
virtual CXChildVisitResult visit_impl(CXCursor cursor, CXCursor parent) = 0;
};
class SourceScanner final : public ClangVisitor {
TypeDeclarationSymbol* last_objc_type_ = nullptr;
std::deque<Symbol*> current_;
std::unordered_map<CXType, NamedTypeSymbol*, CXTypeHash> anonymous_;
std::unordered_map<std::string, std::uint64_t> anonymous_count_;
std::unordered_set<CXCursor, CXCursorHash> visited_;
size_t func_parameter_index_ = 0;
[[nodiscard]] NamedTypeSymbol* current_type() const
{
for (auto it = current_.crbegin(); it != current_.crend(); ++it) {
if (auto* symbol = dynamic_cast<NamedTypeSymbol*>(*it)) {
return symbol;
}
assert(dynamic_cast<NonTypeSymbol*>(*it));
}
return nullptr;
}
[[nodiscard]] NonTypeSymbol* current_non_type() const
{
for (auto it = current_.crbegin(); it != current_.crend(); ++it) {
if (auto* symbol = dynamic_cast<NonTypeSymbol*>(*it)) {
return symbol;
}
assert(dynamic_cast<NamedTypeSymbol*>(*it));
}
return nullptr;
}
[[nodiscard]] TypeDeclarationSymbol* current_type_declaration() const
{
auto* named_type = current_type();
assert(named_type);
auto* type_declaration = dynamic_cast<TypeDeclarationSymbol*>(named_type);
assert(type_declaration);
return type_declaration;
}
[[nodiscard]] std::size_t level() const
{
return current_.size();
}
[[nodiscard]] bool is_on_top_level() const
{
return current_.empty();
}
[[nodiscard]] bool current_top_is_type() const
{
return !current_.empty() && dynamic_cast<NamedTypeSymbol*>(current_.back());
}
[[nodiscard]] bool current_top_is_non_type() const
{
return !current_.empty() && dynamic_cast<NonTypeSymbol*>(current_.back());
}
[[nodiscard]] bool current_top_is_property() const noexcept
{
if (current_.empty()) {
return false;
}
const auto* top = dynamic_cast<const NonTypeSymbol*>(current_.back());
return top && top->kind() == NonTypeSymbol::Kind::Property;
}
Symbol* push_current(NamedTypeSymbol* symbol, const bool is_objc)
{
assert(symbol);
assert(is_objc == (symbol->is(Kind::Interface) || symbol->is(Kind::Protocol) || symbol->is(Kind::Category)));
current_.push_back(symbol);
if (is_objc) {
last_objc_type_ = current_type_declaration();
assert(last_objc_type_);
}
return symbol;
}
Symbol* push_current(NonTypeSymbol* symbol)
{
assert(symbol);
current_.push_back(symbol);
return symbol;
}
Symbol* push_current(NonTypeSymbol& symbol)
{
return push_current(std::addressof(symbol));
}
auto* push_current(EnumConstantSymbol& constant)
{
return current_.emplace_back(&constant);
}
void pop_current([[maybe_unused]] Symbol* symbol)
{
assert(symbol);
assert(!current_.empty());
assert(current_.back() == symbol);
current_.pop_back();
}
[[nodiscard]] Symbol* push_top_level_function(const CXCursor& cursor, std::string&& name);
[[nodiscard]] Symbol* push_property(
std::string&& name, std::string&& getter, std::string&& setter, uint16_t modifiers);
[[nodiscard]] Symbol* push_member_method(CXCursor cursor, std::string&& name, uint16_t modifiers);
[[nodiscard]] Symbol* push_constructor(CXCursor cursor, std::string&& name);
[[nodiscard]] NamedTypeSymbol* named_type_symbol(CXType type);
[[nodiscard]] TypeDeclarationSymbol* type_symbol(CXType type);
[[nodiscard]] std::string new_anonymous_name(CXCursor decl);
[[nodiscard]] TypeLikeSymbol* add_type(NamedTypeSymbol::Kind kind, const std::string& name, const CXType& type);
template <class FuncLikeTypeSymbol>
[[nodiscard]] FuncLikeTypeSymbol* create_func_like_type_symbol(const CXType& type);
[[nodiscard]] TypeLikeSymbol* type_like_symbol(CXType type);
public:
explicit SourceScanner() = default;
SourceScanner(const SourceScanner& other) = delete;
SourceScanner(SourceScanner&& other) noexcept = delete;
SourceScanner& operator=(const SourceScanner& other) = delete;
SourceScanner& operator=(SourceScanner&& other) noexcept = delete;
protected:
CXChildVisitResult visit_impl(CXCursor cursor, CXCursor parent) override;
private:
bool visit_parameter_declaration(std::string cursor_name, const CXType& cursor_type);
* Get the generic type declaration the currently processed type parameter
* belongs to.
*/
[[nodiscard]] const TypeDeclarationSymbol& get_owner_generic_type() const;
};
class ClangSessionImpl final : public ClangSession {
CXIndex index_;
SourceScanner scanner_;
public:
ClangSessionImpl()
{
index_ = clang_createIndex(0, 1);
}
~ClangSessionImpl() override;
ClangSessionImpl(const ClangSessionImpl& other) = delete;
ClangSessionImpl(ClangSessionImpl&& other) noexcept = delete;
ClangSessionImpl& operator=(const ClangSessionImpl& other) = delete;
ClangSessionImpl& operator=(ClangSessionImpl&& other) noexcept = delete;
[[nodiscard]] CXIndex index() const
{
return index_;
}
[[nodiscard]] SourceScanner& scanner()
{
return scanner_;
}
private:
void parse_sources(const std::vector<std::string>& files, const std::vector<std::string>& arguments) override;
};
std::unique_ptr<ClangSession> ClangSession::create()
{
return std::make_unique<ClangSessionImpl>();
}
ClangSessionImpl::~ClangSessionImpl()
{
clang_disposeIndex(index());
}
[[nodiscard]] static std::string as_string(CXString string)
{
std::string c_string = clang_getCString(string);
clang_disposeString(string);
return c_string;
}
[[nodiscard]] static bool is_valid(CXCursor cursor)
{
if (clang_Cursor_isNull(cursor))
return false;
if (cursor.kind >= CXCursor_FirstInvalid && cursor.kind <= CXCursor_LastInvalid)
return false;
return true;
}
[[nodiscard]] static bool is_valid(CXType type)
{
return type.kind != CXType_Invalid;
}
[[nodiscard]] static bool is_builtin(CXType type)
{
assert(is_valid(type));
const auto kind = type.kind;
if (kind >= CXType_FirstBuiltin && kind <= CXType_LastBuiltin) {
switch (kind) {
case CXType_ObjCId:
case CXType_ObjCClass:
case CXType_ObjCSel:
return false;
default:
return true;
}
}
return false;
}
[[nodiscard]] static bool is_anonymous(CXCursor cursor)
{
assert(is_valid(cursor));
return !!clang_Cursor_isAnonymous(cursor) || !!clang_Cursor_isAnonymousRecordDecl(cursor);
}
[[nodiscard]] static bool is_canonical(CXCursor cursor)
{
assert(is_valid(cursor));
const auto canonical = clang_getCanonicalCursor(cursor);
assert(is_valid(canonical));
return cursor == canonical;
}
[[nodiscard]] static bool is_defining(CXCursor cursor)
{
assert(is_valid(cursor));
const auto definition = clang_getCursorDefinition(cursor);
if (!is_valid(definition))
return true;
return cursor == definition;
}
[[nodiscard]] static bool is_defining(CXType type, CXCursor cursor)
{
assert(is_valid(type));
assert(is_valid(cursor));
const auto declaration = clang_getTypeDeclaration(type);
assert(is_valid(declaration));
return cursor == declaration;
}
[[nodiscard]] static bool is_null_location(const CXSourceLocation& loc)
{
return clang_equalLocations(loc, clang_getNullLocation());
}
[[nodiscard]] static Location get_location(const CXCursor& decl)
{
assert(is_valid(decl));
auto loc = clang_getCursorLocation(decl);
if (is_null_location(loc)) {
return {};
}
Location location;
CXFile file;
clang_getFileLocation(loc, &file, &location.pos_.line_, &location.pos_.col_, nullptr);
if (!file) {
return {};
}
location.file_ = as_string(clang_getFileName(file));
if (!location.file_.is_absolute()) {
location.file_ = std::filesystem::absolute(location.file_);
}
return location;
}
[[nodiscard]] static std::string declaring_file_name(const CXCursor& decl)
{
assert(is_valid(decl));
auto location = get_location(decl);
assert(location.file_.has_stem());
return location.file_.stem().u8string();
}
static bool set_definition_location(const CXCursor& decl, FileLevelSymbol* symbol)
{
assert(is_valid(decl));
assert(symbol);
auto loc = get_location(decl);
if (loc.is_null()) {
return false;
}
symbol->set_definition_location(loc);
return true;
}
template <class Decl>
[[nodiscard]] const std::enable_if_t<std::is_base_of_v<clang::Decl, Decl>, Decl>& cursor_to_decl(const CXCursor& cursor)
{
const auto* decl = llvm::dyn_cast_or_null<Decl>(static_cast<const clang::Decl*>(cursor.data[0]));
assert(decl);
return *decl;
}
[[nodiscard]] static clang::QualType type_to_qual_type(const CXType& type)
{
return clang::QualType::getFromOpaquePtr(type.data[0]);
}
[[nodiscard]] static PrimitiveTypeCategory get_primitive_category(const CXType& type)
{
if (type.kind == CXType_Bool) {
assert(clang_Type_getSizeOf(type) == 1);
return PrimitiveTypeCategory::Boolean;
}
const auto cpp = type_to_qual_type(type);
if (cpp->isUnsignedIntegerOrEnumerationType()) {
return PrimitiveTypeCategory::UnsignedInteger;
}
if (cpp->isSignedIntegerOrEnumerationType()) {
return PrimitiveTypeCategory::SignedInteger;
}
if (cpp->isFloatingType()) {
return PrimitiveTypeCategory::FloatingPoint;
}
return PrimitiveTypeCategory::Unit;
}
NamedTypeSymbol* SourceScanner::named_type_symbol(CXType type)
{
auto* symbol = type_like_symbol(type);
auto* named = dynamic_cast<NamedTypeSymbol*>(symbol);
assert(named);
auto* original = named->original();
assert(original);
assert(named->name() == original->name());
return original;
}
TypeDeclarationSymbol* SourceScanner::type_symbol(CXType type)
{
auto* symbol = named_type_symbol(type);
auto* result = dynamic_cast<TypeDeclarationSymbol*>(symbol);
assert(result);
return result;
}
template <class FuncLikeTypeSymbol> FuncLikeTypeSymbol* SourceScanner::create_func_like_type_symbol(const CXType& type)
{
assert(type.kind == CXType_FunctionProto || type.kind == CXType_FunctionNoProto);
auto* parameters = new TupleTypeSymbol();
int num_arg_types = clang_getNumArgTypes(type);
assert(num_arg_types >= 0);
for (int i = 0; i < num_arg_types; ++i) {
parameters->add_item(type_like_symbol(clang_getArgType(type, static_cast<unsigned>(i))));
}
auto* return_type = type_like_symbol(clang_getResultType(type));
return new FuncLikeTypeSymbol(parameters, return_type);
}
std::string SourceScanner::new_anonymous_name(CXCursor decl)
{
assert(is_anonymous(decl));
auto file_name = declaring_file_name(decl);
std::uint64_t index = 1;
if (auto&& [item, inserted] = anonymous_count_.insert({file_name, index}); !inserted) {
index = ++item->second;
}
std::string type_name = "__";
type_name += file_name;
type_name += "_";
type_name += std::to_string(index);
return type_name;
}
TypeLikeSymbol* SourceScanner::add_type(const NamedTypeSymbol::Kind kind, const std::string& name, const CXType& type)
{
NamedTypeSymbol* symbol;
switch (kind) {
case NamedTypeSymbol::Kind::TypeDef:
symbol = new TypeAliasSymbol(name);
break;
case NamedTypeSymbol::Kind::Enum:
symbol = new EnumDeclarationSymbol(name);
break;
default:
symbol = new TypeDeclarationSymbol(kind, name);
break;
}
if (const auto decl = clang_getTypeDeclaration(type); decl.kind != CXCursor_NoDeclFound) {
if (is_anonymous(decl)) {
assert(anonymous_.find(type) == anonymous_.end());
anonymous_.emplace(type, symbol);
}
auto has_definition_location = set_definition_location(decl, symbol);
if (!has_definition_location && type.kind == CXType_Typedef && name != "instancetype") {
auto cx_target = clang_getTypedefDeclUnderlyingType(decl);
if (cx_target.kind != CXType_Invalid) {
auto* target = type_like_symbol(cx_target);
assert(target);
return target;
}
}
}
Universe::get().register_type(symbol);
return symbol;
}
static NamedTypeSymbol* protocol_symbol(CXType objc_object_type, unsigned i)
{
assert(objc_object_type.kind == CXType_ObjCObject);
assert(i < clang_Type_getNumObjCProtocolRefs(objc_object_type));
auto protocol_decl = clang_Type_getObjCProtocolDecl(objc_object_type, i);
assert(protocol_decl.kind == CXCursor_ObjCProtocolDecl);
auto protocol_name = as_string(clang_getCursorSpelling(protocol_decl));
auto& universe = Universe::get();
auto* result = universe.type(NamedTypeSymbol::Kind::Protocol, protocol_name);
if (!result) {
result = new TypeDeclarationSymbol(NamedTypeSymbol::Kind::Protocol, protocol_name);
universe.register_type(result);
set_definition_location(protocol_decl, result);
}
return result;
}
struct UndecorateResult {
std::string_view undecorated_type_name;
std::string_view narrowing_protocol_name;
};
* The type parameter name can be specified with a narrowing protocol. Like in
* this sample (`T<NSCopying>` instead of just `T`):
* <pre>
* @interface A<T> : NSObject
* - (void) foo: (T <NSCopying>) x;
* @end
* </pre>
*
* Also the type parameter name can be prefixed with the `const`,
* `__unsafe_unretained`, or `__strong` modifier.
*
* <p> We need a pure name without any "decorations", to make it possible to
* find the parameter in its owner's parameter list. The pure name hardly can
* be obtained with the libclang API. Let us "undecorate" it by ourselves.
*/
static UndecorateResult undecorate_parameter_type_name(const std::string& decorated_type_name)
{
auto without_prefix =
remove_prefix(remove_prefix(remove_prefix(decorated_type_name, "__unsafe_unretained "), "__strong "), "const ");
auto opening_bracket = without_prefix.find('<');
return opening_bracket == std::string_view::npos || without_prefix.back() != '>'
? UndecorateResult{without_prefix, std::string_view()}
: UndecorateResult{without_prefix.substr(0, opening_bracket),
without_prefix.substr(opening_bracket + 1, without_prefix.size() - opening_bracket - 2)};
}
[[nodiscard]] std::string get_type_name(const CXType& type)
{
auto type_name = as_string(clang_getTypeSpelling(type));
#if CLANG_VERSION_MAJOR < 16
remove_prefix_in_place(type_name, "const ");
remove_prefix_in_place(type_name, "volatile ");
if (ends_with(type_name, "*restrict")) {
remove_prefix_in_place(type_name, "restrict");
}
#endif
remove_prefix_in_place(type_name, "__strong ");
remove_prefix_in_place(type_name, "__unsafe_unretained ");
remove_prefix_in_place(type_name, "__autoreleasing ");
return type_name;
}
[[nodiscard]] static TypeLikeSymbol& primitive_type(const CXType& type) noexcept
{
auto size = clang_Type_getSizeOf(type);
auto& universe = Universe::get();
if (size <= 0) {
return universe.unit();
}
auto* type_symbol = universe.primitive_type(get_primitive_category(type), static_cast<size_t>(size));
if (type_symbol) {
return *type_symbol;
}
return *new VArraySymbol(universe.int8(), static_cast<size_t>(size));
}
const TypeDeclarationSymbol& SourceScanner::get_owner_generic_type() const
{
if (!is_on_top_level()) {
const auto* owner_type = current_type_declaration();
assert(owner_type);
switch (owner_type->kind()) {
case NamedTypeSymbol::Kind::Interface:
case NamedTypeSymbol::Kind::Protocol:
case NamedTypeSymbol::Kind::Category:
return *owner_type;
default:
break;
}
}
assert(last_objc_type_);
assert(last_objc_type_->is(NamedTypeSymbol::Kind::Interface) ||
last_objc_type_->is(NamedTypeSymbol::Kind::Protocol) || last_objc_type_->is(NamedTypeSymbol::Kind::Category));
return *last_objc_type_;
}
TypeLikeSymbol* SourceScanner::type_like_symbol(CXType type)
{
assert(type.kind != CXType_Invalid);
#if CLANG_VERSION_MAJOR >= 16
if (clang_isConstQualifiedType(type) || clang_isVolatileQualifiedType(type) ||
clang_isRestrictQualifiedType(type)) {
return type_like_symbol(clang_getUnqualifiedType(type));
}
#endif
switch (type.kind) {
case CXType_ObjCObject: {
auto baseCXType = clang_Type_getObjCObjectBaseType(type);
if (baseCXType.kind == CXType_ObjCId) {
auto* id_type = &Universe::get().id();
auto num_protocols = clang_Type_getNumObjCProtocolRefs(type);
switch (num_protocols) {
case 0:
return id_type;
case 1:
return protocol_symbol(type, 0);
default:
auto* result = new ConstructedTypeSymbol(id_type);
for (decltype(num_protocols) i = 0; i < num_protocols; ++i) {
result->add_parameter(protocol_symbol(type, i));
}
return result;
}
} else {
auto* base_type = named_type_symbol(baseCXType);
auto type_arg_count = clang_Type_getNumObjCTypeArgs(type);
if (type_arg_count == 0) {
return base_type;
}
auto* base_type_decl = dynamic_cast<TypeDeclarationSymbol*>(base_type);
assert(base_type_decl);
auto* result = new ConstructedTypeSymbol(base_type_decl);
for (decltype(type_arg_count) i = 0; i < type_arg_count; ++i) {
auto* arg = type_like_symbol(clang_Type_getObjCTypeArg(type, i));
assert(arg);
result->add_parameter(arg);
}
return result;
}
break;
}
case CXType_ObjCObjectPointer:
return type_like_symbol(clang_getPointeeType(type));
case CXType_Pointer:
return &pointer(*type_like_symbol(clang_getPointeeType(type)));
case CXType_BlockPointer:
return create_func_like_type_symbol<BlockTypeSymbol>(clang_getPointeeType(type));
case CXType_Elaborated:
return type_like_symbol(clang_Type_getNamedType(type));
case CXType_Unexposed: {
auto modified_type = clang_Type_getModifiedType(type);
if (modified_type.kind != CXType_Invalid) {
return type_like_symbol(modified_type);
}
auto type_name = get_type_name(type);
auto* result = Universe::get().type(type_name);
return new UnexposedTypeSymbol(type_name, result ? *result : primitive_type(type));
}
case CXType_Attributed: {
auto modified_type = clang_Type_getModifiedType(type);
assert(modified_type.kind != CXType_Invalid);
return type_like_symbol(modified_type);
}
case CXType_ObjCTypeParam: {
const auto& owner_type = get_owner_generic_type();
auto decorated_type_name = as_string(clang_getTypeSpelling(type));
auto [undecorated_type_name, narrowing_protocol_name] = undecorate_parameter_type_name(decorated_type_name);
const auto parameter_count = owner_type.parameter_count();
for (std::size_t i = 0; i < parameter_count; ++i) {
auto* parameter = owner_type.parameter(i);
assert(parameter);
auto parameter_name = parameter->name();
if (parameter_name == undecorated_type_name) {
if (owner_type.kind() == NamedTypeSymbol::Kind::Category) {
assert(dynamic_cast<const CategoryDeclarationSymbol*>(&owner_type));
const auto* interface = static_cast<const CategoryDeclarationSymbol&>(owner_type).interface();
assert(parameter_count == interface->parameter_count());
parameter = interface->parameter(i);
}
if (narrowing_protocol_name.empty()) {
return parameter;
}
return new NarrowedTypeParameterSymbol(*parameter, std::string(narrowing_protocol_name));
}
}
assert(false && "Unknown type parameter");
return nullptr;
}
case CXType_FunctionProto:
case CXType_FunctionNoProto:
return create_func_like_type_symbol<FuncTypeSymbol>(type);
case CXType_IncompleteArray:
return new VArraySymbol(*type_like_symbol(clang_getArrayElementType(type)), 0);
case CXType_ConstantArray:
return new VArraySymbol(
*type_like_symbol(clang_getArrayElementType(type)), static_cast<size_t>(clang_getArraySize(type)));
case CXType_Atomic: {
auto value_type = clang_Type_getValueType(type);
assert(is_valid(value_type));
return type_like_symbol(value_type);
}
default:;
}
if (const auto it = anonymous_.find(type); it != anonymous_.end()) {
return it->second;
}
std::string type_name;
NamedTypeSymbol::Kind type_kind;
switch (type.kind) {
case CXType_ObjCId:
return &Universe::get().id();
case CXType_ObjCClass:
return &Universe::get().clazz();
case CXType_ObjCSel:
return &Universe::get().sel();
case CXType_Typedef:
type_kind = NamedTypeSymbol::Kind::TypeDef;
type_name = get_type_name(type);
break;
case CXType_ObjCInterface: {
type_name = get_type_name(type);
type_kind = NamedTypeSymbol::Kind::Interface;
assert(clang_getCanonicalType(type) == type);
break;
}
case CXType_Record: {
const auto decl = clang_getTypeDeclaration(type);
assert(is_valid(decl));
if (decl.kind == CXCursor_UnionDecl) {
type_kind = NamedTypeSymbol::Kind::Union;
} else {
assert(decl.kind == CXCursor_StructDecl);
type_kind = NamedTypeSymbol::Kind::Struct;
}
if (is_anonymous(decl)) {
type_name = new_anonymous_name(decl);
} else {
type_name = get_type_name(type);
if (type_kind == NamedTypeSymbol::Kind::Union) {
remove_prefix_in_place(type_name, "union ");
} else {
assert(type_kind == NamedTypeSymbol::Kind::Struct);
remove_prefix_in_place(type_name, "struct ");
}
}
break;
}
case CXType_Enum: {
type_kind = NamedTypeSymbol::Kind::Enum;
const auto decl = clang_getTypeDeclaration(type);
if (is_anonymous(decl)) {
type_name = new_anonymous_name(decl);
} else {
type_name = get_type_name(type);
remove_prefix_in_place(type_name, "enum ");
}
break;
}
default:
assert(is_builtin(type));
return &primitive_type(type);
}
if (auto* type_symbol = Universe::get().type(type_kind, type_name)) {
return type_symbol;
}
return this->add_type(type_kind, type_name, type);
}
Symbol* SourceScanner::push_property(std::string&& name, std::string&& getter, std::string&& setter, uint16_t modifiers)
{
assert(current_top_is_type());
auto* decl = current_type_declaration();
if (decl->is(NamedTypeSymbol::Kind::Category)) {
assert(dynamic_cast<CategoryDeclarationSymbol*>(decl));
decl = static_cast<CategoryDeclarationSymbol*>(decl)->interface();
}
return push_current(decl->add_property(std::move(name), getter, setter, modifiers));
}
static bool is_init_method(CXCursor cursor) noexcept
{
assert(cursor.kind == CXCursor_ObjCInstanceMethodDecl);
return cursor_to_decl<clang::ObjCMethodDecl>(cursor).getMethodFamily() == clang::ObjCMethodFamily::OMF_init;
}
class OverriddenCursors final {
public:
[[nodiscard]] static OverriddenCursors get(CXCursor cursor)
{
CXCursor* overridden_cursors;
unsigned num_overridden_cursors;
clang_getOverriddenCursors(cursor, &overridden_cursors, &num_overridden_cursors);
assert((num_overridden_cursors != 0) == (overridden_cursors != nullptr));
return OverriddenCursors(overridden_cursors, num_overridden_cursors);
}
OverriddenCursors(const OverriddenCursors&) = delete;
~OverriddenCursors()
{
clang_disposeOverriddenCursors(overridden_cursors_);
}
[[nodiscard]] const CXCursor* begin() const noexcept
{
return overridden_cursors_;
}
[[nodiscard]] const CXCursor* end() const noexcept
{
return end_;
}
[[nodiscard]] bool empty() const noexcept
{
return overridden_cursors_ == end_;
}
private:
CXCursor* const overridden_cursors_;
const CXCursor* const end_;
OverriddenCursors(CXCursor* overridden_cursors, unsigned num_overridden_cursors) noexcept
: overridden_cursors_(overridden_cursors), end_(overridden_cursors + num_overridden_cursors)
{
}
};
[[nodiscard]] static bool is_attributed_type_nullable(const CXType& type, CXTypeKind modified_type_kind)
{
switch (modified_type_kind) {
case CXType_ObjCObjectPointer:
case CXType_ObjCId:
case CXType_ObjCClass:
case CXType_ObjCSel:
case CXType_ObjCTypeParam:
return clang_Type_getNullability(type) != CXTypeNullability_NonNull;
default:
return false;
}
}
static bool is_nullable(CXType type)
{
switch (type.kind) {
case CXType_Unexposed: {
auto modified_type_kind = clang_Type_getModifiedType(type).kind;
return modified_type_kind != CXType_Invalid && is_attributed_type_nullable(type, modified_type_kind);
}
case CXType_Attributed: {
auto modified_type_kind = clang_Type_getModifiedType(type).kind;
assert(modified_type_kind != CXType_Invalid);
return is_attributed_type_nullable(type, modified_type_kind);
}
case CXType_ObjCObjectPointer:
case CXType_ObjCId:
case CXType_ObjCClass:
case CXType_ObjCSel:
case CXType_ObjCTypeParam:
return true;
default:
return false;
}
}
static std::optional<CXType> nullable_overridden(CXCursor cursor)
{
for (const auto& overridden_cursor : OverriddenCursors::get(cursor)) {
auto overridden_result_type = nullable_overridden(overridden_cursor);
if (overridden_result_type) {
return overridden_result_type;
}
auto ort = clang_getCursorResultType(overridden_cursor);
if (is_nullable(ort)) {
return ort;
}
}
return std::nullopt;
}
Symbol* SourceScanner::push_top_level_function(const CXCursor& cursor, std::string&& name)
{
assert(is_on_top_level());
auto cx_result_type = clang_getCursorResultType(cursor);
auto* result_type = type_like_symbol(cx_result_type);
assert(result_type);
uint16_t modifiers = 0;
if (is_nullable(cx_result_type)) {
modifiers |= ModifierNullable;
}
if (clang_getCursorLinkage(cursor) == CXLinkage_Internal) {
modifiers |= ModifierInternalLinkage;
}
auto& function = Universe::get().register_top_level_function(std::move(name), *result_type, modifiers);
set_definition_location(cursor, &function);
return push_current(&function);
}
Symbol* SourceScanner::push_member_method(CXCursor cursor, std::string&& name, uint16_t modifiers)
{
assert(current_top_is_type() || current_top_is_property());
auto* decl = current_type_declaration();
if (decl->is(NamedTypeSymbol::Kind::Category)) {
assert(dynamic_cast<CategoryDeclarationSymbol*>(decl));
decl = static_cast<CategoryDeclarationSymbol*>(decl)->interface();
}
auto cx_result_type = clang_getCursorResultType(cursor);
auto nullable_overridden_result_type = nullable_overridden(cursor);
if (nullable_overridden_result_type) {
cx_result_type = *nullable_overridden_result_type;
}
auto overridden_cursors = OverriddenCursors::get(cursor);
if (!overridden_cursors.empty()) {
modifiers |= ModifierOverride;
if (cx_result_type.kind == CXType_ObjCId) {
for (const auto& overridden_cursor : overridden_cursors) {
auto overridden_result_type = clang_getCursorResultType(overridden_cursor);
if (overridden_result_type.kind != CXType_ObjCId) {
cx_result_type = overridden_result_type;
break;
}
}
}
}
if (is_nullable(cx_result_type)) {
modifiers |= ModifierNullable;
}
return push_current(decl->add_member_method(std::move(name), type_like_symbol(cx_result_type), modifiers));
}
Symbol* SourceScanner::push_constructor(CXCursor cursor, std::string&& name)
{
assert(current_top_is_type());
auto result_type = clang_getCursorResultType(cursor);
auto* decl = current_type_declaration();
if (decl->is(NamedTypeSymbol::Kind::Category)) {
assert(dynamic_cast<CategoryDeclarationSymbol*>(decl));
decl = static_cast<CategoryDeclarationSymbol*>(decl)->interface();
}
return push_current(decl->add_constructor(std::move(name), type_like_symbol(result_type)));
}
constexpr auto WordsInAPInt = 2;
[[nodiscard]] static std::array<uint64_t, WordsInAPInt> get_enum_constant_value(const CXCursor& cursor)
{
static_assert(llvm::APInt::APINT_WORD_SIZE == sizeof(uint64_t));
auto val = cursor_to_decl<clang::EnumConstantDecl>(cursor).getInitVal();
assert(val.getNumWords() <= WordsInAPInt);
const auto* raw_value = val.getRawData();
return {raw_value[0], val.getBitWidth() <= llvm::APInt::APINT_BITS_PER_WORD ? 0 : raw_value[1]};
}
bool SourceScanner::visit_parameter_declaration(std::string cursor_name, const CXType& cursor_type)
{
auto& non_type = *current_non_type();
if (non_type.is_method()) {
auto param_index = this->func_parameter_index_++;
const auto* return_func_type = dynamic_cast<const FuncTypeSymbol*>(non_type.return_type());
if (return_func_type) {
auto number_of_parameters_in_return_func_type = return_func_type->parameters()->item_count();
if (param_index < number_of_parameters_in_return_func_type) {
return false;
}
param_index -= number_of_parameters_in_return_func_type;
}
if (cursor_name.empty()) {
cursor_name = non_type.parameter_count() ? 'x' + std::to_string(param_index) : "x";
}
non_type.add_parameter(std::move(cursor_name), type_like_symbol(cursor_type), is_nullable(cursor_type));
return false;
}
return !non_type.is_property();
}
CXChildVisitResult SourceScanner::visit_impl(CXCursor cursor, CXCursor parent)
{
assert(is_valid(cursor));
assert(is_valid(parent));
const auto cursor_kind = clang_getCursorKind(cursor);
auto name = as_string(clang_getCursorSpelling(cursor));
const auto type = clang_getCursorType(cursor);
const auto [_, first_visit] = visited_.emplace(cursor);
if (verbosity >= LogLevel::DEBUG) {
const auto level = this->level();
for (std::size_t i = 0; i < level; i++) {
std::cout << ' ';
}
std::cout << as_string(clang_getCursorKindSpelling(cursor_kind)) << ' ' << name;
if (type.kind != CXType_Invalid) {
std::cout << " <" << as_string(clang_getTypeSpelling(type)) << ">";
}
if (is_anonymous(cursor)) {
std::cout << " [anonymous]";
}
if (!first_visit) {
std::cout << " [visited]";
}
std::cout << std::endl;
}
if (!first_visit) {
return CXChildVisit_Continue;
}
int always_unavailable;
clang_getCursorPlatformAvailability(cursor, nullptr, nullptr, &always_unavailable, nullptr, nullptr, 0);
if (always_unavailable) {
return CXChildVisit_Continue;
}
if (!inputs.add_cursor(get_location(cursor), name)) {
return CXChildVisit_Continue;
}
Symbol* pushed = nullptr;
auto recurse = true;
switch (cursor_kind) {
case CXCursor_TypedefDecl: {
assert(is_on_top_level());
assert(is_defining(cursor));
auto* def = named_type_symbol(type);
switch (def->kind()) {
case NamedTypeSymbol::Kind::TypeDef:
assert(dynamic_cast<TypeAliasSymbol*>(def));
static_cast<TypeAliasSymbol*>(def)->set_target(
type_like_symbol(clang_getTypedefDeclUnderlyingType(cursor)));
break;
case NamedTypeSymbol::Kind::Protocol:
assert(name == "id");
return CXChildVisit_Continue;
case NamedTypeSymbol::Kind::Interface:
assert(name == "SEL" || name == "Class");
return CXChildVisit_Continue;
case NamedTypeSymbol::Kind::Struct:
case NamedTypeSymbol::Kind::Union:
case NamedTypeSymbol::Kind::Enum:
break;
default:
assert(false);
break;
}
recurse = false;
break;
}
case CXCursor_ObjCProtocolDecl: {
assert(!current_type());
assert(is_on_top_level());
assert(!is_valid(type));
assert(is_defining(cursor));
auto& universe = Universe::get();
auto* decl = universe.type(NamedTypeSymbol::Kind::Protocol, name);
if (!decl) {
decl = new TypeDeclarationSymbol(NamedTypeSymbol::Kind::Protocol, name);
universe.register_type(decl);
set_definition_location(cursor, decl);
}
pushed = push_current(decl, true);
break;
}
case CXCursor_ObjCInterfaceDecl: {
assert(!current_type());
assert(is_on_top_level());
assert(is_defining(type, cursor));
auto& universe = Universe::get();
auto* named = universe.type(NamedTypeSymbol::Kind::Interface, name);
if (!named) {
auto* decl = new TypeDeclarationSymbol(NamedTypeSymbol::Kind::Interface, name);
universe.register_type(decl);
set_definition_location(cursor, decl);
named = decl;
}
pushed = push_current(named, true);
break;
}
case CXCursor_TemplateTypeParameter: {
if (is_valid(type) && type.kind == CXType_ObjCTypeParam && is_valid(parent)) {
switch (parent.kind) {
case CXCursor_ObjCInterfaceDecl:
case CXCursor_ObjCCategoryDecl:
assert(current_top_is_type());
{
auto* decl = current_type_declaration();
assert((parent.kind == CXCursor_ObjCInterfaceDecl &&
decl->is(NamedTypeSymbol::Kind::Interface)) ||
(parent.kind == CXCursor_ObjCCategoryDecl &&
decl->is(NamedTypeSymbol::Kind::Category)));
assert(is_canonical(cursor));
assert(is_defining(cursor));
decl->add_parameter(name);
}
break;
default:
break;
}
}
break;
}
case CXCursor_ObjCCategoryDecl: {
assert(!current_type());
assert(!is_valid(type));
assert(is_on_top_level());
assert(is_canonical(cursor));
assert(is_defining(cursor));
const auto& cpp = cursor_to_decl<clang::ObjCCategoryDecl>(cursor);
auto* interface_decl = cpp.getClassInterface();
assert(interface_decl->getKind() == clang::Decl::ObjCInterface);
CXCursor target = {};
target.kind = CXCursor_ObjCInterfaceDecl;
target.data[0] = interface_decl;
target.data[2] = cursor.data[2];
const auto interface_type = clang_getCursorType(target);
assert(interface_type.kind == CXType_ObjCInterface);
auto* decl = named_type_symbol(interface_type);
assert(dynamic_cast<TypeDeclarationSymbol*>(decl));
pushed = push_current(
new CategoryDeclarationSymbol(std::string(cpp.getName()), static_cast<TypeDeclarationSymbol*>(decl)),
true);
break;
}
case CXCursor_StructDecl:
case CXCursor_UnionDecl: {
assert(type.kind == CXType_Record);
auto* decl = named_type_symbol(type);
pushed = push_current(decl, false);
break;
}
case CXCursor_EnumDecl: {
assert(type.kind == CXType_Enum);
auto* decl = named_type_symbol(type);
auto underlying_type = clang_getEnumDeclIntegerType(cursor);
assert(is_valid(underlying_type));
auto* underlying_type_symbol = type_like_symbol(underlying_type);
assert(underlying_type_symbol);
assert(dynamic_cast<const EnumDeclarationSymbol*>(decl));
static_cast<EnumDeclarationSymbol&>(*decl).set_underlying_type(*underlying_type_symbol);
pushed = push_current(decl, false);
break;
}
case CXCursor_ObjCSuperClassRef: {
assert(current_top_is_type());
assert(level() == 1);
assert(current_type_declaration()->is(NamedTypeSymbol::Kind::Interface));
assert(parent.kind == CXCursor_ObjCInterfaceDecl);
auto* base = type_like_symbol(type);
assert(dynamic_cast<TypeDeclarationSymbol*>(base));
current_type_declaration()->add_base(static_cast<TypeDeclarationSymbol&>(*base));
break;
}
case CXCursor_ObjCProtocolRef: {
if (parent.kind == CXCursor_TranslationUnit && is_on_top_level()) {
break;
}
const auto is_interface = parent.kind == CXCursor_ObjCInterfaceDecl;
const auto is_protocol = parent.kind == CXCursor_ObjCProtocolDecl;
if (is_interface || is_protocol) {
using Kind = NamedTypeSymbol::Kind;
assert(current_top_is_type());
assert(level() == 1);
auto* type_decl = current_type_declaration();
[[maybe_unused]] const auto kind = type_decl->kind();
assert((is_interface && kind == Kind::Interface) || (is_protocol && kind == Kind::Protocol));
const auto referenced = clang_getCursorReferenced(cursor);
assert(is_valid(referenced));
const auto referenced_type = as_string(clang_getCursorSpelling(referenced));
auto* protocol = Universe::get().type(Kind::Protocol, referenced_type);
assert(dynamic_cast<TypeDeclarationSymbol*>(protocol));
type_decl->add_base(static_cast<TypeDeclarationSymbol&>(*protocol));
}
break;
}
case CXCursor_ObjCInstanceMethodDecl: {
pushed = is_init_method(cursor) ? push_constructor(cursor, std::move(name))
: push_member_method(cursor, std::move(name),
clang_Cursor_isObjCOptional(cursor) ? ModifierOptional : 0);
this->func_parameter_index_ = 0;
break;
}
case CXCursor_ObjCClassMethodDecl: {
uint16_t modifiers = ModifierStatic;
if (clang_Cursor_isObjCOptional(cursor)) {
modifiers |= ModifierOptional;
}
pushed = push_member_method(cursor, std::move(name), modifiers);
this->func_parameter_index_ = 0;
break;
}
case CXCursor_ObjCPropertyDecl: {
uint16_t modifiers = 0;
auto attributes = clang_Cursor_getObjCPropertyAttributes(cursor, 0);
if (attributes & CXObjCPropertyAttr_class) {
modifiers |= ModifierStatic;
}
if (attributes & CXObjCPropertyAttr_readonly) {
modifiers |= ModifierReadonly;
}
if (clang_Cursor_isObjCOptional(cursor)) {
modifiers |= ModifierOptional;
}
pushed = push_property(std::move(name), as_string(clang_Cursor_getObjCPropertyGetterName(cursor)),
as_string(clang_Cursor_getObjCPropertySetterName(cursor)), modifiers);
break;
}
case CXCursor_ObjCIvarDecl: {
assert(current_top_is_type());
assert(is_canonical(cursor));
assert(is_defining(cursor));
auto access_control = cursor_to_decl<clang::ObjCIvarDecl>(cursor).getCanonicalAccessControl();
switch (access_control) {
case clang::ObjCIvarDecl::AccessControl::Package:
recurse = false;
break;
case clang::ObjCIvarDecl::AccessControl::Private:
recurse = false;
break;
case clang::ObjCIvarDecl::AccessControl::Public:
pushed = push_current(current_type_declaration()->add_instance_variable(
name, type_like_symbol(type), is_nullable(type) ? ModifierNullable : 0));
break;
default:
assert(access_control == clang::ObjCIvarDecl::AccessControl::Protected);
pushed = push_current(current_type_declaration()->add_instance_variable(
name, type_like_symbol(type), ModifierProtected | (is_nullable(type) ? ModifierNullable : 0)));
break;
}
break;
}
case CXCursor_FieldDecl: {
assert(current_top_is_type());
assert(is_canonical(cursor));
assert(is_defining(cursor));
uint16_t modifiers = 0;
if (is_nullable(type)) {
modifiers |= ModifierNullable;
}
if (clang_Cursor_isBitField(cursor)) {
modifiers |= ModifierBitField;
}
pushed = push_current(current_type_declaration()->add_field(name, type_like_symbol(type), modifiers));
break;
}
case CXCursor_EnumConstantDecl: {
assert(current_top_is_type());
assert(is_canonical(cursor));
assert(is_defining(cursor));
auto* decl = current_type();
assert(dynamic_cast<const EnumDeclarationSymbol*>(decl));
auto& enum_decl = static_cast<EnumDeclarationSymbol&>(*decl);
pushed = push_current(enum_decl.add_constant(std::move(name), get_enum_constant_value(cursor)));
break;
}
case CXCursor_ParmDecl:
assert(is_canonical(cursor));
assert(is_defining(cursor));
if (current_top_is_non_type()) {
recurse = visit_parameter_declaration(name, type);
}
break;
case CXCursor_FunctionDecl:
pushed = push_top_level_function(cursor, std::move(name));
this->func_parameter_index_ = 0;
break;
case CXCursor_VarDecl: {
recurse = false;
break;
}
case CXCursor_ObjCImplementationDecl:
case CXCursor_CompoundStmt:
return CXChildVisit_Continue;
default:;
}
if (recurse) {
visit(cursor);
}
if (pushed) {
pop_current(pushed);
}
return CXChildVisit_Continue;
}
class TranslationUnit {
public:
TranslationUnit(CXIndex index, const std::string& file, const std::vector<const char*>& args)
: tu_(clang_parseTranslationUnit(index, file.c_str(), args.data(), static_cast<int>(args.size()), nullptr, 0,
CXTranslationUnit_KeepGoing | CXTranslationUnit_VisitImplicitAttributes |
CXTranslationUnit_IncludeAttributedTypes))
{
}
~TranslationUnit()
{
clang_disposeTranslationUnit(tu_);
}
operator CXTranslationUnit() const noexcept
{
return tu_;
}
private:
const CXTranslationUnit tu_;
};
static bool parse_source(CXIndex index, const std::string& file, std::vector<const char*>& args, SourceScanner& visitor)
{
assert(!file.empty());
TranslationUnit tu(index, file, args);
if (!tu) {
return false;
}
auto numDiagnostics = clang_getNumDiagnostics(tu);
for (unsigned i = 0; i < numDiagnostics; ++i) {
switch (clang_getDiagnosticSeverity(clang_getDiagnostic(tu, i))) {
case CXDiagnostic_Error:
case CXDiagnostic_Fatal:
return false;
default:
break;
}
}
const auto cursor = clang_getTranslationUnitCursor(tu);
inputs.next_translation();
visitor.visit(cursor);
return true;
}
void ClangSessionImpl::parse_sources(const std::vector<std::string>& files, const std::vector<std::string>& arguments)
{
std::vector args = {
"-xobjective-c",
"-fobjc-nonfragile-abi",
"-fobjc-arc",
"-fblocks"
};
for (auto&& argument : arguments) {
args.push_back(argument.c_str());
}
auto all_file_names_are_empty = true;
for (auto&& file : files) {
if (!file.empty()) {
all_file_names_are_empty = false;
if (!parse_source(index_, file, args, scanner_)) {
fatal("Parsing failed because of compiler errors");
}
}
}
if (all_file_names_are_empty) {
fatal("No input files");
}
}
}