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