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
27using namespace clang;
28using namespace dataflow;
29using namespace ast_matchers;
30
31static bool
32isAnnotationDirectlyAfterStatement(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
50llvm::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
71llvm::Expected<llvm::DenseMap<const Stmt *, std::string>>
72test::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
157llvm::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
169llvm::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
211const 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
220const 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
229std::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

source code of clang/unittests/Analysis/FlowSensitive/TestingSupport.cpp