Skip to content

Commit 8cca1b0

Browse files
committed
Clang now suppresses -Wreturn-type diagnostics for non-void functions
that always throw, by inferring an implicit InferredNoReturnAttr on simple cases (single throw statements).
1 parent d9b3920 commit 8cca1b0

File tree

5 files changed

+56
-31
lines changed

5 files changed

+56
-31
lines changed

clang/include/clang/Basic/Attr.td

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -965,6 +965,13 @@ def AnalyzerNoReturn : InheritableAttr {
965965
let Documentation = [Undocumented];
966966
}
967967

968+
def InferredNoReturn : InheritableAttr {
969+
let Spellings = [];
970+
let SemaHandler = 0;
971+
let Documentation = [Undocumented];
972+
let Subjects = SubjectList<[Function], ErrorDiag>;
973+
}
974+
968975
def Annotate : InheritableParamOrStmtAttr {
969976
let Spellings = [Clang<"annotate">];
970977
let Args = [StringArgument<"Annotation">, VariadicExprArgument<"Args">];

clang/include/clang/Sema/Sema.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -834,6 +834,8 @@ enum class CCEKind {
834834
///< message.
835835
};
836836

837+
void inferNoReturnAttr(Sema &S, FunctionDecl *FD);
838+
837839
/// Sema - This implements semantic analysis and AST building for C.
838840
/// \nosubgrouping
839841
class Sema final : public SemaBase {

clang/lib/Sema/AnalysisBasedWarnings.cpp

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -624,34 +624,8 @@ struct CheckFallThroughDiagnostics {
624624
}
625625
};
626626

627-
// FIXME: This is a shallow best-effort check. Currently, we only handle
628-
// cases where the function body consists of a single `throw` expression,
629-
// possibly wrapped in an `ExprWithCleanups`. We do not perform general
630-
// control-flow analysis or handle more complex throwing patterns.
631-
// Consider expanding this to handle more cases in the future.
632-
bool isKnownToAlwaysThrow(const FunctionDecl *FD) {
633-
if (!FD->hasBody())
634-
return false;
635-
const Stmt *Body = FD->getBody();
636-
const Stmt *OnlyStmt = nullptr;
637-
638-
if (const auto *Compound = dyn_cast<CompoundStmt>(Body)) {
639-
if (Compound->size() != 1)
640-
return false; // More than one statement, can't be known to always throw.
641-
OnlyStmt = *Compound->body_begin();
642-
} else {
643-
OnlyStmt = Body;
644-
}
645-
646-
// Unwrap ExprWithCleanups if necessary.
647-
if (const auto *EWC = dyn_cast<ExprWithCleanups>(OnlyStmt)) {
648-
OnlyStmt = EWC->getSubExpr();
649-
}
650-
// Check if the only statement is a throw expression.
651-
return isa<CXXThrowExpr>(OnlyStmt);
652-
}
653-
654627
} // anonymous namespace
628+
655629
/// CheckFallThroughForBody - Check that we don't fall off the end of a
656630
/// function that should return a value. Check that we don't fall off the end
657631
/// of a noreturn function. We assume that functions and blocks not marked
@@ -669,7 +643,7 @@ static void CheckFallThroughForBody(Sema &S, const Decl *D, const Stmt *Body,
669643
ReturnsVoid = CBody->getFallthroughHandler() != nullptr;
670644
else
671645
ReturnsVoid = FD->getReturnType()->isVoidType();
672-
HasNoReturn = FD->isNoReturn();
646+
HasNoReturn = FD->isNoReturn() || FD->hasAttr<InferredNoReturnAttr>();
673647
}
674648
else if (const auto *MD = dyn_cast<ObjCMethodDecl>(D)) {
675649
ReturnsVoid = MD->getReturnType()->isVoidType();
@@ -719,7 +693,7 @@ static void CheckFallThroughForBody(Sema &S, const Decl *D, const Stmt *Body,
719693
}
720694
if (const auto *CE = dyn_cast<CallExpr>(LastStmt)) {
721695
if (const FunctionDecl *Callee = CE->getDirectCallee();
722-
Callee && isKnownToAlwaysThrow(Callee)) {
696+
Callee && Callee->hasAttr<InferredNoReturnAttr>()) {
723697
return; // Don't warn about fall-through.
724698
}
725699
}

clang/lib/Sema/Sema.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2434,9 +2434,12 @@ Sema::PopFunctionScopeInfo(const AnalysisBasedWarnings::Policy *WP,
24342434
OpenMP().popOpenMPFunctionRegion(Scope.get());
24352435

24362436
// Issue any analysis-based warnings.
2437-
if (WP && D)
2437+
if (WP && D) {
2438+
if (auto *FD = dyn_cast<FunctionDecl>(D)) {
2439+
inferNoReturnAttr(*this, const_cast<FunctionDecl *>(FD));
2440+
}
24382441
AnalysisWarnings.IssueWarnings(*WP, Scope.get(), D, BlockType);
2439-
else
2442+
} else
24402443
for (const auto &PUD : Scope->PossiblyUnreachableDiags)
24412444
Diag(PUD.Loc, PUD.PD);
24422445

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#include "clang/Sema/ParsedAttr.h"
4141
#include "clang/Sema/Scope.h"
4242
#include "clang/Sema/ScopeInfo.h"
43+
#include "clang/Sema/Sema.h"
4344
#include "clang/Sema/SemaAMDGPU.h"
4445
#include "clang/Sema/SemaARM.h"
4546
#include "clang/Sema/SemaAVR.h"
@@ -1938,6 +1939,44 @@ static void handleNakedAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
19381939
D->addAttr(::new (S.Context) NakedAttr(S.Context, AL));
19391940
}
19401941

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) {
1947+
if (!FD->hasBody())
1948+
return false;
1949+
const Stmt *Body = FD->getBody();
1950+
const Stmt *OnlyStmt = nullptr;
1951+
1952+
if (const auto *Compound = dyn_cast<CompoundStmt>(Body)) {
1953+
if (Compound->size() != 1)
1954+
return false; // More than one statement, can't be known to always throw.
1955+
OnlyStmt = *Compound->body_begin();
1956+
} else {
1957+
OnlyStmt = Body;
1958+
}
1959+
1960+
// Unwrap ExprWithCleanups if necessary.
1961+
if (const auto *EWC = dyn_cast<ExprWithCleanups>(OnlyStmt)) {
1962+
OnlyStmt = EWC->getSubExpr();
1963+
}
1964+
// Check if the only statement is a throw expression.
1965+
return isa<CXXThrowExpr>(OnlyStmt);
1966+
}
1967+
1968+
void clang::inferNoReturnAttr(Sema &S, FunctionDecl *FD) {
1969+
DiagnosticsEngine &Diags = S.getDiagnostics();
1970+
if (Diags.isIgnored(diag::warn_falloff_nonvoid, FD->getLocation()) &&
1971+
Diags.isIgnored(diag::warn_suggest_noreturn_function, FD->getLocation()))
1972+
return;
1973+
1974+
if (!FD->hasAttr<NoReturnAttr>() && !FD->hasAttr<InferredNoReturnAttr>() &&
1975+
isKnownToAlwaysThrow(FD)) {
1976+
FD->addAttr(InferredNoReturnAttr::CreateImplicit(S.Context));
1977+
}
1978+
}
1979+
19411980
static void handleNoReturnAttr(Sema &S, Decl *D, const ParsedAttr &Attrs) {
19421981
if (hasDeclarator(D)) return;
19431982

0 commit comments

Comments
 (0)