Files
CodeInspector/src/code/clang_inspector.cpp
T

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