[clang] Suppress noreturn warning if last statement in a function is a throw (#145166)
Fixes https://github.com/llvm/llvm-project/issues/144952
This commit is contained in:
@@ -649,6 +649,13 @@ Improvements to Clang's diagnostics
|
||||
#GH69470, #GH59391, #GH58172, #GH46215, #GH45915, #GH45891, #GH44490,
|
||||
#GH36703, #GH32903, #GH23312, #GH69874.
|
||||
|
||||
- Clang now avoids issuing `-Wreturn-type` warnings in some cases where
|
||||
the final statement of a non-void function is a `throw` expression, or
|
||||
a call to a function that is trivially known to always throw (i.e., its
|
||||
body consists solely of a `throw` statement). This avoids certain
|
||||
false positives in exception-heavy code, though only simple patterns
|
||||
are currently recognized.
|
||||
|
||||
|
||||
Improvements to Clang's time-trace
|
||||
----------------------------------
|
||||
|
||||
@@ -965,6 +965,13 @@ def AnalyzerNoReturn : InheritableAttr {
|
||||
let Documentation = [Undocumented];
|
||||
}
|
||||
|
||||
def InferredNoReturn : InheritableAttr {
|
||||
let Spellings = [];
|
||||
let SemaHandler = 0;
|
||||
let Subjects = SubjectList<[Function], ErrorDiag>;
|
||||
let Documentation = [InternalOnly];
|
||||
}
|
||||
|
||||
def Annotate : InheritableParamOrStmtAttr {
|
||||
let Spellings = [Clang<"annotate">];
|
||||
let Args = [StringArgument<"Annotation">, VariadicExprArgument<"Args">];
|
||||
|
||||
@@ -834,6 +834,8 @@ enum class CCEKind {
|
||||
///< message.
|
||||
};
|
||||
|
||||
void inferNoReturnAttr(Sema &S, const Decl *D);
|
||||
|
||||
/// Sema - This implements semantic analysis and AST building for C.
|
||||
/// \nosubgrouping
|
||||
class Sema final : public SemaBase {
|
||||
|
||||
@@ -643,7 +643,7 @@ static void CheckFallThroughForBody(Sema &S, const Decl *D, const Stmt *Body,
|
||||
ReturnsVoid = CBody->getFallthroughHandler() != nullptr;
|
||||
else
|
||||
ReturnsVoid = FD->getReturnType()->isVoidType();
|
||||
HasNoReturn = FD->isNoReturn();
|
||||
HasNoReturn = FD->isNoReturn() || FD->hasAttr<InferredNoReturnAttr>();
|
||||
}
|
||||
else if (const auto *MD = dyn_cast<ObjCMethodDecl>(D)) {
|
||||
ReturnsVoid = MD->getReturnType()->isVoidType();
|
||||
@@ -681,6 +681,28 @@ static void CheckFallThroughForBody(Sema &S, const Decl *D, const Stmt *Body,
|
||||
if (CD.diag_FallThrough_HasNoReturn)
|
||||
S.Diag(RBrace, CD.diag_FallThrough_HasNoReturn) << CD.FunKind;
|
||||
} else if (!ReturnsVoid && CD.diag_FallThrough_ReturnsNonVoid) {
|
||||
// If the final statement is a call to an always-throwing function,
|
||||
// don't warn about the fall-through.
|
||||
if (const auto *FD = D->getAsFunction()) {
|
||||
if (const auto *CS = dyn_cast<CompoundStmt>(Body);
|
||||
CS && !CS->body_empty()) {
|
||||
const Stmt *LastStmt = CS->body_back();
|
||||
// Unwrap ExprWithCleanups if necessary.
|
||||
if (const auto *EWC = dyn_cast<ExprWithCleanups>(LastStmt)) {
|
||||
LastStmt = EWC->getSubExpr();
|
||||
}
|
||||
if (const auto *CE = dyn_cast<CallExpr>(LastStmt)) {
|
||||
if (const FunctionDecl *Callee = CE->getDirectCallee();
|
||||
Callee && Callee->hasAttr<InferredNoReturnAttr>()) {
|
||||
return; // Don't warn about fall-through.
|
||||
}
|
||||
}
|
||||
// Direct throw.
|
||||
if (isa<CXXThrowExpr>(LastStmt)) {
|
||||
return; // Don't warn about fall-through.
|
||||
}
|
||||
}
|
||||
}
|
||||
bool NotInAllControlPaths = FallThroughType == MaybeFallThrough;
|
||||
S.Diag(RBrace, CD.diag_FallThrough_ReturnsNonVoid)
|
||||
<< CD.FunKind << NotInAllControlPaths;
|
||||
|
||||
@@ -2434,9 +2434,10 @@ Sema::PopFunctionScopeInfo(const AnalysisBasedWarnings::Policy *WP,
|
||||
OpenMP().popOpenMPFunctionRegion(Scope.get());
|
||||
|
||||
// Issue any analysis-based warnings.
|
||||
if (WP && D)
|
||||
if (WP && D) {
|
||||
inferNoReturnAttr(*this, D);
|
||||
AnalysisWarnings.IssueWarnings(*WP, Scope.get(), D, BlockType);
|
||||
else
|
||||
} else
|
||||
for (const auto &PUD : Scope->PossiblyUnreachableDiags)
|
||||
Diag(PUD.Loc, PUD.PD);
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#include "clang/Sema/ParsedAttr.h"
|
||||
#include "clang/Sema/Scope.h"
|
||||
#include "clang/Sema/ScopeInfo.h"
|
||||
#include "clang/Sema/Sema.h"
|
||||
#include "clang/Sema/SemaAMDGPU.h"
|
||||
#include "clang/Sema/SemaARM.h"
|
||||
#include "clang/Sema/SemaAVR.h"
|
||||
@@ -1938,6 +1939,49 @@ static void handleNakedAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
|
||||
D->addAttr(::new (S.Context) NakedAttr(S.Context, AL));
|
||||
}
|
||||
|
||||
// FIXME: This is a best-effort heuristic.
|
||||
// Currently only handles single throw expressions (optionally with
|
||||
// ExprWithCleanups). We could expand this to perform control-flow analysis for
|
||||
// more complex patterns.
|
||||
static bool isKnownToAlwaysThrow(const FunctionDecl *FD) {
|
||||
if (!FD->hasBody())
|
||||
return false;
|
||||
const Stmt *Body = FD->getBody();
|
||||
const Stmt *OnlyStmt = nullptr;
|
||||
|
||||
if (const auto *Compound = dyn_cast<CompoundStmt>(Body)) {
|
||||
if (Compound->size() != 1)
|
||||
return false; // More than one statement, can't be known to always throw.
|
||||
OnlyStmt = *Compound->body_begin();
|
||||
} else {
|
||||
OnlyStmt = Body;
|
||||
}
|
||||
|
||||
// Unwrap ExprWithCleanups if necessary.
|
||||
if (const auto *EWC = dyn_cast<ExprWithCleanups>(OnlyStmt)) {
|
||||
OnlyStmt = EWC->getSubExpr();
|
||||
}
|
||||
// Check if the only statement is a throw expression.
|
||||
return isa<CXXThrowExpr>(OnlyStmt);
|
||||
}
|
||||
|
||||
void clang::inferNoReturnAttr(Sema &S, const Decl *D) {
|
||||
auto *FD = dyn_cast<FunctionDecl>(D);
|
||||
if (!FD)
|
||||
return;
|
||||
|
||||
auto *NonConstFD = const_cast<FunctionDecl *>(FD);
|
||||
DiagnosticsEngine &Diags = S.getDiagnostics();
|
||||
if (Diags.isIgnored(diag::warn_falloff_nonvoid, FD->getLocation()) &&
|
||||
Diags.isIgnored(diag::warn_suggest_noreturn_function, FD->getLocation()))
|
||||
return;
|
||||
|
||||
if (!FD->hasAttr<NoReturnAttr>() && !FD->hasAttr<InferredNoReturnAttr>() &&
|
||||
isKnownToAlwaysThrow(FD)) {
|
||||
NonConstFD->addAttr(InferredNoReturnAttr::CreateImplicit(S.Context));
|
||||
}
|
||||
}
|
||||
|
||||
static void handleNoReturnAttr(Sema &S, Decl *D, const ParsedAttr &Attrs) {
|
||||
if (hasDeclarator(D)) return;
|
||||
|
||||
|
||||
46
clang/test/SemaCXX/wreturn-always-throws.cpp
Normal file
46
clang/test/SemaCXX/wreturn-always-throws.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
// RUN: %clang_cc1 -fsyntax-only -fcxx-exceptions -fexceptions -Wreturn-type -verify %s
|
||||
// expected-no-diagnostics
|
||||
|
||||
namespace std {
|
||||
class string {
|
||||
public:
|
||||
string(const char*); // constructor for runtime_error
|
||||
};
|
||||
class runtime_error {
|
||||
public:
|
||||
runtime_error(const string &);
|
||||
};
|
||||
}
|
||||
|
||||
// Non-template version.
|
||||
|
||||
void throwError(const std::string& msg) {
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
|
||||
int ensureZero(const int i) {
|
||||
if (i == 0) return 0;
|
||||
throwError("ERROR"); // no-warning
|
||||
}
|
||||
|
||||
int alwaysThrows() {
|
||||
throw std::runtime_error("This function always throws"); // no-warning
|
||||
}
|
||||
|
||||
// Template version.
|
||||
|
||||
template<typename T>
|
||||
void throwErrorTemplate(const T& msg) {
|
||||
throw msg;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
int ensureZeroTemplate(T i) {
|
||||
if (i == 0) return 0;
|
||||
throwErrorTemplate("ERROR"); // no-warning
|
||||
}
|
||||
|
||||
void testTemplates() {
|
||||
throwErrorTemplate("ERROR");
|
||||
(void)ensureZeroTemplate(42);
|
||||
}
|
||||
Reference in New Issue
Block a user