#include "clang_inspector.hpp" #include "../core/log.hpp" #include 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(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(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(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