277 lines
10 KiB
C++
277 lines
10 KiB
C++
#include "clang_inspector.hpp"
|
|
#include "../core/log.hpp"
|
|
#include <chrono>
|
|
|
|
namespace ci {
|
|
|
|
static std::string cx_to_string(CXString str) {
|
|
const char* cstr = clang_getCString(str);
|
|
std::string s = cstr ? cstr : "";
|
|
clang_disposeString(str);
|
|
return s;
|
|
}
|
|
|
|
ClangInspector::ClangInspector() {
|
|
index_ = clang_createIndex(0, 0);
|
|
if (!index_) {
|
|
Logger::get().error("Failed to create Clang Index (libclang).");
|
|
} else {
|
|
Logger::get().info("libclang initialized successfully.");
|
|
}
|
|
}
|
|
|
|
ClangInspector::~ClangInspector() {
|
|
if (index_) {
|
|
clang_disposeIndex(index_);
|
|
index_ = nullptr;
|
|
}
|
|
}
|
|
|
|
void ClangInspector::clear() {
|
|
functions_.clear();
|
|
ast_roots_.clear();
|
|
diagnostics_.clear();
|
|
has_errors_ = false;
|
|
}
|
|
|
|
void ClangInspector::build_ast_node(CXCursor cursor, ASTNode& node, CXTranslationUnit tu) {
|
|
node.kind = clang_getCursorKind(cursor);
|
|
node.kind_name = cx_to_string(clang_getCursorKindSpelling(node.kind));
|
|
node.name = cx_to_string(clang_getCursorSpelling(cursor));
|
|
node.type_name = cx_to_string(clang_getTypeSpelling(clang_getCursorType(cursor)));
|
|
|
|
CXSourceRange range = clang_getCursorExtent(cursor);
|
|
unsigned line = 0, col = 0, end_line = 0, end_col = 0;
|
|
clang_getSpellingLocation(clang_getRangeStart(range), nullptr, &line, &col, nullptr);
|
|
clang_getSpellingLocation(clang_getRangeEnd(range), nullptr, &end_line, &end_col, nullptr);
|
|
|
|
node.line = (int)line;
|
|
node.column = (int)col;
|
|
node.end_line = (int)end_line;
|
|
node.end_column = (int)end_col;
|
|
|
|
// Token inspection for literals and binary operators
|
|
if (tu && (node.kind == CXCursor_IntegerLiteral || node.kind == CXCursor_FloatingLiteral || node.kind == CXCursor_BinaryOperator)) {
|
|
CXToken* tokens = nullptr;
|
|
unsigned num_tokens = 0;
|
|
clang_tokenize(tu, range, &tokens, &num_tokens);
|
|
if (tokens && num_tokens > 0) {
|
|
if (node.kind == CXCursor_IntegerLiteral) {
|
|
std::string tok = cx_to_string(clang_getTokenSpelling(tu, tokens[0]));
|
|
try {
|
|
node.int_val = std::stoll(tok, nullptr, 0);
|
|
if (node.name.empty()) node.name = tok;
|
|
} catch (...) {}
|
|
} else if (node.kind == CXCursor_FloatingLiteral) {
|
|
std::string tok = cx_to_string(clang_getTokenSpelling(tu, tokens[0]));
|
|
try {
|
|
node.float_val = std::stod(tok);
|
|
if (node.name.empty()) node.name = tok;
|
|
} catch (...) {}
|
|
} else if (node.kind == CXCursor_BinaryOperator && node.name.empty()) {
|
|
for (unsigned t = 0; t < num_tokens; ++t) {
|
|
std::string tok = cx_to_string(clang_getTokenSpelling(tu, tokens[t]));
|
|
if (tok == "==" || tok == "!=" || tok == "<=" || tok == "<" ||
|
|
tok == ">=" || tok == ">" || tok == "+" || tok == "-" ||
|
|
tok == "*" || tok == "/" || tok == "%" || tok == "=" ||
|
|
tok == "+=" || tok == "-=" || tok == "*=" || tok == "/=" ||
|
|
tok == "&&" || tok == "||") {
|
|
node.name = tok;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
clang_disposeTokens(tu, tokens, num_tokens);
|
|
}
|
|
}
|
|
|
|
struct RecurseContext {
|
|
ClangInspector* self;
|
|
ASTNode* parent;
|
|
CXTranslationUnit tu;
|
|
} ctx = { this, &node, tu };
|
|
|
|
clang_visitChildren(cursor, [](CXCursor child, CXCursor parent, CXClientData data) -> CXChildVisitResult {
|
|
RecurseContext* c = static_cast<RecurseContext*>(data);
|
|
CXCursorKind ck = clang_getCursorKind(child);
|
|
if (ck != CXCursor_NoDeclFound && ck != CXCursor_InvalidFile) {
|
|
ASTNode child_node;
|
|
c->self->build_ast_node(child, child_node, c->tu);
|
|
c->parent->children.push_back(child_node);
|
|
}
|
|
return CXChildVisit_Continue;
|
|
}, &ctx);
|
|
}
|
|
|
|
bool ClangInspector::inspect_code(const std::string& filename, const std::string& code_str) {
|
|
if (!index_) {
|
|
index_ = clang_createIndex(0, 0);
|
|
if (!index_) return false;
|
|
}
|
|
|
|
clear();
|
|
auto t0 = std::chrono::high_resolution_clock::now();
|
|
|
|
CXUnsavedFile unsaved;
|
|
unsaved.Filename = filename.c_str();
|
|
unsaved.Contents = code_str.c_str();
|
|
unsaved.Length = (unsigned long)code_str.size();
|
|
|
|
const char* args[] = {
|
|
"-std=c11",
|
|
"-I.",
|
|
"-Wno-everything"
|
|
};
|
|
|
|
CXTranslationUnit tu = clang_parseTranslationUnit(
|
|
index_,
|
|
filename.c_str(),
|
|
args, sizeof(args) / sizeof(args[0]),
|
|
&unsaved, 1,
|
|
CXTranslationUnit_DetailedPreprocessingRecord | CXTranslationUnit_KeepGoing
|
|
);
|
|
|
|
if (!tu) {
|
|
Logger::get().error("libclang failed to parse translation unit: " + filename);
|
|
return false;
|
|
}
|
|
|
|
// 1. Diagnostics extraction
|
|
unsigned num_diags = clang_getNumDiagnostics(tu);
|
|
for (unsigned i = 0; i < num_diags; ++i) {
|
|
CXDiagnostic diag = clang_getDiagnostic(tu, i);
|
|
CXDiagnosticSeverity sev = clang_getDiagnosticSeverity(diag);
|
|
|
|
CXString diag_spelling = clang_formatDiagnostic(diag, clang_defaultDiagnosticDisplayOptions());
|
|
std::string msg = cx_to_string(diag_spelling);
|
|
|
|
CXSourceLocation loc = clang_getDiagnosticLocation(diag);
|
|
unsigned line = 0, col = 0;
|
|
clang_getSpellingLocation(loc, nullptr, &line, &col, nullptr);
|
|
|
|
DiagnosticItem item;
|
|
item.message = msg;
|
|
item.line = (int)line;
|
|
item.column = (int)col;
|
|
item.filename = filename;
|
|
|
|
if (sev >= CXDiagnostic_Error) {
|
|
item.level = LogLevel::Error;
|
|
has_errors_ = true;
|
|
} else if (sev == CXDiagnostic_Warning) {
|
|
item.level = LogLevel::Warning;
|
|
} else {
|
|
item.level = LogLevel::Info;
|
|
}
|
|
|
|
diagnostics_.push_back(item);
|
|
clang_disposeDiagnostic(diag);
|
|
}
|
|
|
|
// 2. Visit Top-Level AST
|
|
CXCursor root = clang_getTranslationUnitCursor(tu);
|
|
|
|
struct VisitorContext {
|
|
ClangInspector* self;
|
|
std::string filename;
|
|
CXTranslationUnit tu;
|
|
} ctx = { this, filename, tu };
|
|
|
|
clang_visitChildren(root, [](CXCursor cursor, CXCursor parent, CXClientData client_data) -> CXChildVisitResult {
|
|
VisitorContext* c = static_cast<VisitorContext*>(client_data);
|
|
CXCursorKind kind = clang_getCursorKind(cursor);
|
|
|
|
// Only look at declarations in main input file
|
|
CXSourceLocation loc = clang_getCursorLocation(cursor);
|
|
CXFile file = nullptr;
|
|
unsigned line = 0, col = 0;
|
|
clang_getSpellingLocation(loc, &file, &line, &col, nullptr);
|
|
|
|
if (file != nullptr) {
|
|
std::string file_str = cx_to_string(clang_getFileName(file));
|
|
if (!file_str.empty() && file_str != c->filename) {
|
|
return CXChildVisit_Continue; // skip headers
|
|
}
|
|
}
|
|
|
|
// Add to AST roots
|
|
ASTNode root_node;
|
|
c->self->build_ast_node(cursor, root_node, c->tu);
|
|
c->self->ast_roots_.push_back(root_node);
|
|
|
|
// Extract Functions
|
|
if (kind == CXCursor_FunctionDecl) {
|
|
std::string func_name = cx_to_string(clang_getCursorSpelling(cursor));
|
|
CXType ret_type = clang_getCursorResultType(cursor);
|
|
std::string ret_name = cx_to_string(clang_getTypeSpelling(ret_type));
|
|
|
|
FunctionDef def;
|
|
def.name = func_name;
|
|
def.return_type = ret_name.empty() ? "void" : ret_name;
|
|
def.line_start = (int)line;
|
|
|
|
CXSourceRange range = clang_getCursorExtent(cursor);
|
|
unsigned end_l = 0;
|
|
clang_getSpellingLocation(clang_getRangeEnd(range), nullptr, &end_l, nullptr, nullptr);
|
|
def.line_end = (int)end_l;
|
|
|
|
// Extract Parameters
|
|
int num_args = clang_Cursor_getNumArguments(cursor);
|
|
for (int a = 0; a < num_args; ++a) {
|
|
CXCursor arg_cursor = clang_Cursor_getArgument(cursor, a);
|
|
std::string arg_name = cx_to_string(clang_getCursorSpelling(arg_cursor));
|
|
CXType arg_type = clang_getCursorType(arg_cursor);
|
|
std::string arg_type_name = cx_to_string(clang_getTypeSpelling(arg_type));
|
|
|
|
FunctionParam param;
|
|
param.name = arg_name.empty() ? ("arg" + std::to_string(a)) : arg_name;
|
|
param.type_name = arg_type_name;
|
|
|
|
if (arg_type_name.find("float") != std::string::npos) {
|
|
param.type = ParamType::Float;
|
|
} else if (arg_type_name.find("double") != std::string::npos) {
|
|
param.type = ParamType::Double;
|
|
} else if (arg_type_name.find("char*") != std::string::npos || arg_type_name.find("const char*") != std::string::npos) {
|
|
param.type = ParamType::String;
|
|
} else {
|
|
param.type = ParamType::Int;
|
|
if (func_name == "factorial") {
|
|
param.int_val = 5;
|
|
param.int_min = 0;
|
|
param.int_max = 20;
|
|
}
|
|
}
|
|
|
|
def.params.push_back(param);
|
|
}
|
|
|
|
// Build full recursive body AST for this function
|
|
c->self->build_ast_node(cursor, def.body_ast, c->tu);
|
|
c->self->functions_.push_back(def);
|
|
}
|
|
|
|
return CXChildVisit_Continue;
|
|
}, &ctx);
|
|
|
|
clang_disposeTranslationUnit(tu);
|
|
|
|
auto t1 = std::chrono::high_resolution_clock::now();
|
|
parse_time_ms_ = (int)std::chrono::duration<double, std::milli>(t1 - t0).count();
|
|
|
|
std::ostringstream ss;
|
|
ss << "libclang AST inspected in " << parse_time_ms_ << "ms ("
|
|
<< functions_.size() << " functions, "
|
|
<< ast_roots_.size() << " AST declarations, "
|
|
<< diagnostics_.size() << " diagnostics)";
|
|
|
|
if (has_errors_) {
|
|
Logger::get().warn(ss.str());
|
|
} else {
|
|
Logger::get().success(ss.str());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace ci
|