diff --git a/clang/unittests/Analysis/FlowSensitive/TestingSupport.h b/clang/unittests/Analysis/FlowSensitive/TestingSupport.h --- a/clang/unittests/Analysis/FlowSensitive/TestingSupport.h +++ b/clang/unittests/Analysis/FlowSensitive/TestingSupport.h @@ -144,7 +144,7 @@ /// Required. Input code that is analyzed. llvm::StringRef Code; - /// Required. The body of the function which matches this matcher is analyzed. + /// Required. All functions that match this matcher are analyzed. ast_matchers::internal::Matcher TargetFuncMatcher; /// Required. The analysis to be run is constructed with this function that /// takes as argument the AST generated from the code being analyzed and the @@ -176,15 +176,16 @@ buildStatementToAnnotationMapping(const FunctionDecl *Func, llvm::Annotations AnnotatedCode); -/// Returns line numbers and content of the annotations in `AnnotatedCode`. -llvm::DenseMap -buildLineToAnnotationMapping(SourceManager &SM, - llvm::Annotations AnnotatedCode); +/// Returns line numbers and content of the annotations in `AnnotatedCode` +/// within the token range `BoundingRange`. +llvm::DenseMap buildLineToAnnotationMapping( + const SourceManager &SM, const LangOptions &LangOpts, + SourceRange BoundingRange, llvm::Annotations AnnotatedCode); -/// Runs dataflow specified from `AI.MakeAnalysis` and `AI.PostVisitCFG` on the -/// body of the function that matches `AI.TargetFuncMatcher` in `AI.Code`. -/// Given the analysis outputs, `VerifyResults` checks that the results from the -/// analysis are correct. +/// Runs dataflow specified from `AI.MakeAnalysis` and `AI.PostVisitCFG` on all +/// functions that match `AI.TargetFuncMatcher` in `AI.Code`. Given the +/// analysis outputs, `VerifyResults` checks that the results from the analysis +/// are correct. /// /// Requirements: /// @@ -212,36 +213,13 @@ "they were printed to the test log"); } - // Get AST node of target function. - const FunctionDecl *Target = ast_matchers::selectFirst( - "target", ast_matchers::match( - ast_matchers::functionDecl(ast_matchers::isDefinition(), - AI.TargetFuncMatcher) - .bind("target"), - Context)); - if (Target == nullptr) - return llvm::make_error( - llvm::errc::invalid_argument, "Could not find target function."); - - // Build control flow graph from body of target function. - auto MaybeCFCtx = - ControlFlowContext::build(Target, *Target->getBody(), Context); - if (!MaybeCFCtx) - return MaybeCFCtx.takeError(); - auto &CFCtx = *MaybeCFCtx; - - // Initialize states for running dataflow analysis. - DataflowAnalysisContext DACtx(std::make_unique(), - {/*Opts=*/AI.BuiltinOptions}); - Environment InitEnv(DACtx, *Target); - auto Analysis = AI.MakeAnalysis(Context, InitEnv); std::function - PostVisitCFGClosure = nullptr; + TypeErasedPostVisitCFG = nullptr; if (AI.PostVisitCFG) { - PostVisitCFGClosure = [&AI, &Context]( - const CFGElement &Element, - const TypeErasedDataflowAnalysisState &State) { + TypeErasedPostVisitCFG = [&AI, &Context]( + const CFGElement &Element, + const TypeErasedDataflowAnalysisState &State) { AI.PostVisitCFG(Context, Element, TransferStateForDiagnostics( llvm::any_cast( @@ -250,32 +228,57 @@ }; } - // Additional test setup. - AnalysisOutputs AO{AnnotatedCode, Context, Target, CFCtx, - Analysis, InitEnv, {}}; - if (AI.SetupTest) { - if (auto Error = AI.SetupTest(AO)) - return Error; + for (const ast_matchers::BoundNodes &BN : + ast_matchers::match(ast_matchers::functionDecl( + ast_matchers::hasBody(ast_matchers::stmt()), + AI.TargetFuncMatcher) + .bind("target"), + Context)) { + // Get the AST node of the target function. + const FunctionDecl *Target = BN.getNodeAs("target"); + if (Target == nullptr) + return llvm::make_error( + llvm::errc::invalid_argument, "Could not find the target function."); + + // Build the control flow graph for the target function. + auto MaybeCFCtx = + ControlFlowContext::build(Target, *Target->getBody(), Context); + if (!MaybeCFCtx) return MaybeCFCtx.takeError(); + auto &CFCtx = *MaybeCFCtx; + + // Initialize states for running dataflow analysis. + DataflowAnalysisContext DACtx(std::make_unique(), + {/*Opts=*/AI.BuiltinOptions}); + Environment InitEnv(DACtx, *Target); + auto Analysis = AI.MakeAnalysis(Context, InitEnv); + + AnalysisOutputs AO{AnnotatedCode, Context, Target, CFCtx, + Analysis, InitEnv, {}}; + + // Additional test setup. + if (AI.SetupTest) { + if (auto Error = AI.SetupTest(AO)) return Error; + } + + // If successful, the dataflow analysis returns a mapping from block IDs to + // the post-analysis states for the CFG blocks that have been evaluated. + llvm::Expected>> + MaybeBlockStates = runTypeErasedDataflowAnalysis( + CFCtx, Analysis, InitEnv, TypeErasedPostVisitCFG); + if (!MaybeBlockStates) return MaybeBlockStates.takeError(); + AO.BlockStates = *MaybeBlockStates; + + // Verify dataflow analysis outputs. + VerifyResults(AO); } - // If successful, the dataflow analysis returns a mapping from block IDs to - // the post-analysis states for the CFG blocks that have been evaluated. - llvm::Expected>> - MaybeBlockStates = runTypeErasedDataflowAnalysis(CFCtx, Analysis, InitEnv, - PostVisitCFGClosure); - if (!MaybeBlockStates) - return MaybeBlockStates.takeError(); - AO.BlockStates = *MaybeBlockStates; - - // Verify dataflow analysis outputs. - VerifyResults(AO); return llvm::Error::success(); } -/// Runs dataflow specified from `AI.MakeAnalysis` and `AI.PostVisitCFG` on the -/// body of the function that matches `AI.TargetFuncMatcher` in `AI.Code`. Given -/// the annotation line numbers and analysis outputs, `VerifyResults` checks -/// that the results from the analysis are correct. +/// Runs dataflow specified from `AI.MakeAnalysis` and `AI.PostVisitCFG` on all +/// functions that match `AI.TargetFuncMatcher` in `AI.Code`. Given the +/// annotation line numbers and analysis outputs, `VerifyResults` checks that +/// the results from the analysis are correct. /// /// Requirements: /// @@ -292,16 +295,17 @@ VerifyResults) { return checkDataflow( std::move(AI), [&VerifyResults](const AnalysisOutputs &AO) { - auto AnnotationLinesAndContent = - buildLineToAnnotationMapping(AO.ASTCtx.getSourceManager(), AO.Code); + auto AnnotationLinesAndContent = buildLineToAnnotationMapping( + AO.ASTCtx.getSourceManager(), AO.ASTCtx.getLangOpts(), + AO.Target->getSourceRange(), AO.Code); VerifyResults(AnnotationLinesAndContent, AO); }); } -/// Runs dataflow specified from `AI.MakeAnalysis` and `AI.PostVisitCFG` on the -/// body of the function that matches `AI.TargetFuncMatcher` in `AI.Code`. Given -/// the state computed at each annotated statement and analysis outputs, -/// `VerifyResults` checks that the results from the analysis are correct. +/// Runs dataflow specified from `AI.MakeAnalysis` and `AI.PostVisitCFG` on all +/// functions that match `AI.TargetFuncMatcher` in `AI.Code`. Given the state +/// computed at each annotated statement and analysis outputs, `VerifyResults` +/// checks that the results from the analysis are correct. /// /// Requirements: /// @@ -371,6 +375,10 @@ .withPostVisitCFG(std::move(PostVisitCFG)), [&VerifyResults, &AnnotationStates](const AnalysisOutputs &AO) { VerifyResults(AnnotationStates, AO); + + // `checkDataflow()` can analyze more than one function. Reset the + // variables to prepare for analyzing the next function. + AnnotationStates.clear(); }); } diff --git a/clang/unittests/Analysis/FlowSensitive/TestingSupport.cpp b/clang/unittests/Analysis/FlowSensitive/TestingSupport.cpp --- a/clang/unittests/Analysis/FlowSensitive/TestingSupport.cpp +++ b/clang/unittests/Analysis/FlowSensitive/TestingSupport.cpp @@ -6,6 +6,7 @@ #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" @@ -45,18 +46,23 @@ return true; } -llvm::DenseMap -test::buildLineToAnnotationMapping(SourceManager &SM, - llvm::Annotations AnnotatedCode) { +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) { - auto LineNumber = - SM.getPresumedLineNumber(SM.getLocForStartOfFile(SM.getMainFileID()) - .getLocWithOffset(AnnotationRange.Begin)); - auto Content = Code.slice(AnnotationRange.Begin, AnnotationRange.End).str(); - LineNumberToContent[LineNumber] = Content; + 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; } @@ -83,11 +89,18 @@ Stmts[Offset] = S; } - unsigned I = 0; - auto Annotations = AnnotatedCode.ranges(); + 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; diff --git a/clang/unittests/Analysis/FlowSensitive/TestingSupportTest.cpp b/clang/unittests/Analysis/FlowSensitive/TestingSupportTest.cpp --- a/clang/unittests/Analysis/FlowSensitive/TestingSupportTest.cpp +++ b/clang/unittests/Analysis/FlowSensitive/TestingSupportTest.cpp @@ -15,6 +15,7 @@ namespace { using ::clang::ast_matchers::functionDecl; +using ::clang::ast_matchers::hasAnyName; using ::clang::ast_matchers::hasName; using ::clang::ast_matchers::isDefinition; using ::clang::dataflow::test::AnalysisInputs; @@ -74,21 +75,21 @@ } void checkDataflow( - llvm::StringRef Code, llvm::StringRef Target, + llvm::StringRef Code, + ast_matchers::internal::Matcher TargetFuncMatcher, std::function< void(const llvm::StringMap> &, const AnalysisOutputs &)> Expectations) { ASSERT_THAT_ERROR(checkDataflow( AnalysisInputs( - Code, hasName(Target), + Code, std::move(TargetFuncMatcher), [](ASTContext &Context, Environment &) { return NoopAnalysis( Context, /*ApplyBuiltinTransfer=*/false); }) .withASTBuildArgs({"-fsyntax-only", "-std=c++17"}), - /*VerifyResults=*/ - std::move(Expectations)), + /*VerifyResults=*/std::move(Expectations)), llvm::Succeeded()); } @@ -100,7 +101,8 @@ EXPECT_CALL(Expectations, Call(IsEmpty(), _)).Times(1); - checkDataflow("void target() {}", "target", Expectations.AsStdFunction()); + checkDataflow("void target() {}", hasName("target"), + Expectations.AsStdFunction()); } TEST(ProgramPointAnnotations, NoAnnotationsDifferentTarget) { @@ -111,10 +113,11 @@ EXPECT_CALL(Expectations, Call(IsEmpty(), _)).Times(1); - checkDataflow("void fun() {}", "fun", Expectations.AsStdFunction()); + checkDataflow("void target() {}", hasName("target"), + Expectations.AsStdFunction()); } -TEST(ProgramPointAnnotations, WithCodepoint) { +TEST(ProgramPointAnnotations, WithProgramPoint) { ::testing::MockFunction> &, const AnalysisOutputs &)> @@ -126,13 +129,13 @@ .Times(1); checkDataflow(R"cc(void target() { - int n; - // [[program-point]] - })cc", - "target", Expectations.AsStdFunction()); + int n; + // [[program-point]] + })cc", + hasName("target"), Expectations.AsStdFunction()); } -TEST(ProgramPointAnnotations, MultipleCodepoints) { +TEST(ProgramPointAnnotations, MultipleProgramPoints) { ::testing::MockFunction> &, const AnalysisOutputs &)> @@ -145,15 +148,59 @@ .Times(1); checkDataflow(R"cc(void target(bool b) { - if (b) { - int n; - // [[program-point-1]] - } else { - int m; - // [[program-point-2]] - } - })cc", - "target", Expectations.AsStdFunction()); + if (b) { + int n; + // [[program-point-1]] + } else { + int m; + // [[program-point-2]] + } + })cc", + hasName("target"), Expectations.AsStdFunction()); +} + +TEST(ProgramPointAnnotations, MultipleFunctionsMultipleProgramPoints) { + ::testing::MockFunction> &, + const AnalysisOutputs &)> + Expectations; + + EXPECT_CALL(Expectations, Call(UnorderedElementsAre( + IsStringMapEntry("program-point-1a", _), + IsStringMapEntry("program-point-1b", _)), + _)) + .Times(1); + + EXPECT_CALL(Expectations, Call(UnorderedElementsAre( + IsStringMapEntry("program-point-2a", _), + IsStringMapEntry("program-point-2b", _)), + _)) + .Times(1); + + checkDataflow( + R"cc( + void target1(bool b) { + if (b) { + int n; + // [[program-point-1a]] + } else { + int m; + // [[program-point-1b]] + } + } + + void target2(bool b) { + if (b) { + int n; + // [[program-point-2a]] + } else { + int m; + // [[program-point-2b]] + } + } + )cc", + functionDecl(hasAnyName("target1", "target2")), + Expectations.AsStdFunction()); } } // namespace