1 | //===- CIndexHigh.cpp - Higher level API functions ------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "CursorVisitor.h" |
10 | #include "CLog.h" |
11 | #include "CXCursor.h" |
12 | #include "CXFile.h" |
13 | #include "CXSourceLocation.h" |
14 | #include "CXTranslationUnit.h" |
15 | #include "clang/AST/DeclObjC.h" |
16 | #include "clang/Frontend/ASTUnit.h" |
17 | #include "llvm/Support/Compiler.h" |
18 | |
19 | using namespace clang; |
20 | using namespace cxcursor; |
21 | using namespace cxindex; |
22 | |
23 | static void getTopOverriddenMethods(CXTranslationUnit TU, |
24 | const Decl *D, |
25 | SmallVectorImpl<const Decl *> &Methods) { |
26 | if (!D) |
27 | return; |
28 | if (!isa<ObjCMethodDecl>(Val: D) && !isa<CXXMethodDecl>(Val: D)) |
29 | return; |
30 | |
31 | SmallVector<CXCursor, 8> Overridden; |
32 | cxcursor::getOverriddenCursors(cursor: cxcursor::MakeCXCursor(D, TU), overridden&: Overridden); |
33 | |
34 | if (Overridden.empty()) { |
35 | Methods.push_back(Elt: D->getCanonicalDecl()); |
36 | return; |
37 | } |
38 | |
39 | for (SmallVectorImpl<CXCursor>::iterator |
40 | I = Overridden.begin(), E = Overridden.end(); I != E; ++I) |
41 | getTopOverriddenMethods(TU, D: cxcursor::getCursorDecl(Cursor: *I), Methods); |
42 | } |
43 | |
44 | namespace { |
45 | |
46 | struct FindFileIdRefVisitData { |
47 | CXTranslationUnit TU; |
48 | FileID FID; |
49 | const Decl *Dcl; |
50 | int SelectorIdIdx; |
51 | CXCursorAndRangeVisitor visitor; |
52 | |
53 | typedef SmallVector<const Decl *, 8> TopMethodsTy; |
54 | TopMethodsTy TopMethods; |
55 | |
56 | FindFileIdRefVisitData(CXTranslationUnit TU, FileID FID, |
57 | const Decl *D, int selectorIdIdx, |
58 | CXCursorAndRangeVisitor visitor) |
59 | : TU(TU), FID(FID), SelectorIdIdx(selectorIdIdx), visitor(visitor) { |
60 | Dcl = getCanonical(D); |
61 | getTopOverriddenMethods(TU, D: Dcl, Methods&: TopMethods); |
62 | } |
63 | |
64 | ASTContext &getASTContext() const { |
65 | return cxtu::getASTUnit(TU)->getASTContext(); |
66 | } |
67 | |
68 | /// We are looking to find all semantically relevant identifiers, |
69 | /// so the definition of "canonical" here is different than in the AST, e.g. |
70 | /// |
71 | /// \code |
72 | /// class C { |
73 | /// C() {} |
74 | /// }; |
75 | /// \endcode |
76 | /// |
77 | /// we consider the canonical decl of the constructor decl to be the class |
78 | /// itself, so both 'C' can be highlighted. |
79 | const Decl *getCanonical(const Decl *D) const { |
80 | if (!D) |
81 | return nullptr; |
82 | |
83 | D = D->getCanonicalDecl(); |
84 | |
85 | if (const ObjCImplDecl *ImplD = dyn_cast<ObjCImplDecl>(Val: D)) { |
86 | if (ImplD->getClassInterface()) |
87 | return getCanonical(ImplD->getClassInterface()); |
88 | |
89 | } else if (const CXXConstructorDecl *CXXCtorD = |
90 | dyn_cast<CXXConstructorDecl>(Val: D)) { |
91 | return getCanonical(D: CXXCtorD->getParent()); |
92 | } |
93 | |
94 | return D; |
95 | } |
96 | |
97 | bool isHit(const Decl *D) const { |
98 | if (!D) |
99 | return false; |
100 | |
101 | D = getCanonical(D); |
102 | if (D == Dcl) |
103 | return true; |
104 | |
105 | if (isa<ObjCMethodDecl>(Val: D) || isa<CXXMethodDecl>(Val: D)) |
106 | return isOverriddingMethod(D); |
107 | |
108 | return false; |
109 | } |
110 | |
111 | private: |
112 | bool isOverriddingMethod(const Decl *D) const { |
113 | if (llvm::is_contained(Range: TopMethods, Element: D)) |
114 | return true; |
115 | |
116 | TopMethodsTy methods; |
117 | getTopOverriddenMethods(TU, D, Methods&: methods); |
118 | for (TopMethodsTy::iterator |
119 | I = methods.begin(), E = methods.end(); I != E; ++I) { |
120 | if (llvm::is_contained(Range: TopMethods, Element: *I)) |
121 | return true; |
122 | } |
123 | |
124 | return false; |
125 | } |
126 | }; |
127 | |
128 | } // end anonymous namespace. |
129 | |
130 | /// For a macro \arg Loc, returns the file spelling location and sets |
131 | /// to \arg isMacroArg whether the spelling resides inside a macro definition or |
132 | /// a macro argument. |
133 | static SourceLocation getFileSpellingLoc(SourceManager &SM, |
134 | SourceLocation Loc, |
135 | bool &isMacroArg) { |
136 | assert(Loc.isMacroID()); |
137 | SourceLocation SpellLoc = SM.getImmediateSpellingLoc(Loc); |
138 | if (SpellLoc.isMacroID()) |
139 | return getFileSpellingLoc(SM, Loc: SpellLoc, isMacroArg); |
140 | |
141 | isMacroArg = SM.isMacroArgExpansion(Loc); |
142 | return SpellLoc; |
143 | } |
144 | |
145 | static enum CXChildVisitResult findFileIdRefVisit(CXCursor cursor, |
146 | CXCursor parent, |
147 | CXClientData client_data) { |
148 | CXCursor declCursor = clang_getCursorReferenced(cursor); |
149 | if (!clang_isDeclaration(declCursor.kind)) |
150 | return CXChildVisit_Recurse; |
151 | |
152 | const Decl *D = cxcursor::getCursorDecl(Cursor: declCursor); |
153 | if (!D) |
154 | return CXChildVisit_Continue; |
155 | |
156 | FindFileIdRefVisitData *data = (FindFileIdRefVisitData *)client_data; |
157 | if (data->isHit(D)) { |
158 | cursor = cxcursor::getSelectorIdentifierCursor(SelIdx: data->SelectorIdIdx, cursor); |
159 | |
160 | // We are looking for identifiers to highlight so for objc methods (and |
161 | // not a parameter) we can only highlight the selector identifiers. |
162 | if ((cursor.kind == CXCursor_ObjCClassMethodDecl || |
163 | cursor.kind == CXCursor_ObjCInstanceMethodDecl) && |
164 | cxcursor::getSelectorIdentifierIndex(cursor) == -1) |
165 | return CXChildVisit_Recurse; |
166 | |
167 | if (clang_isExpression(cursor.kind)) { |
168 | if (cursor.kind == CXCursor_DeclRefExpr || |
169 | cursor.kind == CXCursor_MemberRefExpr) { |
170 | // continue.. |
171 | |
172 | } else if (cursor.kind == CXCursor_ObjCMessageExpr && |
173 | cxcursor::getSelectorIdentifierIndex(cursor) != -1) { |
174 | // continue.. |
175 | |
176 | } else |
177 | return CXChildVisit_Recurse; |
178 | } |
179 | |
180 | SourceLocation |
181 | Loc = cxloc::translateSourceLocation(L: clang_getCursorLocation(cursor)); |
182 | SourceLocation SelIdLoc = cxcursor::getSelectorIdentifierLoc(cursor); |
183 | if (SelIdLoc.isValid()) |
184 | Loc = SelIdLoc; |
185 | |
186 | ASTContext &Ctx = data->getASTContext(); |
187 | SourceManager &SM = Ctx.getSourceManager(); |
188 | bool isInMacroDef = false; |
189 | if (Loc.isMacroID()) { |
190 | bool isMacroArg; |
191 | Loc = getFileSpellingLoc(SM, Loc, isMacroArg); |
192 | isInMacroDef = !isMacroArg; |
193 | } |
194 | |
195 | // We are looking for identifiers in a specific file. |
196 | std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(Loc); |
197 | if (LocInfo.first != data->FID) |
198 | return CXChildVisit_Recurse; |
199 | |
200 | if (isInMacroDef) { |
201 | // FIXME: For a macro definition make sure that all expansions |
202 | // of it expand to the same reference before allowing to point to it. |
203 | return CXChildVisit_Recurse; |
204 | } |
205 | |
206 | if (data->visitor.visit(data->visitor.context, cursor, |
207 | cxloc::translateSourceRange(Context&: Ctx, R: Loc)) == CXVisit_Break) |
208 | return CXChildVisit_Break; |
209 | } |
210 | return CXChildVisit_Recurse; |
211 | } |
212 | |
213 | static bool findIdRefsInFile(CXTranslationUnit TU, CXCursor declCursor, |
214 | const FileEntry *File, |
215 | CXCursorAndRangeVisitor Visitor) { |
216 | assert(clang_isDeclaration(declCursor.kind)); |
217 | SourceManager &SM = cxtu::getASTUnit(TU)->getSourceManager(); |
218 | |
219 | FileID FID = SM.translateFile(SourceFile: File); |
220 | const Decl *Dcl = cxcursor::getCursorDecl(Cursor: declCursor); |
221 | if (!Dcl) |
222 | return false; |
223 | |
224 | FindFileIdRefVisitData data(TU, FID, Dcl, |
225 | cxcursor::getSelectorIdentifierIndex(cursor: declCursor), |
226 | Visitor); |
227 | |
228 | if (const DeclContext *DC = Dcl->getParentFunctionOrMethod()) { |
229 | return clang_visitChildren(parent: cxcursor::MakeCXCursor(D: cast<Decl>(Val: DC), TU), |
230 | visitor: findFileIdRefVisit, client_data: &data); |
231 | } |
232 | |
233 | SourceRange Range(SM.getLocForStartOfFile(FID), SM.getLocForEndOfFile(FID)); |
234 | CursorVisitor FindIdRefsVisitor(TU, |
235 | findFileIdRefVisit, &data, |
236 | /*VisitPreprocessorLast=*/true, |
237 | /*VisitIncludedEntities=*/false, |
238 | Range, |
239 | /*VisitDeclsOnly=*/true); |
240 | return FindIdRefsVisitor.visitFileRegion(); |
241 | } |
242 | |
243 | namespace { |
244 | |
245 | struct FindFileMacroRefVisitData { |
246 | ASTUnit &Unit; |
247 | const FileEntry *File; |
248 | const IdentifierInfo *Macro; |
249 | CXCursorAndRangeVisitor visitor; |
250 | |
251 | FindFileMacroRefVisitData(ASTUnit &Unit, const FileEntry *File, |
252 | const IdentifierInfo *Macro, |
253 | CXCursorAndRangeVisitor visitor) |
254 | : Unit(Unit), File(File), Macro(Macro), visitor(visitor) { } |
255 | |
256 | ASTContext &getASTContext() const { |
257 | return Unit.getASTContext(); |
258 | } |
259 | }; |
260 | |
261 | } // anonymous namespace |
262 | |
263 | static enum CXChildVisitResult findFileMacroRefVisit(CXCursor cursor, |
264 | CXCursor parent, |
265 | CXClientData client_data) { |
266 | const IdentifierInfo *Macro = nullptr; |
267 | if (cursor.kind == CXCursor_MacroDefinition) |
268 | Macro = getCursorMacroDefinition(C: cursor)->getName(); |
269 | else if (cursor.kind == CXCursor_MacroExpansion) |
270 | Macro = getCursorMacroExpansion(C: cursor).getName(); |
271 | if (!Macro) |
272 | return CXChildVisit_Continue; |
273 | |
274 | FindFileMacroRefVisitData *data = (FindFileMacroRefVisitData *)client_data; |
275 | if (data->Macro != Macro) |
276 | return CXChildVisit_Continue; |
277 | |
278 | SourceLocation |
279 | Loc = cxloc::translateSourceLocation(L: clang_getCursorLocation(cursor)); |
280 | |
281 | ASTContext &Ctx = data->getASTContext(); |
282 | SourceManager &SM = Ctx.getSourceManager(); |
283 | bool isInMacroDef = false; |
284 | if (Loc.isMacroID()) { |
285 | bool isMacroArg; |
286 | Loc = getFileSpellingLoc(SM, Loc, isMacroArg); |
287 | isInMacroDef = !isMacroArg; |
288 | } |
289 | |
290 | // We are looking for identifiers in a specific file. |
291 | std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(Loc); |
292 | if (SM.getFileEntryForID(FID: LocInfo.first) != data->File) |
293 | return CXChildVisit_Continue; |
294 | |
295 | if (isInMacroDef) { |
296 | // FIXME: For a macro definition make sure that all expansions |
297 | // of it expand to the same reference before allowing to point to it. |
298 | return CXChildVisit_Continue; |
299 | } |
300 | |
301 | if (data->visitor.visit(data->visitor.context, cursor, |
302 | cxloc::translateSourceRange(Context&: Ctx, R: Loc)) == CXVisit_Break) |
303 | return CXChildVisit_Break; |
304 | return CXChildVisit_Continue; |
305 | } |
306 | |
307 | static bool findMacroRefsInFile(CXTranslationUnit TU, CXCursor Cursor, |
308 | const FileEntry *File, |
309 | CXCursorAndRangeVisitor Visitor) { |
310 | if (Cursor.kind != CXCursor_MacroDefinition && |
311 | Cursor.kind != CXCursor_MacroExpansion) |
312 | return false; |
313 | |
314 | ASTUnit *Unit = cxtu::getASTUnit(TU); |
315 | SourceManager &SM = Unit->getSourceManager(); |
316 | |
317 | FileID FID = SM.translateFile(SourceFile: File); |
318 | const IdentifierInfo *Macro = nullptr; |
319 | if (Cursor.kind == CXCursor_MacroDefinition) |
320 | Macro = getCursorMacroDefinition(C: Cursor)->getName(); |
321 | else |
322 | Macro = getCursorMacroExpansion(C: Cursor).getName(); |
323 | if (!Macro) |
324 | return false; |
325 | |
326 | FindFileMacroRefVisitData data(*Unit, File, Macro, Visitor); |
327 | |
328 | SourceRange Range(SM.getLocForStartOfFile(FID), SM.getLocForEndOfFile(FID)); |
329 | CursorVisitor FindMacroRefsVisitor(TU, |
330 | findFileMacroRefVisit, &data, |
331 | /*VisitPreprocessorLast=*/false, |
332 | /*VisitIncludedEntities=*/false, |
333 | Range); |
334 | return FindMacroRefsVisitor.visitPreprocessedEntitiesInRegion(); |
335 | } |
336 | |
337 | namespace { |
338 | |
339 | struct FindFileIncludesVisitor { |
340 | ASTUnit &Unit; |
341 | const FileEntry *File; |
342 | CXCursorAndRangeVisitor visitor; |
343 | |
344 | FindFileIncludesVisitor(ASTUnit &Unit, const FileEntry *File, |
345 | CXCursorAndRangeVisitor visitor) |
346 | : Unit(Unit), File(File), visitor(visitor) { } |
347 | |
348 | ASTContext &getASTContext() const { |
349 | return Unit.getASTContext(); |
350 | } |
351 | |
352 | enum CXChildVisitResult visit(CXCursor cursor, CXCursor parent) { |
353 | if (cursor.kind != CXCursor_InclusionDirective) |
354 | return CXChildVisit_Continue; |
355 | |
356 | SourceLocation |
357 | Loc = cxloc::translateSourceLocation(L: clang_getCursorLocation(cursor)); |
358 | |
359 | ASTContext &Ctx = getASTContext(); |
360 | SourceManager &SM = Ctx.getSourceManager(); |
361 | |
362 | // We are looking for includes in a specific file. |
363 | std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(Loc); |
364 | if (SM.getFileEntryForID(FID: LocInfo.first) != File) |
365 | return CXChildVisit_Continue; |
366 | |
367 | if (visitor.visit(visitor.context, cursor, |
368 | cxloc::translateSourceRange(Context&: Ctx, R: Loc)) == CXVisit_Break) |
369 | return CXChildVisit_Break; |
370 | return CXChildVisit_Continue; |
371 | } |
372 | |
373 | static enum CXChildVisitResult visit(CXCursor cursor, CXCursor parent, |
374 | CXClientData client_data) { |
375 | return static_cast<FindFileIncludesVisitor*>(client_data)-> |
376 | visit(cursor, parent); |
377 | } |
378 | }; |
379 | |
380 | } // anonymous namespace |
381 | |
382 | static bool findIncludesInFile(CXTranslationUnit TU, const FileEntry *File, |
383 | CXCursorAndRangeVisitor Visitor) { |
384 | assert(TU && File && Visitor.visit); |
385 | |
386 | ASTUnit *Unit = cxtu::getASTUnit(TU); |
387 | SourceManager &SM = Unit->getSourceManager(); |
388 | |
389 | FileID FID = SM.translateFile(SourceFile: File); |
390 | |
391 | FindFileIncludesVisitor IncludesVisitor(*Unit, File, Visitor); |
392 | |
393 | SourceRange Range(SM.getLocForStartOfFile(FID), SM.getLocForEndOfFile(FID)); |
394 | CursorVisitor InclusionCursorsVisitor(TU, |
395 | FindFileIncludesVisitor::visit, |
396 | &IncludesVisitor, |
397 | /*VisitPreprocessorLast=*/false, |
398 | /*VisitIncludedEntities=*/false, |
399 | Range); |
400 | return InclusionCursorsVisitor.visitPreprocessedEntitiesInRegion(); |
401 | } |
402 | |
403 | |
404 | //===----------------------------------------------------------------------===// |
405 | // libclang public APIs. |
406 | //===----------------------------------------------------------------------===// |
407 | |
408 | extern "C" { |
409 | |
410 | CXResult clang_findReferencesInFile(CXCursor cursor, CXFile file, |
411 | CXCursorAndRangeVisitor visitor) { |
412 | LogRef Log = Logger::make(name: __func__); |
413 | |
414 | if (clang_Cursor_isNull(cursor)) { |
415 | if (Log) |
416 | *Log << "Null cursor" ; |
417 | return CXResult_Invalid; |
418 | } |
419 | if (cursor.kind == CXCursor_NoDeclFound) { |
420 | if (Log) |
421 | *Log << "Got CXCursor_NoDeclFound" ; |
422 | return CXResult_Invalid; |
423 | } |
424 | if (!file) { |
425 | if (Log) |
426 | *Log << "Null file" ; |
427 | return CXResult_Invalid; |
428 | } |
429 | if (!visitor.visit) { |
430 | if (Log) |
431 | *Log << "Null visitor" ; |
432 | return CXResult_Invalid; |
433 | } |
434 | |
435 | if (Log) |
436 | *Log << cursor << " @" << *cxfile::getFileEntryRef(File: file); |
437 | |
438 | ASTUnit *CXXUnit = cxcursor::getCursorASTUnit(Cursor: cursor); |
439 | if (!CXXUnit) |
440 | return CXResult_Invalid; |
441 | |
442 | ASTUnit::ConcurrencyCheck Check(*CXXUnit); |
443 | |
444 | if (cursor.kind == CXCursor_MacroDefinition || |
445 | cursor.kind == CXCursor_MacroExpansion) { |
446 | if (findMacroRefsInFile(TU: cxcursor::getCursorTU(Cursor: cursor), |
447 | Cursor: cursor, |
448 | File: *cxfile::getFileEntryRef(File: file), |
449 | Visitor: visitor)) |
450 | return CXResult_VisitBreak; |
451 | return CXResult_Success; |
452 | } |
453 | |
454 | // We are interested in semantics of identifiers so for C++ constructor exprs |
455 | // prefer type references, e.g.: |
456 | // |
457 | // return MyStruct(); |
458 | // |
459 | // for 'MyStruct' we'll have a cursor pointing at the constructor decl but |
460 | // we are actually interested in the type declaration. |
461 | cursor = cxcursor::getTypeRefCursor(cursor); |
462 | |
463 | CXCursor refCursor = clang_getCursorReferenced(cursor); |
464 | |
465 | if (!clang_isDeclaration(refCursor.kind)) { |
466 | if (Log) |
467 | *Log << "cursor is not referencing a declaration" ; |
468 | return CXResult_Invalid; |
469 | } |
470 | |
471 | if (findIdRefsInFile(TU: cxcursor::getCursorTU(Cursor: cursor), |
472 | declCursor: refCursor, |
473 | File: *cxfile::getFileEntryRef(File: file), |
474 | Visitor: visitor)) |
475 | return CXResult_VisitBreak; |
476 | return CXResult_Success; |
477 | } |
478 | |
479 | CXResult clang_findIncludesInFile(CXTranslationUnit TU, CXFile file, |
480 | CXCursorAndRangeVisitor visitor) { |
481 | if (cxtu::isNotUsableTU(TU)) { |
482 | LOG_BAD_TU(TU); |
483 | return CXResult_Invalid; |
484 | } |
485 | |
486 | LogRef Log = Logger::make(name: __func__); |
487 | if (!file) { |
488 | if (Log) |
489 | *Log << "Null file" ; |
490 | return CXResult_Invalid; |
491 | } |
492 | if (!visitor.visit) { |
493 | if (Log) |
494 | *Log << "Null visitor" ; |
495 | return CXResult_Invalid; |
496 | } |
497 | |
498 | if (Log) |
499 | *Log << TU << " @" << *cxfile::getFileEntryRef(File: file); |
500 | |
501 | ASTUnit *CXXUnit = cxtu::getASTUnit(TU); |
502 | if (!CXXUnit) |
503 | return CXResult_Invalid; |
504 | |
505 | ASTUnit::ConcurrencyCheck Check(*CXXUnit); |
506 | |
507 | if (findIncludesInFile(TU, File: *cxfile::getFileEntryRef(File: file), Visitor: visitor)) |
508 | return CXResult_VisitBreak; |
509 | return CXResult_Success; |
510 | } |
511 | |
512 | static enum CXVisitorResult _visitCursorAndRange(void *context, |
513 | CXCursor cursor, |
514 | CXSourceRange range) { |
515 | CXCursorAndRangeVisitorBlock block = (CXCursorAndRangeVisitorBlock)context; |
516 | return INVOKE_BLOCK2(block, cursor, range); |
517 | } |
518 | |
519 | CXResult clang_findReferencesInFileWithBlock(CXCursor cursor, |
520 | CXFile file, |
521 | CXCursorAndRangeVisitorBlock block) { |
522 | CXCursorAndRangeVisitor visitor = { .context: block, |
523 | .visit: block ? _visitCursorAndRange : nullptr }; |
524 | return clang_findReferencesInFile(cursor, file, visitor); |
525 | } |
526 | |
527 | CXResult clang_findIncludesInFileWithBlock(CXTranslationUnit TU, |
528 | CXFile file, |
529 | CXCursorAndRangeVisitorBlock block) { |
530 | CXCursorAndRangeVisitor visitor = { .context: block, |
531 | .visit: block ? _visitCursorAndRange : nullptr }; |
532 | return clang_findIncludesInFile(TU, file, visitor); |
533 | } |
534 | |
535 | } // end: extern "C" |
536 | |