| #include "TestingSupport.h" |
| #include "clang/AST/ASTContext.h" |
| #include "clang/AST/Decl.h" |
| #include "clang/AST/Stmt.h" |
| #include "clang/ASTMatchers/ASTMatchFinder.h" |
| #include "clang/ASTMatchers/ASTMatchers.h" |
| #include "clang/Basic/LLVM.h" |
| #include "clang/Basic/LangOptions.h" |
| #include "clang/Basic/SourceLocation.h" |
| #include "clang/Basic/SourceManager.h" |
| #include "clang/Basic/TokenKinds.h" |
| #include "clang/Lex/Lexer.h" |
| #include "llvm/ADT/DenseMap.h" |
| #include "llvm/ADT/StringRef.h" |
| #include "llvm/ADT/StringSet.h" |
| #include "llvm/Support/Error.h" |
| #include "llvm/Testing/Annotations/Annotations.h" |
| #include <cassert> |
| #include <functional> |
| #include <memory> |
| #include <string> |
| #include <system_error> |
| #include <utility> |
| #include <vector> |
| |
| using namespace clang; |
| using namespace dataflow; |
| using namespace ast_matchers; |
| |
| static bool |
| isAnnotationDirectlyAfterStatement(const Stmt *Stmt, unsigned AnnotationBegin, |
| const SourceManager &SourceManager, |
| const LangOptions &LangOptions) { |
| auto NextToken = |
| Lexer::findNextToken(Stmt->getEndLoc(), SourceManager, LangOptions); |
| |
| while (NextToken && SourceManager.getFileOffset(NextToken->getLocation()) < |
| AnnotationBegin) { |
| if (NextToken->isNot(tok::semi)) |
| return false; |
| |
| NextToken = Lexer::findNextToken(NextToken->getEndLoc(), SourceManager, |
| LangOptions); |
| } |
| |
| return true; |
| } |
| |
| llvm::DenseMap<unsigned, std::string> test::buildLineToAnnotationMapping( |
| const SourceManager &SM, const LangOptions &LangOpts, |
| SourceRange BoundingRange, llvm::Annotations AnnotatedCode) { |
| CharSourceRange CharBoundingRange = |
| Lexer::getAsCharRange(BoundingRange, SM, LangOpts); |
| |
| llvm::DenseMap<unsigned, std::string> LineNumberToContent; |
| auto Code = AnnotatedCode.code(); |
| auto Annotations = AnnotatedCode.ranges(); |
| for (auto &AnnotationRange : Annotations) { |
| SourceLocation Loc = SM.getLocForStartOfFile(SM.getMainFileID()) |
| .getLocWithOffset(AnnotationRange.Begin); |
| if (SM.isPointWithin(Loc, CharBoundingRange.getBegin(), |
| CharBoundingRange.getEnd())) { |
| LineNumberToContent[SM.getPresumedLineNumber(Loc)] = |
| Code.slice(AnnotationRange.Begin, AnnotationRange.End).str(); |
| } |
| } |
| return LineNumberToContent; |
| } |
| |
| llvm::Expected<llvm::DenseMap<const Stmt *, std::string>> |
| test::buildStatementToAnnotationMapping(const FunctionDecl *Func, |
| llvm::Annotations AnnotatedCode) { |
| llvm::DenseMap<const Stmt *, std::string> Result; |
| llvm::StringSet<> ExistingAnnotations; |
| |
| auto StmtMatcher = |
| findAll(stmt(unless(anyOf(hasParent(expr()), hasParent(returnStmt())))) |
| .bind("stmt")); |
| |
| // This map should stay sorted because the binding algorithm relies on the |
| // ordering of statement offsets |
| std::map<unsigned, const Stmt *> Stmts; |
| auto &Context = Func->getASTContext(); |
| auto &SourceManager = Context.getSourceManager(); |
| |
| for (auto &Match : match(StmtMatcher, *Func->getBody(), Context)) { |
| const auto *S = Match.getNodeAs<Stmt>("stmt"); |
| unsigned Offset = SourceManager.getFileOffset(S->getEndLoc()); |
| Stmts[Offset] = S; |
| } |
| |
| unsigned FunctionBeginOffset = |
| SourceManager.getFileOffset(Func->getBeginLoc()); |
| unsigned FunctionEndOffset = SourceManager.getFileOffset(Func->getEndLoc()); |
| |
| std::vector<llvm::Annotations::Range> Annotations = AnnotatedCode.ranges(); |
| llvm::erase_if(Annotations, [=](llvm::Annotations::Range R) { |
| return R.Begin < FunctionBeginOffset || R.End >= FunctionEndOffset; |
| }); |
| std::reverse(Annotations.begin(), Annotations.end()); |
| auto Code = AnnotatedCode.code(); |
| |
| unsigned I = 0; |
| for (auto OffsetAndStmt = Stmts.rbegin(); OffsetAndStmt != Stmts.rend(); |
| OffsetAndStmt++) { |
| unsigned Offset = OffsetAndStmt->first; |
| const Stmt *Stmt = OffsetAndStmt->second; |
| |
| if (I < Annotations.size() && Annotations[I].Begin >= Offset) { |
| auto Range = Annotations[I]; |
| |
| if (!isAnnotationDirectlyAfterStatement(Stmt, Range.Begin, SourceManager, |
| Context.getLangOpts())) { |
| return llvm::createStringError( |
| std::make_error_code(std::errc::invalid_argument), |
| "Annotation is not placed after a statement: %s", |
| SourceManager.getLocForStartOfFile(SourceManager.getMainFileID()) |
| .getLocWithOffset(Offset) |
| .printToString(SourceManager) |
| .data()); |
| } |
| |
| auto Annotation = Code.slice(Range.Begin, Range.End).str(); |
| if (!ExistingAnnotations.insert(Annotation).second) { |
| return llvm::createStringError( |
| std::make_error_code(std::errc::invalid_argument), |
| "Repeated use of annotation: %s", Annotation.data()); |
| } |
| Result[Stmt] = std::move(Annotation); |
| |
| I++; |
| |
| if (I < Annotations.size() && Annotations[I].Begin >= Offset) { |
| return llvm::createStringError( |
| std::make_error_code(std::errc::invalid_argument), |
| "Multiple annotations bound to the statement at the location: %s", |
| Stmt->getBeginLoc().printToString(SourceManager).data()); |
| } |
| } |
| } |
| |
| if (I < Annotations.size()) { |
| return llvm::createStringError( |
| std::make_error_code(std::errc::invalid_argument), |
| "Not all annotations were bound to statements. Unbound annotation at: " |
| "%s", |
| SourceManager.getLocForStartOfFile(SourceManager.getMainFileID()) |
| .getLocWithOffset(Annotations[I].Begin) |
| .printToString(SourceManager) |
| .data()); |
| } |
| |
| return Result; |
| } |
| |
| llvm::Error test::checkDataflowWithNoopAnalysis( |
| llvm::StringRef Code, |
| std::function< |
| void(const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &, |
| ASTContext &)> |
| VerifyResults, |
| DataflowAnalysisOptions Options, LangStandard::Kind Std, |
| llvm::StringRef TargetFun) { |
| using ast_matchers::hasName; |
| llvm::SmallVector<std::string, 3> ASTBuildArgs = { |
| // -fnodelayed-template-parsing is the default everywhere but on Windows. |
| // Set it explicitly so that tests behave the same on Windows as on other |
| // platforms. |
| "-fsyntax-only", "-fno-delayed-template-parsing", |
| "-std=" + |
| std::string(LangStandard::getLangStandardForKind(Std).getName())}; |
| AnalysisInputs<NoopAnalysis> AI( |
| Code, hasName(TargetFun), |
| [UseBuiltinModel = Options.BuiltinOpts.has_value()](ASTContext &C, |
| Environment &Env) { |
| return NoopAnalysis( |
| C, |
| DataflowAnalysisOptions{ |
| UseBuiltinModel ? Env.getDataflowAnalysisContext().getOptions() |
| : std::optional<BuiltinOptions>()}); |
| }); |
| AI.ASTBuildArgs = ASTBuildArgs; |
| if (Options.BuiltinOpts) |
| AI.BuiltinOptions = *Options.BuiltinOpts; |
| return checkDataflow<NoopAnalysis>( |
| std::move(AI), |
| /*VerifyResults=*/ |
| [&VerifyResults]( |
| const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| const AnalysisOutputs &AO) { VerifyResults(Results, AO.ASTCtx); }); |
| } |
| |
| const ValueDecl *test::findValueDecl(ASTContext &ASTCtx, llvm::StringRef Name) { |
| auto TargetNodes = match( |
| valueDecl(unless(indirectFieldDecl()), hasName(Name)).bind("v"), ASTCtx); |
| assert(TargetNodes.size() == 1 && "Name must be unique"); |
| auto *const Result = selectFirst<ValueDecl>("v", TargetNodes); |
| assert(Result != nullptr); |
| return Result; |
| } |
| |
| const IndirectFieldDecl *test::findIndirectFieldDecl(ASTContext &ASTCtx, |
| llvm::StringRef Name) { |
| auto TargetNodes = match(indirectFieldDecl(hasName(Name)).bind("i"), ASTCtx); |
| assert(TargetNodes.size() == 1 && "Name must be unique"); |
| const auto *Result = selectFirst<IndirectFieldDecl>("i", TargetNodes); |
| assert(Result != nullptr); |
| return Result; |
| } |