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
19using namespace clang;
20using namespace cxcursor;
21using namespace cxindex;
22
23static 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
44namespace {
45
46struct 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(D: 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
111private:
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.
133static 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
145static 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
213static 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
243namespace {
244
245struct 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
263static 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
307static 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
337namespace {
338
339struct 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
382static 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
408extern "C" {
409
410CXResult 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
479CXResult 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
512static 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
519CXResult 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
527CXResult 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