Skip to content

Commit 21f0d36

Browse files
committed
Add logic to suppress noreturn falling for some basic control flow
1 parent 84be785 commit 21f0d36

File tree

7 files changed

+121
-29
lines changed

7 files changed

+121
-29
lines changed

clang/include/clang/Sema/AnalysisBasedWarnings.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#define LLVM_CLANG_SEMA_ANALYSISBASEDWARNINGS_H
1515

1616
#include "clang/AST/Decl.h"
17+
#include "clang/Analysis/AnalysisDeclContext.h"
1718
#include "llvm/ADT/DenseMap.h"
1819
#include <memory>
1920

@@ -107,6 +108,8 @@ class AnalysisBasedWarnings {
107108
// Issue warnings that require whole-translation-unit analysis.
108109
void IssueWarnings(TranslationUnitDecl *D);
109110

111+
void InferNoReturnAttributes(Sema &S, TranslationUnitDecl *TU);
112+
110113
// Gets the default policy which is in effect at the given source location.
111114
Policy getPolicyInEffectAt(SourceLocation Loc);
112115

@@ -119,6 +122,17 @@ class AnalysisBasedWarnings {
119122
};
120123

121124
} // namespace sema
125+
126+
enum ControlFlowKind {
127+
UnknownFallThrough,
128+
NeverFallThrough,
129+
MaybeFallThrough,
130+
AlwaysFallThrough,
131+
NeverFallThroughOrReturn
132+
};
133+
134+
ControlFlowKind CheckFallThrough(AnalysisDeclContext &AC);
135+
122136
} // namespace clang
123137

124138
#endif

clang/include/clang/Sema/Sema.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -834,7 +834,7 @@ enum class CCEKind {
834834
///< message.
835835
};
836836

837-
void inferNoReturnAttr(Sema &S, const Decl *D);
837+
bool inferNoReturnAttr(Sema &S, const Decl *D, bool FirstPass);
838838

839839
/// Sema - This implements semantic analysis and AST building for C.
840840
/// \nosubgrouping

clang/lib/AST/Decl.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3587,7 +3587,7 @@ bool FunctionDecl::isGlobal() const {
35873587

35883588
bool FunctionDecl::isNoReturn() const {
35893589
if (hasAttr<NoReturnAttr>() || hasAttr<CXX11NoReturnAttr>() ||
3590-
hasAttr<C11NoReturnAttr>())
3590+
hasAttr<C11NoReturnAttr>() || hasAttr<InferredNoReturnAttr>())
35913591
return true;
35923592

35933593
if (auto *FnTy = getType()->getAs<FunctionType>())

clang/lib/Sema/AnalysisBasedWarnings.cpp

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -403,14 +403,6 @@ static bool isNoexcept(const FunctionDecl *FD) {
403403
// Check for missing return value.
404404
//===----------------------------------------------------------------------===//
405405

406-
enum ControlFlowKind {
407-
UnknownFallThrough,
408-
NeverFallThrough,
409-
MaybeFallThrough,
410-
AlwaysFallThrough,
411-
NeverFallThroughOrReturn
412-
};
413-
414406
/// CheckFallThrough - Check that we don't fall off the end of a
415407
/// Statement that should return a value.
416408
///
@@ -420,7 +412,7 @@ enum ControlFlowKind {
420412
/// return. We assume NeverFallThrough iff we never fall off the end of the
421413
/// statement but we may return. We assume that functions not marked noreturn
422414
/// will return.
423-
static ControlFlowKind CheckFallThrough(AnalysisDeclContext &AC) {
415+
clang::ControlFlowKind clang::CheckFallThrough(AnalysisDeclContext &AC) {
424416
CFG *cfg = AC.getCFG();
425417
if (!cfg) return UnknownFallThrough;
426418

@@ -2678,6 +2670,35 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
26782670
}
26792671
}
26802672

2673+
void clang::sema::AnalysisBasedWarnings::InferNoReturnAttributes(
2674+
Sema &S, TranslationUnitDecl *TU) {
2675+
llvm::SmallVector<Decl *, 32> AllFunctions;
2676+
2677+
for (Decl *D : TU->decls()) {
2678+
// Collect ordinary (non-template) functions and methods:
2679+
if (auto *FD = dyn_cast<FunctionDecl>(D)) {
2680+
if (!FD->getDescribedFunctionTemplate() && FD->hasBody())
2681+
AllFunctions.push_back(FD);
2682+
}
2683+
2684+
// For each function template, include all its instantiations:
2685+
if (auto *FT = dyn_cast<FunctionTemplateDecl>(D)) {
2686+
for (auto *Spec : FT->specializations()) {
2687+
if (auto *FDs = dyn_cast<FunctionDecl>(Spec))
2688+
if (FDs->hasBody())
2689+
AllFunctions.push_back(FDs);
2690+
}
2691+
}
2692+
}
2693+
2694+
// Pass 1: Mark all trivial always-throwing functions.
2695+
for (Decl *D : AllFunctions)
2696+
inferNoReturnAttr(S, D, /*FirstPass*/ true);
2697+
// Pass 2: Run until no changes.
2698+
for (Decl *D : AllFunctions)
2699+
inferNoReturnAttr(S, D, /*FirstPass*/ false);
2700+
}
2701+
26812702
void clang::sema::AnalysisBasedWarnings::IssueWarnings(
26822703
sema::AnalysisBasedWarnings::Policy P, sema::FunctionScopeInfo *fscope,
26832704
const Decl *D, QualType BlockType) {

clang/lib/Sema/Sema.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2453,7 +2453,8 @@ Sema::PopFunctionScopeInfo(const AnalysisBasedWarnings::Policy *WP,
24532453

24542454
// Issue any analysis-based warnings.
24552455
if (WP && D) {
2456-
inferNoReturnAttr(*this, D);
2456+
AnalysisWarnings.InferNoReturnAttributes(
2457+
*this, getASTContext().getTranslationUnitDecl());
24572458
AnalysisWarnings.IssueWarnings(*WP, Scope.get(), D, BlockType);
24582459
} else
24592460
for (const auto &PUD : Scope->PossiblyUnreachableDiags)

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "clang/AST/ExprCXX.h"
2424
#include "clang/AST/Mangle.h"
2525
#include "clang/AST/Type.h"
26+
#include "clang/Analysis/AnalysisDeclContext.h"
2627
#include "clang/Basic/CharInfo.h"
2728
#include "clang/Basic/Cuda.h"
2829
#include "clang/Basic/DarwinSDKInfo.h"
@@ -32,6 +33,7 @@
3233
#include "clang/Basic/SourceManager.h"
3334
#include "clang/Basic/TargetInfo.h"
3435
#include "clang/Lex/Preprocessor.h"
36+
#include "clang/Sema/AnalysisBasedWarnings.h"
3537
#include "clang/Sema/Attr.h"
3638
#include "clang/Sema/DeclSpec.h"
3739
#include "clang/Sema/DelayedDiagnostic.h"
@@ -1939,11 +1941,7 @@ static void handleNakedAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
19391941
D->addAttr(::new (S.Context) NakedAttr(S.Context, AL));
19401942
}
19411943

1942-
// FIXME: This is a best-effort heuristic.
1943-
// Currently only handles single throw expressions (optionally with
1944-
// ExprWithCleanups). We could expand this to perform control-flow analysis for
1945-
// more complex patterns.
1946-
static bool isKnownToAlwaysThrow(const FunctionDecl *FD) {
1944+
static bool alwaysThrowsDirect(const FunctionDecl *FD) {
19471945
if (!FD->hasBody())
19481946
return false;
19491947
const Stmt *Body = FD->getBody();
@@ -1965,25 +1963,52 @@ static bool isKnownToAlwaysThrow(const FunctionDecl *FD) {
19651963
return isa<CXXThrowExpr>(OnlyStmt);
19661964
}
19671965

1968-
void clang::inferNoReturnAttr(Sema &S, const Decl *D) {
1966+
static bool isKnownToAlwaysThrowCFG(const FunctionDecl *FD, Sema &S) {
1967+
if (!FD->hasBody())
1968+
return false;
1969+
1970+
// Drop the const qualifier to do an analysis.
1971+
FunctionDecl *NonConstFD = const_cast<FunctionDecl *>(FD);
1972+
AnalysisDeclContext AC(nullptr, NonConstFD);
1973+
1974+
switch (CheckFallThrough(AC)) {
1975+
case NeverFallThrough:
1976+
case NeverFallThroughOrReturn:
1977+
return true;
1978+
case AlwaysFallThrough:
1979+
case UnknownFallThrough:
1980+
case MaybeFallThrough:
1981+
return false;
1982+
}
1983+
}
1984+
1985+
bool clang::inferNoReturnAttr(Sema &S, const Decl *D, bool FirstPass) {
19691986
auto *FD = dyn_cast<FunctionDecl>(D);
19701987
if (!FD)
1971-
return;
1988+
return false;
1989+
if (FD->isInvalidDecl())
1990+
return false;
1991+
if (FD->hasAttr<NoReturnAttr>() || FD->hasAttr<InferredNoReturnAttr>())
1992+
return false;
19721993

19731994
auto *NonConstFD = const_cast<FunctionDecl *>(FD);
1974-
DiagnosticsEngine &Diags = S.getDiagnostics();
1975-
if (Diags.isIgnored(diag::warn_falloff_nonvoid, FD->getLocation()) &&
1976-
Diags.isIgnored(diag::warn_suggest_noreturn_function, FD->getLocation()))
1977-
return;
19781995

1979-
if (!FD->hasAttr<NoReturnAttr>() && !FD->hasAttr<InferredNoReturnAttr>() &&
1980-
isKnownToAlwaysThrow(FD)) {
1981-
NonConstFD->addAttr(InferredNoReturnAttr::CreateImplicit(S.Context));
1996+
auto tryAddInferredNoReturn = [&](bool Condition) {
1997+
if (Condition) {
1998+
NonConstFD->addAttr(InferredNoReturnAttr::CreateImplicit(S.Context));
1999+
if (FD->getReturnType()->isVoidType())
2000+
S.Diag(FD->getLocation(), diag::warn_suggest_noreturn_function)
2001+
<< /*isFunction=*/0 << FD;
2002+
return true;
2003+
}
2004+
return false;
2005+
};
19822006

1983-
// Emit a diagnostic suggesting the function being marked [[noreturn]].
1984-
S.Diag(FD->getLocation(), diag::warn_suggest_noreturn_function)
1985-
<< /*isFunction=*/0 << FD;
2007+
if (FirstPass) {
2008+
// Look for trivial function bodies that always throw.
2009+
return tryAddInferredNoReturn(alwaysThrowsDirect(FD));
19862010
}
2011+
return tryAddInferredNoReturn(isKnownToAlwaysThrowCFG(FD, S));
19872012
}
19882013

19892014
static void handleNoReturnAttr(Sema &S, Decl *D, const ParsedAttr &Attrs) {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// RUN: %clang_cc1 -fsyntax-only -fcxx-exceptions -fexceptions -Wreturn-type -verify %s
2+
// expected-no-diagnostics
3+
4+
namespace std {
5+
class string {
6+
public:
7+
string(const char*); // constructor for runtime_error
8+
};
9+
class runtime_error {
10+
public:
11+
runtime_error(const string &);
12+
};
13+
}
14+
15+
void throwA() {
16+
throw std::runtime_error("ERROR A");
17+
}
18+
19+
void throwB(int n) {
20+
if (n)
21+
throw std::runtime_error("ERROR B");
22+
else
23+
throw std::runtime_error("ERROR B with n=0");
24+
}
25+
26+
int test(int x) {
27+
if (x > 0)
28+
throwA();
29+
else
30+
throwB(x);
31+
}

0 commit comments

Comments
 (0)