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 | // -fnodelayed-template-parsing is the default everywhere but on Windows. |
180 | // Set it explicitly so that tests behave the same on Windows as on other |
181 | // platforms. |
182 | // Set -Wno-unused-value because it's often desirable in tests to write |
183 | // expressions with unused value, and we don't want the output to be |
184 | // cluttered with warnings about them. |
185 | "-fsyntax-only" , "-fno-delayed-template-parsing" , "-Wno-unused-value" , |
186 | "-std=" + |
187 | std::string(LangStandard::getLangStandardForKind(K: Std).getName())}; |
188 | AnalysisInputs<NoopAnalysis> AI( |
189 | Code, TargetFuncMatcher, |
190 | [UseBuiltinModel = Options.BuiltinOpts.has_value(), |
191 | &SyntheticFieldCallback](ASTContext &C, Environment &Env) { |
192 | Env.getDataflowAnalysisContext().setSyntheticFieldCallback( |
193 | std::move(SyntheticFieldCallback)); |
194 | return NoopAnalysis( |
195 | C, |
196 | DataflowAnalysisOptions{ |
197 | .BuiltinOpts: UseBuiltinModel ? Env.getDataflowAnalysisContext().getOptions() |
198 | : std::optional<BuiltinOptions>()}); |
199 | }); |
200 | AI.ASTBuildArgs = ASTBuildArgs; |
201 | if (Options.BuiltinOpts) |
202 | AI.BuiltinOptions = *Options.BuiltinOpts; |
203 | return checkDataflow<NoopAnalysis>( |
204 | AI: std::move(AI), |
205 | /*VerifyResults=*/ |
206 | [&VerifyResults]( |
207 | const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
208 | const AnalysisOutputs &AO) { VerifyResults(Results, AO.ASTCtx); }); |
209 | } |
210 | |
211 | const ValueDecl *test::findValueDecl(ASTContext &ASTCtx, llvm::StringRef Name) { |
212 | auto TargetNodes = match( |
213 | Matcher: valueDecl(unless(indirectFieldDecl()), hasName(Name)).bind(ID: "v" ), Context&: ASTCtx); |
214 | assert(TargetNodes.size() == 1 && "Name must be unique" ); |
215 | auto *const Result = selectFirst<ValueDecl>(BoundTo: "v" , Results: TargetNodes); |
216 | assert(Result != nullptr); |
217 | return Result; |
218 | } |
219 | |
220 | const IndirectFieldDecl *test::findIndirectFieldDecl(ASTContext &ASTCtx, |
221 | llvm::StringRef Name) { |
222 | auto TargetNodes = match(Matcher: indirectFieldDecl(hasName(Name)).bind(ID: "i" ), Context&: ASTCtx); |
223 | assert(TargetNodes.size() == 1 && "Name must be unique" ); |
224 | const auto *Result = selectFirst<IndirectFieldDecl>(BoundTo: "i" , Results: TargetNodes); |
225 | assert(Result != nullptr); |
226 | return Result; |
227 | } |
228 | |
229 | std::vector<const Formula *> test::parseFormulas(Arena &A, StringRef Lines) { |
230 | std::vector<const Formula *> Result; |
231 | while (!Lines.empty()) { |
232 | auto [First, Rest] = Lines.split(Separator: '\n'); |
233 | Lines = Rest; |
234 | if (First.trim().empty()) |
235 | continue; |
236 | if (auto F = A.parseFormula(First)) |
237 | Result.push_back(x: &*F); |
238 | else |
239 | ADD_FAILURE() << llvm::toString(E: F.takeError()); |
240 | } |
241 | return Result; |
242 | } |
243 | |