ACAV f0ba4b7c9529
Abstract Syntax Tree (AST) visualization tool for C, C++, and Objective-C
Loading...
Searching...
No Matches
AcavAstBuilder.cpp
1/*$!{
2* Aurora Clang AST Viewer (ACAV)
3*
4* Copyright (c) 2026 Min Liu
5* Copyright (c) 2026 Michael David Adams
6*
7* SPDX-License-Identifier: GPL-2.0-or-later
8*
9* This program is free software; you can redistribute it and/or modify
10* it under the terms of the GNU General Public License as published by
11* the Free Software Foundation; either version 2 of the License, or
12* (at your option) any later version.
13*
14* This program is distributed in the hope that it will be useful,
15* but WITHOUT ANY WARRANTY; without even the implied warranty of
16* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17* GNU General Public License for more details.
18*
19* You should have received a copy of the GNU General Public License along
20* with this program; if not, see <https://www.gnu.org/licenses/>.
21}$!*/
22
23#include "core/AcavAstBuilder.h"
24// Qt defines 'emit' as a no-op macro (via QObject in AcavAstBuilder.h)
25// which conflicts with Sema.h in LLVM 22+ (included transitively)
26#undef emit
27#include <cctype>
28#include <clang/AST/ASTContext.h>
29#include <clang/AST/Attr.h>
30#include <clang/AST/Comment.h>
31#include <clang/AST/CommentVisitor.h>
32#include <clang/AST/Decl.h>
33#include <clang/AST/DeclCXX.h>
34#include <clang/AST/DeclContextInternals.h>
35#include <clang/AST/DeclTemplate.h>
36#include <clang/AST/Expr.h>
37#include <clang/AST/ExprCXX.h>
38#include <clang/AST/NestedNameSpecifier.h>
39#include <clang/AST/RecursiveASTVisitor.h>
40#include <clang/AST/TemplateBase.h>
41#include <clang/AST/TemplateName.h>
42#include <clang/AST/Type.h>
43#include <clang/AST/TypeLoc.h>
44#include <clang/Frontend/ASTUnit.h>
45#include <llvm/ADT/DenseMap.h>
46#include <llvm/Config/llvm-config.h>
47#include <llvm/ADT/SmallPtrSet.h>
48#include <llvm/Support/raw_ostream.h>
49#include <unordered_map>
50
51namespace acav {
52
53namespace {
54
55InternedString exprValueKindName(clang::ExprValueKind kind) {
56 switch (kind) {
57 case clang::VK_PRValue:
58 return InternedString("VK_PRValue");
59 case clang::VK_LValue:
60 return InternedString("VK_LValue");
61 case clang::VK_XValue:
62 return InternedString("VK_XValue");
63 }
64 return InternedString("VK_Unknown");
65}
66
67InternedString exprObjectKindName(clang::ExprObjectKind kind) {
68 switch (kind) {
69 case clang::OK_Ordinary:
70 return InternedString("OK_Ordinary");
71 case clang::OK_BitField:
72 return InternedString("OK_BitField");
73 case clang::OK_VectorComponent:
74 return InternedString("OK_VectorComponent");
75 case clang::OK_ObjCProperty:
76 return InternedString("OK_ObjCProperty");
77 case clang::OK_ObjCSubscript:
78 return InternedString("OK_ObjCSubscript");
79 case clang::OK_MatrixComponent:
80 return InternedString("OK_MatrixComponent");
81 }
82 return InternedString("OK_Unknown");
83}
84
85InternedString varDeclInitStyleName(clang::VarDecl::InitializationStyle style) {
86 switch (style) {
87 case clang::VarDecl::CInit:
88 return InternedString("CInit");
89 case clang::VarDecl::CallInit:
90 return InternedString("CallInit");
91 case clang::VarDecl::ListInit:
92 return InternedString("ListInit");
93 case clang::VarDecl::ParenListInit:
94 return InternedString("ParenListInit");
95 }
96 return InternedString("InitStyle_Unknown");
97}
98
99InternedString storageClassName(clang::StorageClass storageClass) {
100 switch (storageClass) {
101 case clang::SC_None:
102 return InternedString("SC_None");
103 case clang::SC_Extern:
104 return InternedString("SC_Extern");
105 case clang::SC_Static:
106 return InternedString("SC_Static");
107 case clang::SC_PrivateExtern:
108 return InternedString("SC_PrivateExtern");
109 case clang::SC_Auto:
110 return InternedString("SC_Auto");
111 case clang::SC_Register:
112 return InternedString("SC_Register");
113 }
114 return InternedString("SC_Unknown");
115}
116
117InternedString linkageName(clang::Linkage linkage) {
118 switch (linkage) {
119 case clang::Linkage::Invalid:
120 return InternedString("Invalid");
121 case clang::Linkage::None:
122 return InternedString("None");
123 case clang::Linkage::Internal:
124 return InternedString("Internal");
125 case clang::Linkage::UniqueExternal:
126 return InternedString("UniqueExternal");
127 case clang::Linkage::VisibleNone:
128 return InternedString("VisibleNone");
129 case clang::Linkage::Module:
130 return InternedString("Module");
131 case clang::Linkage::External:
132 return InternedString("External");
133 }
134 return InternedString("Unknown");
135}
136
137AcavJson sourceLocationToJson(const SourceLocation &loc) {
138 AcavJson obj = AcavJson::object();
139 obj["fileId"] = static_cast<uint64_t>(loc.fileID());
140 obj["line"] = static_cast<uint64_t>(loc.line());
141 obj["column"] = static_cast<uint64_t>(loc.column());
142 return obj;
143}
144
145AcavJson sourceRangeToJson(const SourceRange &range) {
146 AcavJson obj = AcavJson::object();
147 obj["begin"] = sourceLocationToJson(range.begin());
148 obj["end"] = sourceLocationToJson(range.end());
149 return obj;
150}
151
152void maybeAddMacroSpellingRange(AcavJson &properties,
153 const clang::SourceRange &range,
154 const clang::SourceManager &sm,
155 FileManager &fileManager) {
156 if (!range.isValid()) {
157 return;
158 }
159 if (!range.getBegin().isMacroID() && !range.getEnd().isMacroID()) {
160 return;
161 }
162
163 clang::SourceLocation spellingBegin = sm.getSpellingLoc(range.getBegin());
164 clang::SourceLocation spellingEnd = sm.getSpellingLoc(range.getEnd());
165 if (spellingBegin.isInvalid() || spellingEnd.isInvalid()) {
166 return;
167 }
168
169 clang::SourceRange spellingRange(spellingBegin, spellingEnd);
170 SourceRange auroraRange =
171 SourceRange::fromClang(spellingRange, sm, fileManager);
172 if (!auroraRange.begin().isValid()) {
173 return;
174 }
175
176 properties["macroSpellingRange"] = sourceRangeToJson(auroraRange);
177}
178
179const char *getFullDeclClassName(const clang::Decl *decl) {
180 if (!decl)
181 return "Decl";
182 switch (decl->getKind()) {
183#define DECL(DERIVED, BASE) \
184 case clang::Decl::DERIVED: \
185 return #DERIVED "Decl";
186#define ABSTRACT_DECL(DECL)
187#include "clang/AST/DeclNodes.inc"
188 }
189 return "Decl";
190}
191
192const char *getFullTypeClassName(const clang::Type *type) {
193 if (!type)
194 return "Type";
195 switch (type->getTypeClass()) {
196#define ABSTRACT_TYPE(DERIVED, BASE)
197#define TYPE(DERIVED, BASE) \
198 case clang::Type::DERIVED: \
199 return #DERIVED "Type";
200#include "clang/AST/TypeNodes.inc"
201 }
202 return "Type";
203}
204
205} // namespace
206
207TypeInfoExtractor::TypeInfoExtractor(clang::ASTContext &ctx)
208 : ctx_(ctx), policy_(ctx.getLangOpts()) {
209 // Control how types are stringified
210 policy_.SuppressTagKeyword = false;
211 policy_.Bool = true;
212 policy_.UseVoidForZeroParams = true;
213 policy_.TerseOutput = false;
214 policy_.PolishForDeclaration = true;
215 policy_.SuppressUnwrittenScope = false;
216}
217
219 AcavJson &properties) const {
220 if (qt.isNull()) {
221 return;
222 }
223
224 // Spelled type (as written in source)
225 std::string spelledType = qt.getAsString(policy_);
226 properties["spelledType"] = InternedString(spelledType);
227
228 // Canonical type (for comparisons and deduplication)
229 clang::QualType canonicalType = qt.getCanonicalType();
230 if (canonicalType != qt) {
231 std::string canonicalStr = canonicalType.getAsString(policy_);
232 properties["canonicalType"] = InternedString(canonicalStr);
233 }
234
235 // Desugared type (expand typedefs/using)
236 clang::QualType desugared = qt.getDesugaredType(ctx_);
237 if (desugared != qt && desugared != canonicalType) {
238 std::string desugaredStr = desugared.getAsString(policy_);
239 properties["desugaredType"] = InternedString(desugaredStr);
240 }
241
242 // Extract qualifiers
243 clang::Qualifiers quals = qt.getQualifiers();
244 if (quals.hasConst()) {
245 properties["isConst"] = true;
246 }
247 if (quals.hasVolatile()) {
248 properties["isVolatile"] = true;
249 }
250 if (quals.hasRestrict()) {
251 properties["isRestrict"] = true;
252 }
253
254 // Type class for categorization
255 const clang::Type *typePtr = qt.getTypePtr();
256 if (typePtr) {
257 properties["typeClass"] = InternedString(getFullTypeClassName(typePtr));
258 }
259
260 // Additional type-specific information
261 if (const auto *ptrType = typePtr->getAs<clang::PointerType>()) {
262 clang::QualType pointee = ptrType->getPointeeType();
263 properties["pointeeType"] = InternedString(pointee.getAsString(policy_));
264 } else if (const auto *refType = typePtr->getAs<clang::ReferenceType>()) {
265 clang::QualType pointee = refType->getPointeeType();
266 properties["referencedType"] = InternedString(pointee.getAsString(policy_));
267 properties["isLValueReference"] =
268 llvm::isa<clang::LValueReferenceType>(refType);
269 properties["isRValueReference"] =
270 llvm::isa<clang::RValueReferenceType>(refType);
271 } else if (const auto *arrType = typePtr->getAsArrayTypeUnsafe()) {
272 clang::QualType elemType = arrType->getElementType();
273 properties["elementType"] = InternedString(elemType.getAsString(policy_));
274
275 // For constant arrays, include size
276 if (const auto *constArrType =
277 llvm::dyn_cast<clang::ConstantArrayType>(arrType)) {
278 properties["arraySize"] =
279 static_cast<int64_t>(constArrType->getSize().getZExtValue());
280 }
281 } else if (const auto *funcType =
282 typePtr->getAs<clang::FunctionProtoType>()) {
283 // Function type details
284 clang::QualType returnType = funcType->getReturnType();
285 properties["returnType"] = InternedString(returnType.getAsString(policy_));
286 properties["numParams"] = static_cast<int64_t>(funcType->getNumParams());
287
288 // Exception specification
289 switch (funcType->getExceptionSpecType()) {
290 case clang::EST_None:
291 break;
292 case clang::EST_NoThrow:
293 case clang::EST_NoexceptTrue:
294 properties["noexcept"] = true;
295 break;
296 case clang::EST_NoexceptFalse:
297 properties["noexcept"] = false;
298 break;
299 default:
300 properties["exceptionSpec"] =
301 static_cast<int64_t>(funcType->getExceptionSpecType());
302 break;
303 }
304
305 // Ref qualifier
306 switch (funcType->getRefQualifier()) {
307 case clang::RQ_None:
308 break;
309 case clang::RQ_LValue:
310 properties["refQualifier"] = InternedString("&");
311 break;
312 case clang::RQ_RValue:
313 properties["refQualifier"] = InternedString("&&");
314 break;
315 }
316 } else if (const auto *recordType = typePtr->getAs<clang::RecordType>()) {
317 if (auto *recordDecl = recordType->getDecl()) {
318 properties["recordName"] = InternedString(recordDecl->getNameAsString());
319 }
320 } else if (const auto *enumType = typePtr->getAs<clang::EnumType>()) {
321 if (auto *enumDecl = enumType->getDecl()) {
322 properties["enumName"] = InternedString(enumDecl->getNameAsString());
323 }
324 } else if (const auto *typedefType = typePtr->getAs<clang::TypedefType>()) {
325 if (auto *typedefDecl = typedefType->getDecl()) {
326 clang::QualType underlying = typedefDecl->getUnderlyingType();
327 properties["underlyingType"] =
328 InternedString(underlying.getAsString(policy_));
329 }
330 } else if (const auto *autoType = typePtr->getAs<clang::AutoType>()) {
331 if (autoType->isDeduced()) {
332 clang::QualType deducedType = autoType->getDeducedType();
333 properties["deducedType"] =
334 InternedString(deducedType.getAsString(policy_));
335 }
336 properties["isDecltypeAuto"] = autoType->isDecltypeAuto();
337 }
338}
339
341 AcavJson &properties) const {
342 if (tl.isNull()) {
343 return;
344 }
345
346 // TypeLoc class name
347 const clang::Type *typePtr = tl.getTypePtr();
348 if (typePtr) {
349 properties["typeLocClass"] = InternedString(getFullTypeClassName(typePtr));
350 }
351
352 // Check if has valid local source range
353 clang::SourceRange localRange = tl.getLocalSourceRange();
354 properties["hasLocalSourceRange"] = localRange.isValid();
355}
356
358 const clang::TemplateArgumentList *args, AcavJson &properties) const {
359 if (!args || args->size() == 0) {
360 return;
361 }
362
363 AcavJson argArray = AcavJson::array();
364 for (unsigned i = 0; i < args->size(); ++i) {
365 const clang::TemplateArgument &arg = args->get(i);
366 argArray.push_back(extractTemplateArg(arg));
367 }
368
369 properties["templateArgs"] = argArray;
370 properties["numTemplateArgs"] = static_cast<int64_t>(args->size());
371}
372
374 const clang::TemplateArgument &arg) const {
375 AcavJson argInfo;
376
377 // Argument kind
378 argInfo["kind"] = static_cast<int64_t>(arg.getKind());
379
380 switch (arg.getKind()) {
381 case clang::TemplateArgument::Null:
382 argInfo["kindName"] = InternedString("Null");
383 break;
384
385 case clang::TemplateArgument::Type:
386 argInfo["kindName"] = InternedString("Type");
387 {
388 clang::QualType argType = arg.getAsType();
389 if (!argType.isNull() && argType.getTypePtr()) {
390 argInfo["value"] = InternedString(argType.getAsString(policy_));
391 }
392 }
393 break;
394
395 case clang::TemplateArgument::Declaration:
396 argInfo["kindName"] = InternedString("Declaration");
397 if (auto *decl = arg.getAsDecl()) {
398 if (auto *namedDecl = llvm::dyn_cast<clang::NamedDecl>(decl)) {
399 argInfo["declName"] = InternedString(namedDecl->getNameAsString());
400 }
401 }
402 break;
403
404 case clang::TemplateArgument::NullPtr:
405 argInfo["kindName"] = InternedString("NullPtr");
406 argInfo["value"] = InternedString("nullptr");
407 break;
408
409 case clang::TemplateArgument::Integral:
410 argInfo["kindName"] = InternedString("Integral");
411 {
412 // APSInt access is safe - it's a value type
413 argInfo["value"] = arg.getAsIntegral().getSExtValue();
414
415 clang::QualType integralType = arg.getIntegralType();
416 if (!integralType.isNull() && integralType.getTypePtr()) {
417 argInfo["integralType"] =
418 InternedString(integralType.getAsString(policy_));
419 }
420 }
421 break;
422
423 case clang::TemplateArgument::Template:
424 argInfo["kindName"] = InternedString("Template");
425 {
426 clang::TemplateName templateName = arg.getAsTemplate();
427 if (!templateName.isNull()) {
428 std::string templateNameStr;
429 llvm::raw_string_ostream os(templateNameStr);
430 templateName.print(os, policy_);
431 os.flush();
432 if (!templateNameStr.empty()) {
433 argInfo["value"] = InternedString(templateNameStr);
434 }
435 }
436 }
437 break;
438
439 case clang::TemplateArgument::TemplateExpansion:
440 argInfo["kindName"] = InternedString("TemplateExpansion");
441 {
442 clang::TemplateName templateName = arg.getAsTemplateOrTemplatePattern();
443 if (!templateName.isNull()) {
444 std::string templateNameStr;
445 llvm::raw_string_ostream os(templateNameStr);
446 templateName.print(os, policy_);
447 os.flush();
448 if (!templateNameStr.empty()) {
449 argInfo["value"] = InternedString(templateNameStr);
450 }
451 }
452 }
453 break;
454
455 case clang::TemplateArgument::Expression:
456 argInfo["kindName"] = InternedString("Expression");
457 // Expression details would require additional handling
458 break;
459
460 case clang::TemplateArgument::Pack:
461 argInfo["kindName"] = InternedString("Pack");
462 {
463 unsigned packSize = arg.pack_size();
464 argInfo["packSize"] = static_cast<int64_t>(packSize);
465
466 // Only process packs with reasonable size to avoid potential issues
467 if (packSize > 0 && packSize < 1000) {
468 AcavJson packArgs = AcavJson::array();
469 for (const auto &packArg : arg.pack_elements()) {
470 // Recursively extract pack elements
471 packArgs.push_back(extractTemplateArg(packArg));
472 }
473 argInfo["packElements"] = packArgs;
474 }
475 }
476 break;
477
478 case clang::TemplateArgument::StructuralValue:
479 argInfo["kindName"] = InternedString("StructuralValue");
480 // StructuralValue is a C++20 feature for non-type template parameters
481 // Additional handling could be added here in the future
482 break;
483 }
484
485 return argInfo;
486}
487
488std::string
489TypeInfoExtractor::getQualifierString(clang::Qualifiers quals) const {
490 std::string result;
491 if (quals.hasConst())
492 result += "const ";
493 if (quals.hasVolatile())
494 result += "volatile ";
495 if (quals.hasRestrict())
496 result += "restrict ";
497 return result;
498}
499
500namespace {
501
502std::string_view toStringView(llvm::StringRef ref) {
503 return std::string_view(ref.data(), ref.size());
504}
505
506void maybeSetNamedDeclProperty(AcavJson &properties, const char *key,
507 const clang::NamedDecl *decl) {
508 if (!decl) {
509 return;
510 }
511
512 if (const clang::IdentifierInfo *identifier = decl->getIdentifier()) {
513 properties[key] = InternedString(toStringView(identifier->getName()));
514 return;
515 }
516
517 std::string nameStr = decl->getNameAsString();
518 if (!nameStr.empty()) {
519 properties[key] = InternedString(std::move(nameStr));
520 }
521}
522
523AcavJson makeDeclContextEntry(const clang::Decl *decl,
524 AstViewNode *node = nullptr) {
525 AcavJson entry;
526 entry["kind"] = InternedString(getFullDeclClassName(decl));
527 if (!decl) {
528 return entry;
529 }
530 if (auto *namedDecl = llvm::dyn_cast<clang::NamedDecl>(decl)) {
531 maybeSetNamedDeclProperty(entry, "name", namedDecl);
532 }
533 if (node) {
534 entry["nodePtr"] = reinterpret_cast<uint64_t>(node);
535 }
536 return entry;
537}
538
539AcavJson buildDeclContextChain(
540 const clang::DeclContext *start, const clang::Decl *self,
541 const std::unordered_map<const clang::DeclContext *, AstViewNode *>
542 &contextMap,
543 AstViewNode *selfNode) {
544 AcavJson chain = AcavJson::array();
545 std::vector<const clang::DeclContext *> contexts;
546
547 const clang::DeclContext *current = start;
548 while (current) {
549 contexts.push_back(current);
550 current = current->getParent();
551 }
552
553 const clang::Decl *innermostContextDecl = nullptr;
554 if (!contexts.empty()) {
555 innermostContextDecl = llvm::dyn_cast<clang::Decl>(contexts.front());
556 }
557
558 for (auto it = contexts.rbegin(); it != contexts.rend(); ++it) {
559 const clang::DeclContext *ctx = *it;
560 if (auto *ctxDecl = llvm::dyn_cast<clang::Decl>(ctx)) {
561 // Look up AstViewNode for this DeclContext
562 AstViewNode *ctxNode = nullptr;
563 auto nodeIt = contextMap.find(ctx);
564 if (nodeIt != contextMap.end()) {
565 ctxNode = nodeIt->second;
566 }
567 chain.push_back(makeDeclContextEntry(ctxDecl, ctxNode));
568 } else {
569 AcavJson entry;
570 entry["kind"] = InternedString("DeclContext");
571 chain.push_back(entry);
572 }
573 }
574
575 if (self && self != innermostContextDecl) {
576 chain.push_back(makeDeclContextEntry(self, selfNode));
577 }
578
579 return chain;
580}
581
582static void appendWithSpacing(std::string &out, llvm::StringRef text) {
583 if (text.empty()) {
584 return;
585 }
586 if (!out.empty()) {
587 char last = out.back();
588 char first = text.front();
589 if (!std::isspace(static_cast<unsigned char>(last)) &&
590 !std::isspace(static_cast<unsigned char>(first))) {
591 out.push_back(' ');
592 }
593 }
594 out.append(text.begin(), text.end());
595}
596
597static void collectCommentText(const clang::comments::Comment *comment,
598 std::string &out) {
599 if (!comment) {
600 return;
601 }
602
603 if (const auto *text =
604 llvm::dyn_cast<clang::comments::TextComment>(comment)) {
605 appendWithSpacing(out, text->getText());
606 return;
607 }
608
609 if (const auto *verbatim =
610 llvm::dyn_cast<clang::comments::VerbatimBlockLineComment>(comment)) {
611 appendWithSpacing(out, verbatim->getText());
612 return;
613 }
614
615 if (const auto *verbatimLine =
616 llvm::dyn_cast<clang::comments::VerbatimLineComment>(comment)) {
617 appendWithSpacing(out, verbatimLine->getText());
618 return;
619 }
620
621 for (auto it = comment->child_begin(); it != comment->child_end(); ++it) {
622 collectCommentText(*it, out);
623 }
624
625 if (llvm::isa<clang::comments::ParagraphComment>(comment)) {
626 if (!out.empty() && out.back() != '\n') {
627 out.push_back('\n');
628 }
629 }
630}
631
632class AcavAstVisitor : public clang::RecursiveASTVisitor<AcavAstVisitor> {
633public:
634 AcavAstVisitor(AstContext *context, FileManager &fm, clang::ASTContext &ctx,
635 clang::Preprocessor *preprocessor, bool extractComments)
636 : context_(context), fileManager_(fm), astContext_(ctx),
637 preprocessor_(preprocessor), extractComments_(extractComments),
638 typeExtractor_(ctx) {}
639
640 bool shouldVisitImplicitCode() const { return true; }
641 bool shouldVisitTemplateInstantiations() const { return true; }
642 bool shouldWalkTypesOfTypeLocs() const { return true; }
643 bool shouldTraversePostOrder() const { return false; }
644
645 bool TraverseDecl(clang::Decl *decl);
646 bool TraverseStmt(clang::Stmt *stmt);
647#if LLVM_VERSION_MAJOR >= 22
648 bool TraverseType(clang::QualType qualType, bool TraverseQualifier = true);
649 bool TraverseTypeLoc(clang::TypeLoc typeLoc, bool TraverseQualifier = true);
650#else
651 bool TraverseType(clang::QualType qualType);
652 bool TraverseTypeLoc(clang::TypeLoc typeLoc);
653#endif
654 bool TraverseAttr(clang::Attr *attr);
655 bool TraverseConceptReference(clang::ConceptReference *cr);
656 bool TraverseCXXBaseSpecifier(const clang::CXXBaseSpecifier &spec);
657 bool TraverseConstructorInitializer(clang::CXXCtorInitializer *init);
658 bool TraverseLambdaCapture(clang::LambdaExpr *lambda,
659 const clang::LambdaCapture *capture,
660 clang::Expr *init);
661#if LLVM_VERSION_MAJOR >= 22
662 bool TraverseNestedNameSpecifier(clang::NestedNameSpecifier nns);
663#else
664 bool TraverseNestedNameSpecifier(clang::NestedNameSpecifier *nns);
665#endif
666 bool TraverseNestedNameSpecifierLoc(clang::NestedNameSpecifierLoc loc);
667 bool TraverseTemplateArgument(clang::TemplateArgument arg);
668 bool TraverseTemplateArgumentLoc(clang::TemplateArgumentLoc loc);
669 bool TraverseTemplateName(clang::TemplateName name);
670
671 // Visit hooks (node creation + stack push). Traverse manages pop.
672 bool VisitDecl(clang::Decl *decl);
673 bool VisitStmt(clang::Stmt *stmt);
674 bool VisitType(clang::Type *type);
675 bool VisitTypeLoc(clang::TypeLoc typeLoc);
676 bool VisitAttr(clang::Attr *attr);
677 bool VisitConceptReference(clang::ConceptReference *cr);
678 bool VisitCXXBaseSpecifier(const clang::CXXBaseSpecifier &spec);
679 bool VisitCXXCtorInitializer(clang::CXXCtorInitializer *init);
680 bool VisitLambdaCapture(const clang::LambdaCapture *capture);
681 bool VisitNestedNameSpecifier(clang::NestedNameSpecifier *nns);
682 bool VisitNestedNameSpecifierLoc(clang::NestedNameSpecifierLoc loc);
683 bool VisitTemplateArgument(clang::TemplateArgument arg);
684 bool VisitTemplateArgumentLoc(clang::TemplateArgumentLoc loc);
685 bool VisitTemplateName(clang::TemplateName name);
686
687 AstViewNode *getRootNode() const { return root_; }
688 const AstExtractionStats &getStats() const { return stats_; }
689
690private:
691 const InternedString &getCachedTypeString(clang::QualType type) {
692 const void *key = type.getAsOpaquePtr();
693 auto it = typeStringCache_.find(key);
694 if (it != typeStringCache_.end()) {
695 return it->second;
696 }
697 auto [insertIt, inserted] =
698 typeStringCache_.try_emplace(key, InternedString(type.getAsString()));
699 return insertIt->second;
700 }
701
702 void pushNode(AstViewNode *node) {
703 if (!node) {
704 return;
705 }
706 if (!root_) {
707 root_ = node;
708 } else if (!parentStack_.empty()) {
709 parentStack_.back()->addChild(node);
710 }
711 parentStack_.push_back(node);
712 }
713
714 void popNode() {
715 if (!parentStack_.empty()) {
716 parentStack_.pop_back();
717 }
718 }
719
720 AcavJson &currentProperties() {
721 return parentStack_.back()->getNode()->getProperties();
722 }
723
724 AstViewNode *createNodeFromDecl(clang::Decl *decl);
725 AstViewNode *createNodeFromStmt(clang::Stmt *stmt);
726 AstViewNode *createNodeFromType(const clang::Type *type);
727 AstViewNode *createNodeFromTypeLoc(clang::TypeLoc typeLoc);
728 AstViewNode *createNodeFromAttr(clang::Attr *attr);
729 AstViewNode *createNodeFromConceptRef(clang::ConceptReference *cr);
730 AstViewNode *createNodeFromCXXBaseSpec(const clang::CXXBaseSpecifier &spec);
731 AstViewNode *createNodeFromCtorInit(clang::CXXCtorInitializer *init);
732 AstViewNode *createNodeFromLambdaCapture(const clang::LambdaCapture *capture);
733#if LLVM_VERSION_MAJOR >= 22
734 AstViewNode *createNodeFromNestedNameSpec(clang::NestedNameSpecifier nns);
735#else
736 AstViewNode *createNodeFromNestedNameSpec(clang::NestedNameSpecifier *nns);
737#endif
738 AstViewNode *
739 createNodeFromNestedNameSpecLoc(clang::NestedNameSpecifierLoc loc);
740 AstViewNode *createNodeFromTemplateArg(clang::TemplateArgument arg);
741 AstViewNode *createNodeFromTemplateArgLoc(clang::TemplateArgumentLoc loc);
742 AstViewNode *createNodeFromTemplateName(clang::TemplateName name);
743
744 AstContext *context_;
745 FileManager &fileManager_;
746 clang::ASTContext &astContext_;
747 clang::Preprocessor *preprocessor_;
748 bool extractComments_;
749 TypeInfoExtractor typeExtractor_;
750 AstViewNode *root_ = nullptr;
751 std::vector<AstViewNode *> parentStack_;
752 AstExtractionStats stats_;
753 llvm::DenseMap<const void *, InternedString> typeStringCache_;
754 // Map DeclContext to AstViewNode for navigation in declaration context panel
755 std::unordered_map<const clang::DeclContext *, AstViewNode *>
756 declContextToNode_;
757};
758
759// Traverse and create implementations are identical to the prior version in
760// AstExtractorRunner; keep behavior but centralized here.
761
762bool AcavAstVisitor::TraverseDecl(clang::Decl *decl) {
763 if (!decl) {
764 return true;
765 }
766 AstViewNode *node = createNodeFromDecl(decl);
767 if (!node) {
768 return true;
769 }
770 // Extract comment as property on the Decl node
771 if (extractComments_ && preprocessor_) {
772 if (const auto *comment =
773 astContext_.getCommentForDecl(decl, preprocessor_)) {
774 std::string text;
775 collectCommentText(comment, text);
776 if (!text.empty()) {
777 node->getNode()->getProperties()["comment"] = InternedString(text);
778 ++stats_.commentCount;
779 }
780 }
781 }
782 // Track DeclContext -> AstViewNode mapping for navigation
783 if (auto *dc = llvm::dyn_cast<clang::DeclContext>(decl)) {
784 declContextToNode_[dc] = node;
785 }
786 pushNode(node);
787 bool result = RecursiveASTVisitor::TraverseDecl(decl);
788 popNode();
789 return result;
790}
791
792bool AcavAstVisitor::TraverseStmt(clang::Stmt *stmt) {
793 if (!stmt) {
794 return true;
795 }
796 AstViewNode *node = createNodeFromStmt(stmt);
797 if (!node) {
798 return true;
799 }
800 pushNode(node);
801 bool result = RecursiveASTVisitor::TraverseStmt(stmt);
802 popNode();
803 return result;
804}
805
806#if LLVM_VERSION_MAJOR >= 22
807bool AcavAstVisitor::TraverseType(clang::QualType qualType,
808 bool TraverseQualifier) {
809 if (qualType.isNull()) {
810 return true;
811 }
812 const clang::Type *type = qualType.getTypePtr();
813 AstViewNode *node = createNodeFromType(type);
814 if (!node) {
815 return true;
816 }
817 pushNode(node);
818 bool result = RecursiveASTVisitor::TraverseType(qualType, TraverseQualifier);
819 popNode();
820 return result;
821}
822
823bool AcavAstVisitor::TraverseTypeLoc(clang::TypeLoc typeLoc,
824 bool TraverseQualifier) {
825 if (typeLoc.isNull()) {
826 return true;
827 }
828 AstViewNode *node = createNodeFromTypeLoc(typeLoc);
829 if (!node) {
830 return true;
831 }
832 pushNode(node);
833 bool result =
834 RecursiveASTVisitor::TraverseTypeLoc(typeLoc, TraverseQualifier);
835 popNode();
836 return result;
837}
838#else
839bool AcavAstVisitor::TraverseType(clang::QualType qualType) {
840 if (qualType.isNull()) {
841 return true;
842 }
843 const clang::Type *type = qualType.getTypePtr();
844 AstViewNode *node = createNodeFromType(type);
845 if (!node) {
846 return true;
847 }
848 pushNode(node);
849 bool result = RecursiveASTVisitor::TraverseType(qualType);
850 popNode();
851 return result;
852}
853
854bool AcavAstVisitor::TraverseTypeLoc(clang::TypeLoc typeLoc) {
855 if (typeLoc.isNull()) {
856 return true;
857 }
858 AstViewNode *node = createNodeFromTypeLoc(typeLoc);
859 if (!node) {
860 return true;
861 }
862 pushNode(node);
863 bool result = RecursiveASTVisitor::TraverseTypeLoc(typeLoc);
864 popNode();
865 return result;
866}
867#endif
868
869bool AcavAstVisitor::TraverseAttr(clang::Attr *attr) {
870 if (!attr) {
871 return true;
872 }
873 AstViewNode *node = createNodeFromAttr(attr);
874 if (!node) {
875 return true;
876 }
877 pushNode(node);
878 bool result = RecursiveASTVisitor::TraverseAttr(attr);
879 popNode();
880 return result;
881}
882
883bool AcavAstVisitor::TraverseConceptReference(clang::ConceptReference *cr) {
884 if (!cr) {
885 return true;
886 }
887 AstViewNode *node = createNodeFromConceptRef(cr);
888 if (!node) {
889 return true;
890 }
891 pushNode(node);
892 bool result = RecursiveASTVisitor::TraverseConceptReference(cr);
893 popNode();
894 return result;
895}
896
897bool AcavAstVisitor::TraverseCXXBaseSpecifier(
898 const clang::CXXBaseSpecifier &spec) {
899 AstViewNode *node = createNodeFromCXXBaseSpec(spec);
900 if (!node) {
901 return true;
902 }
903 pushNode(node);
904 bool result = RecursiveASTVisitor::TraverseCXXBaseSpecifier(spec);
905 popNode();
906 return result;
907}
908
909bool AcavAstVisitor::TraverseConstructorInitializer(
910 clang::CXXCtorInitializer *init) {
911 if (!init) {
912 return true;
913 }
914 AstViewNode *node = createNodeFromCtorInit(init);
915 if (!node) {
916 return true;
917 }
918 pushNode(node);
919 bool result = RecursiveASTVisitor::TraverseConstructorInitializer(init);
920 popNode();
921 return result;
922}
923
924bool AcavAstVisitor::TraverseLambdaCapture(
925 clang::LambdaExpr *lambda, const clang::LambdaCapture *capture,
926 clang::Expr *init) {
927 if (!capture) {
928 return true;
929 }
930 AstViewNode *node = createNodeFromLambdaCapture(capture);
931 if (!node) {
932 return true;
933 }
934 pushNode(node);
935 bool result =
936 RecursiveASTVisitor::TraverseLambdaCapture(lambda, capture, init);
937 popNode();
938 return result;
939}
940
941#if LLVM_VERSION_MAJOR >= 22
942bool AcavAstVisitor::TraverseNestedNameSpecifier(
943 clang::NestedNameSpecifier nns) {
944 if (!nns) {
945 return true;
946 }
947 AstViewNode *node = createNodeFromNestedNameSpec(nns);
948 if (!node) {
949 return true;
950 }
951 pushNode(node);
952 bool result = RecursiveASTVisitor::TraverseNestedNameSpecifier(nns);
953 popNode();
954 return result;
955}
956#else
957bool AcavAstVisitor::TraverseNestedNameSpecifier(
958 clang::NestedNameSpecifier *nns) {
959 if (!nns) {
960 return true;
961 }
962 AstViewNode *node = createNodeFromNestedNameSpec(nns);
963 if (!node) {
964 return true;
965 }
966 pushNode(node);
967 bool result = RecursiveASTVisitor::TraverseNestedNameSpecifier(nns);
968 popNode();
969 return result;
970}
971#endif
972
973bool AcavAstVisitor::TraverseNestedNameSpecifierLoc(
974 clang::NestedNameSpecifierLoc loc) {
975 if (!loc) {
976 return true;
977 }
978 AstViewNode *node = createNodeFromNestedNameSpecLoc(loc);
979 if (!node) {
980 return true;
981 }
982 pushNode(node);
983 bool result = RecursiveASTVisitor::TraverseNestedNameSpecifierLoc(loc);
984 popNode();
985 return result;
986}
987
988bool AcavAstVisitor::TraverseTemplateArgument(clang::TemplateArgument arg) {
989 AstViewNode *node = createNodeFromTemplateArg(arg);
990 if (!node) {
991 return true;
992 }
993 pushNode(node);
994 bool result = RecursiveASTVisitor::TraverseTemplateArgument(arg);
995 popNode();
996 return result;
997}
998
999bool AcavAstVisitor::TraverseTemplateArgumentLoc(
1000 clang::TemplateArgumentLoc loc) {
1001 AstViewNode *node = createNodeFromTemplateArgLoc(loc);
1002 if (!node) {
1003 return true;
1004 }
1005 pushNode(node);
1006 bool result = RecursiveASTVisitor::TraverseTemplateArgumentLoc(loc);
1007 popNode();
1008 return result;
1009}
1010
1011bool AcavAstVisitor::TraverseTemplateName(clang::TemplateName name) {
1012 AstViewNode *node = createNodeFromTemplateName(name);
1013 if (!node) {
1014 return true;
1015 }
1016 pushNode(node);
1017 bool result = RecursiveASTVisitor::TraverseTemplateName(name);
1018 popNode();
1019 return result;
1020}
1021
1022bool AcavAstVisitor::VisitDecl(clang::Decl *decl) {
1023 if (!decl || parentStack_.empty()) {
1024 return true;
1025 }
1026 AcavJson &properties = currentProperties();
1027 properties["isDeclContext"] = llvm::isa<clang::DeclContext>(decl);
1028 if (auto *namedDecl = llvm::dyn_cast<clang::NamedDecl>(decl)) {
1029 maybeSetNamedDeclProperty(properties, "name", namedDecl);
1030 if (namedDecl->hasLinkage()) {
1031 properties["linkage"] =
1032 static_cast<int64_t>(namedDecl->getLinkageInternal());
1033 properties["linkageName"] = linkageName(namedDecl->getLinkageInternal());
1034 }
1035 }
1036 if (auto *valueDecl = llvm::dyn_cast<clang::ValueDecl>(decl)) {
1037 clang::QualType type = valueDecl->getType();
1038 if (!type.isNull()) {
1039 properties["type"] = getCachedTypeString(type);
1040 }
1041 }
1042 if (auto *funcDecl = llvm::dyn_cast<clang::FunctionDecl>(decl)) {
1043 properties["isDefined"] = funcDecl->isDefined();
1044 properties["isInlined"] = funcDecl->isInlined();
1045 properties["isConstexpr"] = funcDecl->isConstexpr();
1046 properties["isDeleted"] = funcDecl->isDeleted();
1047 properties["isDefaulted"] = funcDecl->isDefaulted();
1048 clang::QualType returnType = funcDecl->getReturnType();
1049 if (!returnType.isNull()) {
1050 properties["returnType"] = getCachedTypeString(returnType);
1051 }
1052 unsigned numParams = funcDecl->getNumParams();
1053 properties["numParams"] = static_cast<int64_t>(numParams);
1054 if (numParams > 0) {
1055 AcavJson params = AcavJson::array();
1056 for (unsigned i = 0; i < numParams; ++i) {
1057 const clang::ParmVarDecl *param = funcDecl->getParamDecl(i);
1058 AcavJson paramInfo;
1059 maybeSetNamedDeclProperty(paramInfo, "name", param);
1060 paramInfo["type"] = getCachedTypeString(param->getType());
1061 params.push_back(paramInfo);
1062 }
1063 properties["params"] = params;
1064 }
1065 if (auto *cxxMethod = llvm::dyn_cast<clang::CXXMethodDecl>(funcDecl)) {
1066 properties["isVirtual"] = cxxMethod->isVirtual();
1067 properties["isPure"] = cxxMethod->isPureVirtual();
1068 properties["isConst"] = cxxMethod->isConst();
1069 properties["isStatic"] = cxxMethod->isStatic();
1070 properties["isOverride"] = cxxMethod->size_overridden_methods() > 0;
1071 switch (cxxMethod->getRefQualifier()) {
1072 case clang::RQ_None:
1073 break;
1074 case clang::RQ_LValue:
1075 properties["refQualifier"] = InternedString("&");
1076 break;
1077 case clang::RQ_RValue:
1078 properties["refQualifier"] = InternedString("&&");
1079 break;
1080 }
1081 }
1082 }
1083 if (auto *varDecl = llvm::dyn_cast<clang::VarDecl>(decl)) {
1084 properties["isUsed"] = varDecl->isUsed();
1085 properties["isReferenced"] = varDecl->isReferenced();
1086 properties["hasInit"] = varDecl->hasInit();
1087 properties["isConstexpr"] = varDecl->isConstexpr();
1088 if (varDecl->hasInit()) {
1089 properties["initStyle"] = static_cast<int64_t>(varDecl->getInitStyle());
1090 properties["initStyleName"] =
1091 varDeclInitStyleName(varDecl->getInitStyle());
1092 }
1093 properties["storageClass"] =
1094 static_cast<int64_t>(varDecl->getStorageClass());
1095 properties["storageClassName"] =
1096 storageClassName(varDecl->getStorageClass());
1097 properties["isThreadLocal"] =
1098 varDecl->getTLSKind() != clang::VarDecl::TLS_None;
1099 }
1100 if (auto *fieldDecl = llvm::dyn_cast<clang::FieldDecl>(decl)) {
1101 properties["isMutable"] = fieldDecl->isMutable();
1102 properties["isBitField"] = fieldDecl->isBitField();
1103 }
1104 if (auto *parmDecl = llvm::dyn_cast<clang::ParmVarDecl>(decl)) {
1105 properties["isUsed"] = parmDecl->isUsed();
1106 properties["hasDefaultArg"] = parmDecl->hasDefaultArg();
1107 }
1108 if (auto *recordDecl = llvm::dyn_cast<clang::CXXRecordDecl>(decl)) {
1109 properties["isReferenced"] = recordDecl->isReferenced();
1110 properties["hasDefinition"] = recordDecl->hasDefinition();
1111 if (recordDecl->hasDefinition() && recordDecl->isCompleteDefinition()) {
1112 properties["isAbstract"] = recordDecl->isAbstract();
1113 properties["isPolymorphic"] = recordDecl->isPolymorphic();
1114 properties["isEmpty"] = recordDecl->isEmpty();
1115 properties["isAggregate"] = recordDecl->isAggregate();
1116 properties["isPOD"] = recordDecl->isPOD();
1117 properties["isTrivial"] = recordDecl->isTrivial();
1118 if (recordDecl->getNumBases() > 0) {
1119 AcavJson bases = AcavJson::array();
1120 for (const auto &base : recordDecl->bases()) {
1121 clang::QualType baseType = base.getType();
1122 if (baseType.isNull()) {
1123 continue;
1124 }
1125 AcavJson baseInfo;
1126 baseInfo["type"] = getCachedTypeString(baseType);
1127 baseInfo["isVirtual"] = base.isVirtual();
1128 baseInfo["accessSpecifier"] =
1129 static_cast<int64_t>(base.getAccessSpecifier());
1130 bases.push_back(baseInfo);
1131 }
1132 properties["bases"] = bases;
1133 properties["numBases"] =
1134 static_cast<int64_t>(recordDecl->getNumBases());
1135 }
1136 }
1137 if (auto *tagDecl = llvm::dyn_cast<clang::TagDecl>(recordDecl)) {
1138 properties["tagKind"] = static_cast<int64_t>(tagDecl->getTagKind());
1139 properties["isCompleteDefinition"] = tagDecl->isCompleteDefinition();
1140 }
1141 }
1142 if (auto *enumDecl = llvm::dyn_cast<clang::EnumDecl>(decl)) {
1143 properties["isScoped"] = enumDecl->isScoped();
1144 properties["isFixed"] = enumDecl->isFixed();
1145 if (enumDecl->isFixed()) {
1146 clang::QualType intType = enumDecl->getIntegerType();
1147 if (!intType.isNull()) {
1148 properties["underlyingType"] = getCachedTypeString(intType);
1149 }
1150 }
1151 }
1152 if (auto *enumConstDecl = llvm::dyn_cast<clang::EnumConstantDecl>(decl)) {
1153 properties["initVal"] = enumConstDecl->getInitVal().getSExtValue();
1154 }
1155 if (auto *tempTypeParm = llvm::dyn_cast<clang::TemplateTypeParmDecl>(decl)) {
1156 properties["depth"] = static_cast<int64_t>(tempTypeParm->getDepth());
1157 properties["index"] = static_cast<int64_t>(tempTypeParm->getIndex());
1158 properties["hasDefaultArgument"] = tempTypeParm->hasDefaultArgument();
1159 }
1160 if (auto *classTemplateSpec =
1161 llvm::dyn_cast<clang::ClassTemplateSpecializationDecl>(decl)) {
1162 const clang::TemplateArgumentList &args =
1163 classTemplateSpec->getTemplateArgs();
1164 typeExtractor_.extractTemplateArgs(&args, properties);
1165 properties["specializationKind"] =
1166 static_cast<int64_t>(classTemplateSpec->getSpecializationKind());
1167 }
1168 if (auto *typedefDecl = llvm::dyn_cast<clang::TypedefNameDecl>(decl)) {
1169 clang::QualType underlying = typedefDecl->getUnderlyingType();
1170 if (!underlying.isNull()) {
1171 properties["underlyingType"] = getCachedTypeString(underlying);
1172 }
1173 }
1174 // Attach declaration context info for all Decl nodes.
1175 // All Decl nodes have semantic and lexical declaration contexts.
1176 AstViewNode *currentNode =
1177 parentStack_.empty() ? nullptr : parentStack_.back();
1178 properties["semanticDeclContext"] = buildDeclContextChain(
1179 decl->getDeclContext(), decl, declContextToNode_, currentNode);
1180 properties["lexicalDeclContext"] = buildDeclContextChain(
1181 decl->getLexicalDeclContext(), decl, declContextToNode_, currentNode);
1182 return true;
1183}
1184
1185bool AcavAstVisitor::VisitStmt(clang::Stmt *stmt) {
1186 if (!stmt || parentStack_.empty()) {
1187 return true;
1188 }
1189 AcavJson &properties = currentProperties();
1190 if (auto *expr = llvm::dyn_cast<clang::Expr>(stmt)) {
1191 clang::QualType exprType = expr->getType();
1192 if (!exprType.isNull()) {
1193 properties["type"] = getCachedTypeString(exprType);
1194 }
1195 properties["valueKind"] = static_cast<int64_t>(expr->getValueKind());
1196 properties["valueKindName"] = exprValueKindName(expr->getValueKind());
1197 properties["objectKind"] = static_cast<int64_t>(expr->getObjectKind());
1198 properties["objectKindName"] = exprObjectKindName(expr->getObjectKind());
1199 properties["isTypeDependent"] = expr->isTypeDependent();
1200 properties["isValueDependent"] = expr->isValueDependent();
1201 properties["isLValue"] = expr->isLValue();
1202 properties["isPRValue"] = expr->isPRValue();
1203 properties["isXValue"] = expr->isXValue();
1204 properties["isGLValue"] = expr->isGLValue();
1205 }
1206 if (auto *ifStmt = llvm::dyn_cast<clang::IfStmt>(stmt)) {
1207 properties["hasElse"] = (ifStmt->getElse() != nullptr);
1208 properties["hasInit"] = (ifStmt->getInit() != nullptr);
1209 properties["hasVar"] = (ifStmt->getConditionVariable() != nullptr);
1210 properties["isConstexpr"] = ifStmt->isConstexpr();
1211 }
1212 if (auto *binOp = llvm::dyn_cast<clang::BinaryOperator>(stmt)) {
1213 properties["opcode"] = InternedString(toStringView(binOp->getOpcodeStr()));
1214 properties["opcodeValue"] = static_cast<int64_t>(binOp->getOpcode());
1215 properties["isAssignment"] = binOp->isAssignmentOp();
1216 properties["isCompound"] = binOp->isCompoundAssignmentOp();
1217 properties["isComparison"] = binOp->isComparisonOp();
1218 properties["isLogical"] = binOp->isLogicalOp();
1219 }
1220 if (auto *unaryOp = llvm::dyn_cast<clang::UnaryOperator>(stmt)) {
1221 properties["opcode"] = InternedString(
1222 toStringView(clang::UnaryOperator::getOpcodeStr(unaryOp->getOpcode())));
1223 properties["opcodeValue"] = static_cast<int64_t>(unaryOp->getOpcode());
1224 properties["isPrefix"] = unaryOp->isPrefix();
1225 properties["isPostfix"] = unaryOp->isPostfix();
1226 properties["isIncrementOp"] = unaryOp->isIncrementOp();
1227 properties["isDecrementOp"] = unaryOp->isDecrementOp();
1228 }
1229 if (auto *intLit = llvm::dyn_cast<clang::IntegerLiteral>(stmt)) {
1230 properties["value"] = intLit->getValue().getSExtValue();
1231 }
1232 if (auto *floatLit = llvm::dyn_cast<clang::FloatingLiteral>(stmt)) {
1233 properties["value"] = floatLit->getValueAsApproximateDouble();
1234 properties["isExact"] = floatLit->isExact();
1235 }
1236 if (auto *strLit = llvm::dyn_cast<clang::StringLiteral>(stmt)) {
1237 properties["byteLength"] = static_cast<int64_t>(strLit->getByteLength());
1238 properties["length"] = static_cast<int64_t>(strLit->getLength());
1239 properties["charWidth"] = static_cast<int64_t>(strLit->getCharByteWidth());
1240
1241 const bool isSingleByte = strLit->getCharByteWidth() == 1;
1242
1243 if (isSingleByte) {
1244 properties["value"] = InternedString(toStringView(strLit->getString()));
1245 properties["encoding"] = InternedString("char");
1246 } else {
1247 std::string buffer;
1248 llvm::raw_string_ostream os(buffer);
1249 strLit->outputString(os);
1250 os.flush();
1251 properties["value"] = InternedString(buffer);
1252 properties["encoding"] = InternedString("wide");
1253 }
1254 }
1255 if (auto *charLit = llvm::dyn_cast<clang::CharacterLiteral>(stmt)) {
1256 properties["value"] = static_cast<int64_t>(charLit->getValue());
1257 }
1258 if (auto *boolLit = llvm::dyn_cast<clang::CXXBoolLiteralExpr>(stmt)) {
1259 properties["value"] = boolLit->getValue();
1260 }
1261 if (auto *declRef = llvm::dyn_cast<clang::DeclRefExpr>(stmt)) {
1262 if (auto *decl = declRef->getDecl()) {
1263 if (auto *namedDecl = llvm::dyn_cast<clang::NamedDecl>(decl)) {
1264 maybeSetNamedDeclProperty(properties, "declName", namedDecl);
1265 }
1266 }
1267 }
1268 if (auto *memberExpr = llvm::dyn_cast<clang::MemberExpr>(stmt)) {
1269 if (auto *member = memberExpr->getMemberDecl()) {
1270 maybeSetNamedDeclProperty(properties, "memberName", member);
1271 }
1272 properties["isArrow"] = memberExpr->isArrow();
1273 }
1274 if (auto *callExpr = llvm::dyn_cast<clang::CallExpr>(stmt)) {
1275 properties["numArgs"] = static_cast<int64_t>(callExpr->getNumArgs());
1276 if (auto *callee = callExpr->getCallee()) {
1277 if (auto *declRef = llvm::dyn_cast<clang::DeclRefExpr>(callee)) {
1278 if (auto *funcDecl =
1279 llvm::dyn_cast<clang::FunctionDecl>(declRef->getDecl())) {
1280 maybeSetNamedDeclProperty(properties, "calleeName", funcDecl);
1281 }
1282 }
1283 }
1284 }
1285 if (auto *castExpr = llvm::dyn_cast<clang::CastExpr>(stmt)) {
1286 properties["castKind"] = InternedString(castExpr->getCastKindName());
1287 properties["castKindValue"] = static_cast<int64_t>(castExpr->getCastKind());
1288 if (auto *explicitCast =
1289 llvm::dyn_cast<clang::ExplicitCastExpr>(castExpr)) {
1290 clang::QualType targetType = explicitCast->getTypeAsWritten();
1291 properties["targetType"] = getCachedTypeString(targetType);
1292 }
1293 }
1294 if (auto *ctorExpr = llvm::dyn_cast<clang::CXXConstructExpr>(stmt)) {
1295 if (auto *ctorDecl = ctorExpr->getConstructor()) {
1296 maybeSetNamedDeclProperty(properties, "constructorName", ctorDecl);
1297 }
1298 properties["numArgs"] = static_cast<int64_t>(ctorExpr->getNumArgs());
1299 properties["isElidable"] = ctorExpr->isElidable();
1300 properties["requiresZeroInit"] = ctorExpr->requiresZeroInitialization();
1301 }
1302 if (auto *newExpr = llvm::dyn_cast<clang::CXXNewExpr>(stmt)) {
1303 clang::QualType allocatedType = newExpr->getAllocatedType();
1304 properties["allocatedType"] = getCachedTypeString(allocatedType);
1305 properties["isArray"] = newExpr->isArray();
1306 properties["isGlobalNew"] = newExpr->isGlobalNew();
1307 }
1308 if (auto *deleteExpr = llvm::dyn_cast<clang::CXXDeleteExpr>(stmt)) {
1309 properties["isArray"] = deleteExpr->isArrayForm();
1310 properties["isGlobalDelete"] = deleteExpr->isGlobalDelete();
1311 }
1312 if (auto *lambdaExpr = llvm::dyn_cast<clang::LambdaExpr>(stmt)) {
1313 properties["isMutable"] = lambdaExpr->isMutable();
1314 properties["hasExplicitParams"] = lambdaExpr->hasExplicitParameters();
1315 properties["hasExplicitResultType"] = lambdaExpr->hasExplicitResultType();
1316 properties["numCaptures"] =
1317 static_cast<int64_t>(lambdaExpr->capture_size());
1318 }
1319 return true;
1320}
1321
1322bool AcavAstVisitor::VisitType(clang::Type *type) {
1323 if (!type || parentStack_.empty()) {
1324 return true;
1325 }
1326 const clang::Type *canonicalType =
1327 type->getCanonicalTypeInternal().getTypePtr();
1328 AcavJson &properties = currentProperties();
1329 if (properties.contains("canonicalType")) {
1330 return true;
1331 }
1332 clang::QualType qt(canonicalType, 0);
1333 properties["canonicalType"] = getCachedTypeString(qt);
1334 if (auto *builtinType = llvm::dyn_cast<clang::BuiltinType>(canonicalType)) {
1335 clang::PrintingPolicy policy(astContext_.getLangOpts());
1336 properties["typeName"] =
1337 InternedString(toStringView(builtinType->getName(policy)));
1338 }
1339 if (auto *arrayType =
1340 llvm::dyn_cast<clang::ConstantArrayType>(canonicalType)) {
1341 properties["size"] =
1342 static_cast<int64_t>(arrayType->getSize().getZExtValue());
1343 }
1344 return true;
1345}
1346
1347bool AcavAstVisitor::VisitTypeLoc(clang::TypeLoc typeLoc) {
1348 if (typeLoc.isNull() || parentStack_.empty()) {
1349 return true;
1350 }
1351 const clang::Type *type = typeLoc.getType().getTypePtr();
1352 AcavJson &properties = currentProperties();
1353 Q_UNUSED(type);
1354 clang::SourceRange localRange = typeLoc.getLocalSourceRange();
1355 if (localRange.isValid()) {
1356 properties["hasLocalRange"] = true;
1357 }
1358 return true;
1359}
1360
1361bool AcavAstVisitor::VisitAttr(clang::Attr *attr) {
1362 if (!attr || parentStack_.empty()) {
1363 return true;
1364 }
1365 AcavJson &properties = currentProperties();
1366 properties["spelling"] = InternedString(attr->getSpelling());
1367 return true;
1368}
1369
1370bool AcavAstVisitor::VisitConceptReference(clang::ConceptReference *cr) {
1371 if (!cr || parentStack_.empty()) {
1372 return true;
1373 }
1374 AcavJson &properties = currentProperties();
1375 if (auto *namedConcept = cr->getNamedConcept()) {
1376 maybeSetNamedDeclProperty(properties, "name", namedConcept);
1377 }
1378 return true;
1379}
1380
1381bool AcavAstVisitor::VisitCXXBaseSpecifier(
1382 const clang::CXXBaseSpecifier &spec) {
1383 if (parentStack_.empty()) {
1384 return true;
1385 }
1386 AcavJson &properties = currentProperties();
1387 clang::QualType baseType = spec.getType();
1388 if (!baseType.isNull()) {
1389 properties["baseType"] = getCachedTypeString(baseType);
1390 }
1391 return true;
1392}
1393
1394bool AcavAstVisitor::VisitCXXCtorInitializer(
1395 clang::CXXCtorInitializer *init) {
1396 if (!init || parentStack_.empty()) {
1397 return true;
1398 }
1399 AcavJson &properties = currentProperties();
1400 if (init->isMemberInitializer() && init->getMember()) {
1401 maybeSetNamedDeclProperty(properties, "member", init->getMember());
1402 }
1403 return true;
1404}
1405
1406bool AcavAstVisitor::VisitLambdaCapture(const clang::LambdaCapture *capture) {
1407 if (!capture || parentStack_.empty()) {
1408 return true;
1409 }
1410 AcavJson &properties = currentProperties();
1411 properties["captureKind"] = static_cast<int64_t>(capture->getCaptureKind());
1412 return true;
1413}
1414
1415bool AcavAstVisitor::VisitNestedNameSpecifier(
1416 clang::NestedNameSpecifier *nns) {
1417 if (!nns || parentStack_.empty()) {
1418 return true;
1419 }
1420 AcavJson &properties = currentProperties();
1421 std::string qualifierStr;
1422 llvm::raw_string_ostream os(qualifierStr);
1423 nns->print(os, astContext_.getPrintingPolicy());
1424 os.flush();
1425 properties["qualifier"] = InternedString(qualifierStr);
1426 return true;
1427}
1428
1429bool AcavAstVisitor::VisitNestedNameSpecifierLoc(
1430 clang::NestedNameSpecifierLoc loc) {
1431 if (!loc || parentStack_.empty()) {
1432 return true;
1433 }
1434 AcavJson &properties = currentProperties();
1435 return true;
1436}
1437
1438bool AcavAstVisitor::VisitTemplateArgument(clang::TemplateArgument arg) {
1439 if (parentStack_.empty()) {
1440 return true;
1441 }
1442 AcavJson &properties = currentProperties();
1443 properties["argKind"] = static_cast<int64_t>(arg.getKind());
1444 switch (arg.getKind()) {
1445 case clang::TemplateArgument::Type:
1446 if (!arg.getAsType().isNull()) {
1447 properties["value"] = getCachedTypeString(arg.getAsType());
1448 }
1449 break;
1450 case clang::TemplateArgument::Integral:
1451 properties["value"] = arg.getAsIntegral().getSExtValue();
1452 break;
1453 default:
1454 break;
1455 }
1456 return true;
1457}
1458
1459bool AcavAstVisitor::VisitTemplateArgumentLoc(
1460 clang::TemplateArgumentLoc loc) {
1461 if (parentStack_.empty()) {
1462 return true;
1463 }
1464 AcavJson &properties = currentProperties();
1465 const clang::TemplateArgument &arg = loc.getArgument();
1466 properties["argKind"] = static_cast<int64_t>(arg.getKind());
1467 switch (arg.getKind()) {
1468 case clang::TemplateArgument::Type:
1469 if (!arg.getAsType().isNull()) {
1470 properties["value"] = getCachedTypeString(arg.getAsType());
1471 }
1472 break;
1473 case clang::TemplateArgument::Integral:
1474 properties["value"] = arg.getAsIntegral().getSExtValue();
1475 break;
1476 default:
1477 break;
1478 }
1479 return true;
1480}
1481
1482bool AcavAstVisitor::VisitTemplateName(clang::TemplateName name) {
1483 if (parentStack_.empty()) {
1484 return true;
1485 }
1486 AcavJson &properties = currentProperties();
1487 if (auto *tempDecl = name.getAsTemplateDecl()) {
1488 if (auto *namedDecl = llvm::dyn_cast<clang::NamedDecl>(tempDecl)) {
1489 maybeSetNamedDeclProperty(properties, "name", namedDecl);
1490 }
1491 }
1492 return true;
1493}
1494
1495// The createNode* implementations below are copied from the previous visitor
1496// in AstExtractorRunner to preserve behavior.
1497
1498AstViewNode *AcavAstVisitor::createNodeFromDecl(clang::Decl *decl) {
1499 if (!decl) {
1500 return nullptr;
1501 }
1502 AcavJson properties;
1503 properties["kind"] = InternedString(getFullDeclClassName(decl));
1504 properties["isDecl"] = true;
1505 clang::SourceRange clangRange = decl->getSourceRange();
1506 maybeAddMacroSpellingRange(properties, clangRange,
1507 astContext_.getSourceManager(), fileManager_);
1508 SourceRange auroraRange = SourceRange::fromClang(
1509 clangRange, astContext_.getSourceManager(), fileManager_);
1510 AstNode *astNode = context_->createAstNode(properties, auroraRange);
1511 ++stats_.totalCount;
1512 ++stats_.declCount;
1513 return context_->createAstViewNode(astNode);
1514}
1515
1516AstViewNode *AcavAstVisitor::createNodeFromStmt(clang::Stmt *stmt) {
1517 if (!stmt) {
1518 return nullptr;
1519 }
1520 AcavJson properties;
1521 properties["kind"] = InternedString(stmt->getStmtClassName());
1522 clang::SourceRange clangRange = stmt->getSourceRange();
1523 maybeAddMacroSpellingRange(properties, clangRange,
1524 astContext_.getSourceManager(), fileManager_);
1525 SourceRange auroraRange = SourceRange::fromClang(
1526 clangRange, astContext_.getSourceManager(), fileManager_);
1527 AstNode *astNode = context_->createAstNode(properties, auroraRange);
1528 ++stats_.totalCount;
1529 ++stats_.stmtCount;
1530 return context_->createAstViewNode(astNode);
1531}
1532
1533AstViewNode *AcavAstVisitor::createNodeFromType(const clang::Type *type) {
1534 if (!type) {
1535 return nullptr;
1536 }
1537 const clang::Type *canonicalType =
1538 type->getCanonicalTypeInternal().getTypePtr();
1539 if (AstNode *existing = context_->findTypeNode(canonicalType)) {
1540 ++stats_.totalCount;
1541 ++stats_.typeCount;
1542 return context_->createAstViewNode(existing);
1543 }
1544 AcavJson properties;
1545 properties["kind"] = InternedString(getFullTypeClassName(type));
1546 SourceRange auroraRange = SourceRange::fromClang(
1547 clang::SourceRange(), astContext_.getSourceManager(), fileManager_);
1548 AstNode *astNode =
1549 context_->getOrCreateTypeNode(canonicalType, properties, auroraRange);
1550 ++stats_.totalCount;
1551 ++stats_.typeCount;
1552 return context_->createAstViewNode(astNode);
1553}
1554
1555AstViewNode *AcavAstVisitor::createNodeFromTypeLoc(clang::TypeLoc typeLoc) {
1556 if (typeLoc.isNull()) {
1557 return nullptr;
1558 }
1559 const clang::Type *type = typeLoc.getType().getTypePtr();
1560 AcavJson properties;
1561 properties["kind"] = InternedString(getFullTypeClassName(type));
1562 maybeAddMacroSpellingRange(properties, typeLoc.getSourceRange(),
1563 astContext_.getSourceManager(), fileManager_);
1564 SourceRange auroraRange = SourceRange::fromClang(
1565 typeLoc.getSourceRange(), astContext_.getSourceManager(), fileManager_);
1566 AstNode *astNode = context_->createAstNode(properties, auroraRange);
1567 ++stats_.totalCount;
1568 ++stats_.typeLocCount;
1569 return context_->createAstViewNode(astNode);
1570}
1571
1572AstViewNode *AcavAstVisitor::createNodeFromAttr(clang::Attr *attr) {
1573 if (!attr) {
1574 return nullptr;
1575 }
1576 AcavJson properties;
1577 properties["kind"] = InternedString("Attr");
1578 maybeAddMacroSpellingRange(properties, attr->getRange(),
1579 astContext_.getSourceManager(), fileManager_);
1580 SourceRange auroraRange = SourceRange::fromClang(
1581 attr->getRange(), astContext_.getSourceManager(), fileManager_);
1582 AstNode *astNode = context_->createAstNode(properties, auroraRange);
1583 ++stats_.totalCount;
1584 ++stats_.attrCount;
1585 return context_->createAstViewNode(astNode);
1586}
1587
1588AstViewNode *
1589AcavAstVisitor::createNodeFromConceptRef(clang::ConceptReference *cr) {
1590 if (!cr) {
1591 return nullptr;
1592 }
1593 AcavJson properties;
1594 properties["kind"] = InternedString("ConceptReference");
1595 maybeAddMacroSpellingRange(properties, cr->getSourceRange(),
1596 astContext_.getSourceManager(), fileManager_);
1597 SourceRange auroraRange = SourceRange::fromClang(
1598 cr->getSourceRange(), astContext_.getSourceManager(), fileManager_);
1599 AstNode *astNode = context_->createAstNode(properties, auroraRange);
1600 ++stats_.totalCount;
1601 ++stats_.conceptRefCount;
1602 return context_->createAstViewNode(astNode);
1603}
1604
1605AstViewNode *AcavAstVisitor::createNodeFromCXXBaseSpec(
1606 const clang::CXXBaseSpecifier &spec) {
1607 AcavJson properties;
1608 properties["kind"] = InternedString("CXXBaseSpecifier");
1609 maybeAddMacroSpellingRange(properties, spec.getSourceRange(),
1610 astContext_.getSourceManager(), fileManager_);
1611 SourceRange auroraRange = SourceRange::fromClang(
1612 spec.getSourceRange(), astContext_.getSourceManager(), fileManager_);
1613 AstNode *astNode = context_->createAstNode(properties, auroraRange);
1614 ++stats_.totalCount;
1615 ++stats_.cxxBaseSpecCount;
1616 return context_->createAstViewNode(astNode);
1617}
1618
1619AstViewNode *
1620AcavAstVisitor::createNodeFromCtorInit(clang::CXXCtorInitializer *init) {
1621 if (!init) {
1622 return nullptr;
1623 }
1624 AcavJson properties;
1625 properties["kind"] = InternedString("CXXCtorInitializer");
1626 maybeAddMacroSpellingRange(properties, init->getSourceRange(),
1627 astContext_.getSourceManager(), fileManager_);
1628 SourceRange auroraRange = SourceRange::fromClang(
1629 init->getSourceRange(), astContext_.getSourceManager(), fileManager_);
1630 AstNode *astNode = context_->createAstNode(properties, auroraRange);
1631 ++stats_.totalCount;
1632 ++stats_.ctorInitCount;
1633 return context_->createAstViewNode(astNode);
1634}
1635
1636AstViewNode *AcavAstVisitor::createNodeFromLambdaCapture(
1637 const clang::LambdaCapture *capture) {
1638 if (!capture) {
1639 return nullptr;
1640 }
1641 AcavJson properties;
1642 properties["kind"] = InternedString("LambdaCapture");
1643 SourceRange auroraRange = SourceRange::fromClang(
1644 clang::SourceRange(), astContext_.getSourceManager(), fileManager_);
1645 AstNode *astNode = context_->createAstNode(properties, auroraRange);
1646 ++stats_.totalCount;
1647 ++stats_.lambdaCaptureCount;
1648 return context_->createAstViewNode(astNode);
1649}
1650
1651#if LLVM_VERSION_MAJOR >= 22
1652AstViewNode *AcavAstVisitor::createNodeFromNestedNameSpec(
1653 clang::NestedNameSpecifier nns) {
1654 if (!nns) {
1655 return nullptr;
1656 }
1657#else
1658AstViewNode *AcavAstVisitor::createNodeFromNestedNameSpec(
1659 clang::NestedNameSpecifier *nns) {
1660 if (!nns) {
1661 return nullptr;
1662 }
1663#endif
1664 AcavJson properties;
1665 properties["kind"] = InternedString("NestedNameSpecifier");
1666 SourceRange auroraRange = SourceRange::fromClang(
1667 clang::SourceRange(), astContext_.getSourceManager(), fileManager_);
1668 AstNode *astNode = context_->createAstNode(properties, auroraRange);
1669 ++stats_.totalCount;
1670 ++stats_.nestedNameSpecCount;
1671 return context_->createAstViewNode(astNode);
1672}
1673
1674AstViewNode *AcavAstVisitor::createNodeFromNestedNameSpecLoc(
1675 clang::NestedNameSpecifierLoc loc) {
1676 if (!loc) {
1677 return nullptr;
1678 }
1679 AcavJson properties;
1680 properties["kind"] = InternedString("NestedNameSpecifierLoc");
1681 maybeAddMacroSpellingRange(properties, loc.getSourceRange(),
1682 astContext_.getSourceManager(), fileManager_);
1683 SourceRange auroraRange = SourceRange::fromClang(
1684 loc.getSourceRange(), astContext_.getSourceManager(), fileManager_);
1685 AstNode *astNode = context_->createAstNode(properties, auroraRange);
1686 ++stats_.totalCount;
1687 ++stats_.nestedNameSpecLocCount;
1688 return context_->createAstViewNode(astNode);
1689}
1690
1691AstViewNode *
1692AcavAstVisitor::createNodeFromTemplateArg(clang::TemplateArgument arg) {
1693 AcavJson properties;
1694 properties["kind"] = InternedString("TemplateArgument");
1695 SourceRange auroraRange = SourceRange::fromClang(
1696 clang::SourceRange(), astContext_.getSourceManager(), fileManager_);
1697 AstNode *astNode = context_->createAstNode(properties, auroraRange);
1698 ++stats_.totalCount;
1699 ++stats_.tempArgCount;
1700 return context_->createAstViewNode(astNode);
1701}
1702
1703AstViewNode *
1704AcavAstVisitor::createNodeFromTemplateArgLoc(clang::TemplateArgumentLoc loc) {
1705 AcavJson properties;
1706 properties["kind"] = InternedString("TemplateArgumentLoc");
1707 maybeAddMacroSpellingRange(properties, loc.getSourceRange(),
1708 astContext_.getSourceManager(), fileManager_);
1709 SourceRange auroraRange = SourceRange::fromClang(
1710 loc.getSourceRange(), astContext_.getSourceManager(), fileManager_);
1711 AstNode *astNode = context_->createAstNode(properties, auroraRange);
1712 ++stats_.totalCount;
1713 ++stats_.tempArgLocCount;
1714 return context_->createAstViewNode(astNode);
1715}
1716
1717AstViewNode *
1718AcavAstVisitor::createNodeFromTemplateName(clang::TemplateName name) {
1719 AcavJson properties;
1720 properties["kind"] = InternedString("TemplateName");
1721 SourceRange auroraRange = SourceRange::fromClang(
1722 clang::SourceRange(), astContext_.getSourceManager(), fileManager_);
1723 AstNode *astNode = context_->createAstNode(properties, auroraRange);
1724 ++stats_.totalCount;
1725 ++stats_.tempNameCount;
1726 return context_->createAstViewNode(astNode);
1727}
1728
1729} // namespace
1730
1732 AstContext *context,
1733 FileManager &fileManager,
1734 AstExtractionStats &stats,
1735 bool extractComments) {
1736 if (!context) {
1737 return nullptr;
1738 }
1739 clang::ASTContext &ctx = astUnit.getASTContext();
1740 clang::TranslationUnitDecl *tuDecl = ctx.getTranslationUnitDecl();
1741 if (!tuDecl) {
1742 return nullptr;
1743 }
1744 AcavAstVisitor visitor(context, fileManager, ctx,
1745 astUnit.getPreprocessorPtr().get(), extractComments);
1746 visitor.TraverseDecl(tuDecl);
1747 stats = visitor.getStats();
1748 return visitor.getRootNode();
1749}
1750
1751} // namespace acav
Clang AST to ACAV AST transformation.
nlohmann::basic_json< std::map, std::vector, InternedString, bool, int64_t, uint64_t, double, std::allocator, nlohmann::adl_serializer, std::vector< uint8_t > > AcavJson
Custom JSON type using InternedString for automatic string deduplication.
Definition AstNode.h:51
static AstViewNode * buildFromASTUnit(clang::ASTUnit &astUnit, AstContext *context, FileManager &fileManager, AstExtractionStats &stats, bool extractComments=false)
Build ACAV AST from Clang ASTUnit.
Memory manager for AST nodes in a translation unit.
Definition AstNode.h:66
Represents node in AST tree hierarchy.
Definition AstNode.h:195
Centralized file registry providing path-to-FileID mapping.
Definition FileManager.h:45
Immutable string with automatic deduplication via global pool.
Represents a specific position in source code.
Represents a span of source code (begin to end).
static SourceRange fromClang(const clang::SourceRange &range, const clang::SourceManager &sm, FileManager &fileMgr)
Create SourceRange from Clang's SourceRange.
void extractTypeInfo(clang::QualType qt, AcavJson &properties) const
Extract comprehensive type information into JSON.
void extractTypeLocInfo(clang::TypeLoc tl, AcavJson &properties) const
Extract TypeLoc-specific information.
AcavJson extractTemplateArg(const clang::TemplateArgument &arg) const
Extract single template argument details.
void extractTemplateArgs(const clang::TemplateArgumentList *args, AcavJson &properties) const
Extract template argument information.
Statistics collected during AST extraction.