#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/Analysis/FlowSensitive/NoopAnalysis.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 "gtest/gtest.h" #include #include #include #include #include #include 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 test::buildLineToAnnotationMapping( const SourceManager &SM, const LangOptions &LangOpts, SourceRange BoundingRange, llvm::Annotations AnnotatedCode) { CharSourceRange CharBoundingRange = Lexer::getAsCharRange(BoundingRange, SM, LangOpts); llvm::DenseMap 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> test::buildStatementToAnnotationMapping(const FunctionDecl *Func, llvm::Annotations AnnotatedCode) { llvm::DenseMap 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 Stmts; auto &Context = Func->getASTContext(); auto &SourceManager = Context.getSourceManager(); for (auto &Match : match(StmtMatcher, *Func->getBody(), Context)) { const auto *S = Match.getNodeAs("stmt"); unsigned Offset = SourceManager.getFileOffset(S->getEndLoc()); Stmts[Offset] = S; } unsigned FunctionBeginOffset = SourceManager.getFileOffset(Func->getBeginLoc()); unsigned FunctionEndOffset = SourceManager.getFileOffset(Func->getEndLoc()); std::vector 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> &, ASTContext &)> VerifyResults, DataflowAnalysisOptions Options, LangStandard::Kind Std, llvm::StringRef TargetFun) { return checkDataflowWithNoopAnalysis(Code, ast_matchers::hasName(TargetFun), VerifyResults, Options, Std); } llvm::Error test::checkDataflowWithNoopAnalysis( llvm::StringRef Code, ast_matchers::internal::Matcher TargetFuncMatcher, std::function< void(const llvm::StringMap> &, ASTContext &)> VerifyResults, DataflowAnalysisOptions Options, LangStandard::Kind Std, std::function(QualType)> SyntheticFieldCallback) { llvm::SmallVector ASTBuildArgs = { "-fsyntax-only", // -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. "-fno-delayed-template-parsing", // Set -Wno-unused-value because it's often desirable in tests to write // expressions with unused value, and we don't want the output to be // cluttered with warnings about them. "-Wno-unused-value", // Some build environments don't have RTTI enabled by default. // Enable it explicitly to make sure tests work in all environments. "-frtti", "-std=" + std::string(LangStandard::getLangStandardForKind(Std).getName())}; AnalysisInputs AI( Code, TargetFuncMatcher, [UseBuiltinModel = Options.BuiltinOpts.has_value(), &SyntheticFieldCallback](ASTContext &C, Environment &Env) { Env.getDataflowAnalysisContext().setSyntheticFieldCallback( std::move(SyntheticFieldCallback)); return NoopAnalysis( C, DataflowAnalysisOptions{ UseBuiltinModel ? Env.getDataflowAnalysisContext().getOptions() : std::optional()}); }); AI.ASTBuildArgs = ASTBuildArgs; if (Options.BuiltinOpts) AI.BuiltinOptions = *Options.BuiltinOpts; return checkDataflow( std::move(AI), /*VerifyResults=*/ [&VerifyResults]( const llvm::StringMap> &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("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("i", TargetNodes); assert(Result != nullptr); return Result; } std::vector test::parseFormulas(Arena &A, StringRef Lines) { std::vector Result; while (!Lines.empty()) { auto [First, Rest] = Lines.split('\n'); Lines = Rest; if (First.trim().empty()) continue; if (auto F = A.parseFormula(First)) Result.push_back(&*F); else ADD_FAILURE() << llvm::toString(F.takeError()); } return Result; }