| 1 | #include "TestingSupport.h" |
| 2 | #include "clang/AST/ASTContext.h" |
| 3 | #include "clang/AST/Decl.h" |
| 4 | #include "clang/AST/Stmt.h" |
| 5 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
| 6 | #include "clang/ASTMatchers/ASTMatchers.h" |
| 7 | #include "clang/Analysis/FlowSensitive/NoopAnalysis.h" |
| 8 | #include "clang/Basic/LLVM.h" |
| 9 | #include "clang/Basic/LangOptions.h" |
| 10 | #include "clang/Basic/SourceLocation.h" |
| 11 | #include "clang/Basic/SourceManager.h" |
| 12 | #include "clang/Basic/TokenKinds.h" |
| 13 | #include "clang/Lex/Lexer.h" |
| 14 | #include "llvm/ADT/DenseMap.h" |
| 15 | #include "llvm/ADT/StringRef.h" |
| 16 | #include "llvm/ADT/StringSet.h" |
| 17 | #include "llvm/Support/Error.h" |
| 18 | #include "llvm/Testing/Annotations/Annotations.h" |
| 19 | #include "gtest/gtest.h" |
| 20 | #include <cassert> |
| 21 | #include <functional> |
| 22 | #include <string> |
| 23 | #include <system_error> |
| 24 | #include <utility> |
| 25 | #include <vector> |
| 26 | |
| 27 | using namespace clang; |
| 28 | using namespace dataflow; |
| 29 | using namespace ast_matchers; |
| 30 | |
| 31 | static bool |
| 32 | isAnnotationDirectlyAfterStatement(const Stmt *Stmt, unsigned AnnotationBegin, |
| 33 | const SourceManager &SourceManager, |
| 34 | const LangOptions &LangOptions) { |
| 35 | auto NextToken = |
| 36 | Lexer::findNextToken(Loc: Stmt->getEndLoc(), SM: SourceManager, LangOpts: LangOptions); |
| 37 | |
| 38 | while (NextToken && SourceManager.getFileOffset(SpellingLoc: NextToken->getLocation()) < |
| 39 | AnnotationBegin) { |
| 40 | if (NextToken->isNot(K: tok::semi)) |
| 41 | return false; |
| 42 | |
| 43 | NextToken = Lexer::findNextToken(Loc: NextToken->getEndLoc(), SM: SourceManager, |
| 44 | LangOpts: LangOptions); |
| 45 | } |
| 46 | |
| 47 | return true; |
| 48 | } |
| 49 | |
| 50 | llvm::DenseMap<unsigned, std::string> test::buildLineToAnnotationMapping( |
| 51 | const SourceManager &SM, const LangOptions &LangOpts, |
| 52 | SourceRange BoundingRange, llvm::Annotations AnnotatedCode) { |
| 53 | CharSourceRange CharBoundingRange = |
| 54 | Lexer::getAsCharRange(Range: BoundingRange, SM, LangOpts); |
| 55 | |
| 56 | llvm::DenseMap<unsigned, std::string> LineNumberToContent; |
| 57 | auto Code = AnnotatedCode.code(); |
| 58 | auto Annotations = AnnotatedCode.ranges(); |
| 59 | for (auto &AnnotationRange : Annotations) { |
| 60 | SourceLocation Loc = SM.getLocForStartOfFile(FID: SM.getMainFileID()) |
| 61 | .getLocWithOffset(Offset: AnnotationRange.Begin); |
| 62 | if (SM.isPointWithin(Location: Loc, Start: CharBoundingRange.getBegin(), |
| 63 | End: CharBoundingRange.getEnd())) { |
| 64 | LineNumberToContent[SM.getPresumedLineNumber(Loc)] = |
| 65 | Code.slice(Start: AnnotationRange.Begin, End: AnnotationRange.End).str(); |
| 66 | } |
| 67 | } |
| 68 | return LineNumberToContent; |
| 69 | } |
| 70 | |
| 71 | llvm::Expected<llvm::DenseMap<const Stmt *, std::string>> |
| 72 | test::buildStatementToAnnotationMapping(const FunctionDecl *Func, |
| 73 | llvm::Annotations AnnotatedCode) { |
| 74 | llvm::DenseMap<const Stmt *, std::string> Result; |
| 75 | llvm::StringSet<> ExistingAnnotations; |
| 76 | |
| 77 | auto StmtMatcher = |
| 78 | findAll(Matcher: stmt(unless(anyOf(hasParent(expr()), hasParent(returnStmt())))) |
| 79 | .bind(ID: "stmt" )); |
| 80 | |
| 81 | // This map should stay sorted because the binding algorithm relies on the |
| 82 | // ordering of statement offsets |
| 83 | std::map<unsigned, const Stmt *> Stmts; |
| 84 | auto &Context = Func->getASTContext(); |
| 85 | auto &SourceManager = Context.getSourceManager(); |
| 86 | |
| 87 | for (auto &Match : match(StmtMatcher, *Func->getBody(), Context)) { |
| 88 | const auto *S = Match.getNodeAs<Stmt>("stmt" ); |
| 89 | unsigned Offset = SourceManager.getFileOffset(S->getEndLoc()); |
| 90 | Stmts[Offset] = S; |
| 91 | } |
| 92 | |
| 93 | unsigned FunctionBeginOffset = |
| 94 | SourceManager.getFileOffset(Func->getBeginLoc()); |
| 95 | unsigned FunctionEndOffset = SourceManager.getFileOffset(Func->getEndLoc()); |
| 96 | |
| 97 | std::vector<llvm::Annotations::Range> Annotations = AnnotatedCode.ranges(); |
| 98 | llvm::erase_if(C&: Annotations, P: [=](llvm::Annotations::Range R) { |
| 99 | return R.Begin < FunctionBeginOffset || R.End >= FunctionEndOffset; |
| 100 | }); |
| 101 | std::reverse(first: Annotations.begin(), last: Annotations.end()); |
| 102 | auto Code = AnnotatedCode.code(); |
| 103 | |
| 104 | unsigned I = 0; |
| 105 | for (auto OffsetAndStmt = Stmts.rbegin(); OffsetAndStmt != Stmts.rend(); |
| 106 | OffsetAndStmt++) { |
| 107 | unsigned Offset = OffsetAndStmt->first; |
| 108 | const Stmt *Stmt = OffsetAndStmt->second; |
| 109 | |
| 110 | if (I < Annotations.size() && Annotations[I].Begin >= Offset) { |
| 111 | auto Range = Annotations[I]; |
| 112 | |
| 113 | if (!isAnnotationDirectlyAfterStatement(Stmt, Range.Begin, SourceManager, |
| 114 | Context.getLangOpts())) { |
| 115 | return llvm::createStringError( |
| 116 | std::make_error_code(e: std::errc::invalid_argument), |
| 117 | "Annotation is not placed after a statement: %s" , |
| 118 | SourceManager.getLocForStartOfFile(SourceManager.getMainFileID()) |
| 119 | .getLocWithOffset(Offset) |
| 120 | .printToString(SourceManager) |
| 121 | .data()); |
| 122 | } |
| 123 | |
| 124 | auto Annotation = Code.slice(Start: Range.Begin, End: Range.End).str(); |
| 125 | if (!ExistingAnnotations.insert(key: Annotation).second) { |
| 126 | return llvm::createStringError( |
| 127 | EC: std::make_error_code(e: std::errc::invalid_argument), |
| 128 | Fmt: "Repeated use of annotation: %s" , Vals: Annotation.data()); |
| 129 | } |
| 130 | Result[Stmt] = std::move(Annotation); |
| 131 | |
| 132 | I++; |
| 133 | |
| 134 | if (I < Annotations.size() && Annotations[I].Begin >= Offset) { |
| 135 | return llvm::createStringError( |
| 136 | std::make_error_code(e: std::errc::invalid_argument), |
| 137 | "Multiple annotations bound to the statement at the location: %s" , |
| 138 | Stmt->getBeginLoc().printToString(SM: SourceManager).data()); |
| 139 | } |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | if (I < Annotations.size()) { |
| 144 | return llvm::createStringError( |
| 145 | std::make_error_code(e: std::errc::invalid_argument), |
| 146 | "Not all annotations were bound to statements. Unbound annotation at: " |
| 147 | "%s" , |
| 148 | SourceManager.getLocForStartOfFile(SourceManager.getMainFileID()) |
| 149 | .getLocWithOffset(Annotations[I].Begin) |
| 150 | .printToString(SourceManager) |
| 151 | .data()); |
| 152 | } |
| 153 | |
| 154 | return Result; |
| 155 | } |
| 156 | |
| 157 | llvm::Error test::checkDataflowWithNoopAnalysis( |
| 158 | llvm::StringRef Code, |
| 159 | std::function< |
| 160 | void(const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &, |
| 161 | ASTContext &)> |
| 162 | VerifyResults, |
| 163 | DataflowAnalysisOptions Options, LangStandard::Kind Std, |
| 164 | llvm::StringRef TargetFun) { |
| 165 | return checkDataflowWithNoopAnalysis(Code, TargetFuncMatcher: ast_matchers::hasName(Name: TargetFun), |
| 166 | VerifyResults, Options, Std); |
| 167 | } |
| 168 | |
| 169 | llvm::Error test::checkDataflowWithNoopAnalysis( |
| 170 | llvm::StringRef Code, |
| 171 | ast_matchers::internal::Matcher<FunctionDecl> TargetFuncMatcher, |
| 172 | std::function< |
| 173 | void(const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &, |
| 174 | ASTContext &)> |
| 175 | VerifyResults, |
| 176 | DataflowAnalysisOptions Options, LangStandard::Kind Std, |
| 177 | std::function<llvm::StringMap<QualType>(QualType)> SyntheticFieldCallback) { |
| 178 | llvm::SmallVector<std::string, 3> ASTBuildArgs = { |
| 179 | "-fsyntax-only" , |
| 180 | // -fnodelayed-template-parsing is the default everywhere but on Windows. |
| 181 | // Set it explicitly so that tests behave the same on Windows as on other |
| 182 | // platforms. |
| 183 | "-fno-delayed-template-parsing" , |
| 184 | // Set -Wno-unused-value because it's often desirable in tests to write |
| 185 | // expressions with unused value, and we don't want the output to be |
| 186 | // cluttered with warnings about them. |
| 187 | "-Wno-unused-value" , |
| 188 | // Some build environments don't have RTTI enabled by default. |
| 189 | // Enable it explicitly to make sure tests work in all environments. |
| 190 | "-frtti" , |
| 191 | "-std=" + |
| 192 | std::string(LangStandard::getLangStandardForKind(K: Std).getName())}; |
| 193 | AnalysisInputs<NoopAnalysis> AI( |
| 194 | Code, TargetFuncMatcher, |
| 195 | [UseBuiltinModel = Options.BuiltinOpts.has_value(), |
| 196 | &SyntheticFieldCallback](ASTContext &C, Environment &Env) { |
| 197 | Env.getDataflowAnalysisContext().setSyntheticFieldCallback( |
| 198 | std::move(SyntheticFieldCallback)); |
| 199 | return NoopAnalysis( |
| 200 | C, |
| 201 | DataflowAnalysisOptions{ |
| 202 | .BuiltinOpts: UseBuiltinModel ? Env.getDataflowAnalysisContext().getOptions() |
| 203 | : std::optional<BuiltinOptions>()}); |
| 204 | }); |
| 205 | AI.ASTBuildArgs = ASTBuildArgs; |
| 206 | if (Options.BuiltinOpts) |
| 207 | AI.BuiltinOptions = *Options.BuiltinOpts; |
| 208 | return checkDataflow<NoopAnalysis>( |
| 209 | AI: std::move(AI), |
| 210 | /*VerifyResults=*/ |
| 211 | [&VerifyResults]( |
| 212 | const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| 213 | const AnalysisOutputs &AO) { VerifyResults(Results, AO.ASTCtx); }); |
| 214 | } |
| 215 | |
| 216 | const ValueDecl *test::findValueDecl(ASTContext &ASTCtx, llvm::StringRef Name) { |
| 217 | auto TargetNodes = match( |
| 218 | Matcher: valueDecl(unless(indirectFieldDecl()), hasName(Name)).bind(ID: "v" ), Context&: ASTCtx); |
| 219 | assert(TargetNodes.size() == 1 && "Name must be unique" ); |
| 220 | auto *const Result = selectFirst<ValueDecl>(BoundTo: "v" , Results: TargetNodes); |
| 221 | assert(Result != nullptr); |
| 222 | return Result; |
| 223 | } |
| 224 | |
| 225 | const IndirectFieldDecl *test::findIndirectFieldDecl(ASTContext &ASTCtx, |
| 226 | llvm::StringRef Name) { |
| 227 | auto TargetNodes = match(Matcher: indirectFieldDecl(hasName(Name)).bind(ID: "i" ), Context&: ASTCtx); |
| 228 | assert(TargetNodes.size() == 1 && "Name must be unique" ); |
| 229 | const auto *Result = selectFirst<IndirectFieldDecl>(BoundTo: "i" , Results: TargetNodes); |
| 230 | assert(Result != nullptr); |
| 231 | return Result; |
| 232 | } |
| 233 | |
| 234 | std::vector<const Formula *> test::parseFormulas(Arena &A, StringRef Lines) { |
| 235 | std::vector<const Formula *> Result; |
| 236 | while (!Lines.empty()) { |
| 237 | auto [First, Rest] = Lines.split(Separator: '\n'); |
| 238 | Lines = Rest; |
| 239 | if (First.trim().empty()) |
| 240 | continue; |
| 241 | if (auto F = A.parseFormula(First)) |
| 242 | Result.push_back(x: &*F); |
| 243 | else |
| 244 | ADD_FAILURE() << llvm::toString(E: F.takeError()); |
| 245 | } |
| 246 | return Result; |
| 247 | } |
| 248 | |