Index: clang/lib/StaticAnalyzer/Core/BugReporter.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -21,6 +21,7 @@ #include "clang/AST/Stmt.h" #include "clang/AST/StmtCXX.h" #include "clang/AST/StmtObjC.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Analysis/AnalysisDeclContext.h" #include "clang/Analysis/CFG.h" #include "clang/Analysis/CFGStmtMap.h" @@ -2821,6 +2822,42 @@ return Notes; } +/// Suppresses the bugreport if it happened in constexpr context, where +/// generally no undefined behavior should occur in a valid source code. +/// \returns true if it suppressed the bugreport +static bool suppressReportsFromConstexprContext(PathSensitiveBugReport &BR) { + // TODO: Maybe pass the ASTContext as a parameter? + ASTContext &ACtx = + BR.getErrorNode()->getState()->getStateManager().getContext(); + const StackFrameContext *Frame = + BR.getErrorNode()->getLocationContext()->getStackFrame(); + + const StringRef BindName = "decl"; + using namespace clang::ast_matchers; + static const auto Matcher = callExpr(hasAncestor(declStmt().bind(BindName))); + + while (Frame && !Frame->inTopFrame()) { + // CallSite is null for destructors. + if (const Stmt *CallSite = Frame->getCallSite()) { + // Locs are invalid for synthetized call bodies produced by BodyFarm. + if (CallSite->getSourceRange().isValid()) { + const auto Matches = match(Matcher, *CallSite, ACtx); + if (!Matches.empty()) { + const auto *DS = Matches[0].getNodeAs(BindName); + // Assumption: The CFG has one DeclStmt per Decl. + const auto *Var = dyn_cast_or_null(*DS->decl_begin()); + if (Var && Var->isConstexpr()) { + BR.markInvalid("happened in constexpr context", nullptr); + return true; + } + } + } + } + Frame = Frame->getParent()->getStackFrame(); + } + return false; +} + Optional PathDiagnosticBuilder::findValidReport( ArrayRef &bugReports, PathSensitiveBugReporter &Reporter) { @@ -2834,6 +2871,12 @@ assert(R->isValid() && "Report selected by trimmed graph marked invalid."); const ExplodedNode *ErrorNode = BugPath->ErrorNode; + // Suppress reports materialized within constexpr context. + // FIXME: Maybe introduce an analyzer option for disabling this? + if (Reporter.getContext().getLangOpts().CPlusPlus) + if (suppressReportsFromConstexprContext(*R)) + return {}; + // Register refutation visitors first, if they mark the bug invalid no // further analysis is required R->addVisitor(); Index: clang/test/Analysis/suppress-from-constexpr-context.cpp =================================================================== --- /dev/null +++ clang/test/Analysis/suppress-from-constexpr-context.cpp @@ -0,0 +1,71 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.deadcode.UnreachableCode,debug.ExprInspection %s -std=c++17 -verify + +constexpr void clang_analyzer_warnIfReached() {} + +constexpr int generateReport() { + clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} + return 0; +} + +constexpr int createBugreportWhichWillBesuppressed() { + clang_analyzer_warnIfReached(); // no-warning: suppressed since the callsite is constexpr context + return 0; +} + +int TestNonConstexprVarDecl() { + int x = generateReport(); // will have a warning + return x; +} + +int TestSingleVarDecl() { + constexpr int x = createBugreportWhichWillBesuppressed(); // no-warning + return x; +} + +int TestMultipleVarDecl() { + constexpr int y = createBugreportWhichWillBesuppressed(), // no-warning + z = createBugreportWhichWillBesuppressed(); // no-warning + return y + z; +} + +int TestCommaExprAndLambdas() { + // FIXME: For Eval::Call-ed functions the location context is the + // callee's context, so there is no CallSite. The CallSite should + // still refer to the DeclStmt. + // expected-warning@+2 {{REACHABLE}} + constexpr auto f = ( + clang_analyzer_warnIfReached(), // We should have no warning for this. + [](){ + clang_analyzer_warnIfReached(); // Only evaluated later. + } + ); + + // Now evaluate 'f' in constexpr context. + constexpr int x = (f(), 1); // no-warning + constexpr int y = [](){ clang_analyzer_warnIfReached(); return 1; }(); // no-warning + return x + y; +} + +constexpr int TestDeadCodeTopLevelFn() { + if (false) { + // Dead code! + generateReport(); // expected-warning {{This statement is never executed}} + return 42; + } + return 66; +} + +constexpr int deadcode_callee(bool cond) { + if (cond) { + // Dead code! + // FIXME: We shouldn't suppress deadcode warnings from constexpr contexts. + createBugreportWhichWillBesuppressed(); // no-deadcode-warning: it would come from constexpr context + return 42; + } + return 66; +} + +int TestDeadCodeInCallee() { + constexpr int x = deadcode_callee(/*cond=*/false); // no-warning + return x; +}