1 #include "AnalysisInternal.h"
2 #include "clang/AST/ASTContext.h"
3 #include "clang/Basic/FileManager.h"
4 #include "clang/Frontend/TextDiagnostic.h"
5 #include "clang/Testing/TestAST.h"
6 #include "llvm/Support/Error.h"
7 #include "llvm/Testing/Support/Annotations.h"
8 #include "gtest/gtest.h"
9 
10 namespace clang {
11 namespace include_cleaner {
12 namespace {
13 
14 // Specifies a test of which symbols are referenced by a piece of code.
15 //
16 // Example:
17 //   Target:      int ^foo();
18 //   Referencing: int x = ^foo();
19 // There must be exactly one referencing location marked.
20 void testWalk(llvm::StringRef TargetCode, llvm::StringRef ReferencingCode) {
21   llvm::Annotations Target(TargetCode);
22   llvm::Annotations Referencing(ReferencingCode);
23 
24   TestInputs Inputs(Referencing.code());
25   Inputs.ExtraFiles["target.h"] = Target.code().str();
26   Inputs.ExtraArgs.push_back("-include");
27   Inputs.ExtraArgs.push_back("target.h");
28   TestAST AST(Inputs);
29   const auto &SM = AST.sourceManager();
30 
31   // We're only going to record references from the nominated point,
32   // to the target file.
33   FileID ReferencingFile = SM.getMainFileID();
34   SourceLocation ReferencingLoc =
35       SM.getComposedLoc(ReferencingFile, Referencing.point());
36   FileID TargetFile = SM.translateFile(
37       llvm::cantFail(AST.fileManager().getFileRef("target.h")));
38 
39   // Perform the walk, and capture the offsets of the referenced targets.
40   std::vector<size_t> ReferencedOffsets;
41   for (Decl *D : AST.context().getTranslationUnitDecl()->decls()) {
42     if (ReferencingFile != SM.getDecomposedExpansionLoc(D->getLocation()).first)
43       continue;
44     walkAST(*D, [&](SourceLocation Loc, NamedDecl &ND) {
45       if (SM.getFileLoc(Loc) != ReferencingLoc)
46         return;
47       auto NDLoc = SM.getDecomposedLoc(SM.getFileLoc(ND.getLocation()));
48       if (NDLoc.first != TargetFile)
49         return;
50       ReferencedOffsets.push_back(NDLoc.second);
51     });
52   }
53   llvm::sort(ReferencedOffsets);
54 
55   // Compare results to the expected points.
56   // For each difference, show the target point in context, like a diagnostic.
57   std::string DiagBuf;
58   llvm::raw_string_ostream DiagOS(DiagBuf);
59   auto *DiagOpts = new DiagnosticOptions();
60   DiagOpts->ShowLevel = 0;
61   DiagOpts->ShowNoteIncludeStack = 0;
62   TextDiagnostic Diag(DiagOS, AST.context().getLangOpts(), DiagOpts);
63   auto DiagnosePoint = [&](const char *Message, unsigned Offset) {
64     Diag.emitDiagnostic(
65         FullSourceLoc(SM.getComposedLoc(TargetFile, Offset), SM),
66         DiagnosticsEngine::Note, Message, {}, {});
67   };
68   for (auto Expected : Target.points())
69     if (!llvm::is_contained(ReferencedOffsets, Expected))
70       DiagnosePoint("location not marked used", Expected);
71   for (auto Actual : ReferencedOffsets)
72     if (!llvm::is_contained(Target.points(), Actual))
73       DiagnosePoint("location unexpectedly used", Actual);
74 
75   // If there were any differences, we print the entire referencing code once.
76   if (!DiagBuf.empty())
77     ADD_FAILURE() << DiagBuf << "\nfrom code:\n" << ReferencingCode;
78 }
79 
80 TEST(WalkAST, DeclRef) {
81   testWalk("int ^x;", "int y = ^x;");
82   testWalk("int ^foo();", "int y = ^foo();");
83   testWalk("namespace ns { int ^x; }", "int y = ns::^x;");
84   testWalk("struct S { static int ^x; };", "int y = S::^x;");
85   // Canonical declaration only.
86   testWalk("extern int ^x; int x;", "int y = ^x;");
87 }
88 
89 TEST(WalkAST, TagType) {
90   testWalk("struct ^S {};", "^S *y;");
91   testWalk("enum ^E {};", "^E *y;");
92   testWalk("struct ^S { static int x; };", "int y = ^S::x;");
93 }
94 
95 TEST(WalkAST, Alias) {
96   testWalk(R"cpp(
97     namespace ns { int x; }
98     using ns::^x;
99   )cpp",
100            "int y = ^x;");
101   testWalk(R"cpp(
102     namespace ns { struct S; } // Not used
103     using ns::S; // FIXME: S should be used
104   )cpp",
105            "^S *x;");
106 }
107 
108 } // namespace
109 } // namespace include_cleaner
110 } // namespace clang
111