Skip to content

Commit 977cfea

Browse files
authored
[Analysis] Avoid some warnings about exit from noreturn function (#144408)
Compiler sometimes issues warnings on exit from 'noreturn' functions, in the code like: [[noreturn]] extern void nonreturnable(); void (*func_ptr)(); [[noreturn]] void foo() { func_ptr = nonreturnable; (*func_ptr)(); } where exit cannot take place because the function pointer is actually a pointer to noreturn function. This change introduces small data analysis that can remove some of the warnings in the cases when compiler can prove that the set of reaching definitions consists of noreturn functions only.
1 parent da283b5 commit 977cfea

File tree

3 files changed

+382
-0
lines changed

3 files changed

+382
-0
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,11 @@ Improvements to Clang's diagnostics
705705
- Improve the diagnostics for placement new expression when const-qualified
706706
object was passed as the storage argument. (#GH143708)
707707

708+
- Clang now does not issue a warning about returning from a function declared with
709+
the ``[[noreturn]]`` attribute when the function body is ended with a call via
710+
pointer, provided it can be proven that the pointer only points to
711+
``[[noreturn]]`` functions.
712+
708713
Improvements to Clang's time-trace
709714
----------------------------------
710715

clang/lib/Sema/AnalysisBasedWarnings.cpp

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include "clang/Analysis/AnalysisDeclContext.h"
3838
#include "clang/Analysis/CFG.h"
3939
#include "clang/Analysis/CFGStmtMap.h"
40+
#include "clang/Analysis/FlowSensitive/DataflowWorklist.h"
4041
#include "clang/Basic/Diagnostic.h"
4142
#include "clang/Basic/DiagnosticSema.h"
4243
#include "clang/Basic/SourceLocation.h"
@@ -46,6 +47,7 @@
4647
#include "clang/Sema/SemaInternal.h"
4748
#include "llvm/ADT/ArrayRef.h"
4849
#include "llvm/ADT/BitVector.h"
50+
#include "llvm/ADT/DenseMap.h"
4951
#include "llvm/ADT/MapVector.h"
5052
#include "llvm/ADT/STLFunctionalExtras.h"
5153
#include "llvm/ADT/SmallVector.h"
@@ -401,6 +403,143 @@ static bool isNoexcept(const FunctionDecl *FD) {
401403
return false;
402404
}
403405

406+
/// Checks if the given expression is a reference to a function with
407+
/// 'noreturn' attribute.
408+
static bool isReferenceToNoReturn(const Expr *E) {
409+
if (auto *DRef = dyn_cast<DeclRefExpr>(E->IgnoreParenCasts()))
410+
if (auto *FD = dyn_cast<FunctionDecl>(DRef->getDecl()))
411+
return FD->isNoReturn();
412+
return false;
413+
}
414+
415+
/// Checks if the given variable, which is assumed to be a function pointer, is
416+
/// initialized with a function having 'noreturn' attribute.
417+
static bool isInitializedWithNoReturn(const VarDecl *VD) {
418+
if (const Expr *Init = VD->getInit()) {
419+
if (auto *ListInit = dyn_cast<InitListExpr>(Init);
420+
ListInit && ListInit->getNumInits() > 0)
421+
Init = ListInit->getInit(0);
422+
return isReferenceToNoReturn(Init);
423+
}
424+
return false;
425+
}
426+
427+
namespace {
428+
429+
/// Looks for statements, that can define value of the given variable.
430+
struct TransferFunctions : public StmtVisitor<TransferFunctions> {
431+
const VarDecl *Var;
432+
std::optional<bool> AllValuesAreNoReturn;
433+
434+
TransferFunctions(const VarDecl *VD) : Var(VD) {}
435+
436+
void reset() { AllValuesAreNoReturn = std::nullopt; }
437+
438+
void VisitDeclStmt(DeclStmt *DS) {
439+
for (auto *DI : DS->decls())
440+
if (auto *VD = dyn_cast<VarDecl>(DI))
441+
if (VarDecl *Def = VD->getDefinition())
442+
if (Def == Var)
443+
AllValuesAreNoReturn = isInitializedWithNoReturn(Def);
444+
}
445+
446+
void VisitUnaryOperator(UnaryOperator *UO) {
447+
if (UO->getOpcode() == UO_AddrOf) {
448+
if (auto *DRef =
449+
dyn_cast<DeclRefExpr>(UO->getSubExpr()->IgnoreParenCasts()))
450+
if (DRef->getDecl() == Var)
451+
AllValuesAreNoReturn = false;
452+
}
453+
}
454+
455+
void VisitBinaryOperator(BinaryOperator *BO) {
456+
if (BO->getOpcode() == BO_Assign)
457+
if (auto *DRef = dyn_cast<DeclRefExpr>(BO->getLHS()->IgnoreParenCasts()))
458+
if (DRef->getDecl() == Var)
459+
AllValuesAreNoReturn = isReferenceToNoReturn(BO->getRHS());
460+
}
461+
462+
void VisitCallExpr(CallExpr *CE) {
463+
for (CallExpr::arg_iterator I = CE->arg_begin(), E = CE->arg_end(); I != E;
464+
++I) {
465+
const Expr *Arg = *I;
466+
if (Arg->isGLValue() && !Arg->getType().isConstQualified())
467+
if (auto *DRef = dyn_cast<DeclRefExpr>(Arg->IgnoreParenCasts()))
468+
if (auto VD = dyn_cast<VarDecl>(DRef->getDecl()))
469+
if (VD->getDefinition() == Var)
470+
AllValuesAreNoReturn = false;
471+
}
472+
}
473+
};
474+
} // namespace
475+
476+
// Checks if all possible values of the given variable are functions with
477+
// 'noreturn' attribute.
478+
static bool areAllValuesNoReturn(const VarDecl *VD, const CFGBlock &VarBlk,
479+
AnalysisDeclContext &AC) {
480+
// The set of possible values of a constant variable is determined by
481+
// its initializer, unless it is a function parameter.
482+
if (!isa<ParmVarDecl>(VD) && VD->getType().isConstant(AC.getASTContext())) {
483+
if (const VarDecl *Def = VD->getDefinition())
484+
return isInitializedWithNoReturn(Def);
485+
return false;
486+
}
487+
488+
// In multithreaded environment the value of a global variable may be changed
489+
// asynchronously.
490+
if (!VD->getDeclContext()->isFunctionOrMethod())
491+
return false;
492+
493+
// Check the condition "all values are noreturn". It is satisfied if the
494+
// variable is set to "noreturn" value in the current block or all its
495+
// predecessors satisfies the condition.
496+
using MapTy = llvm::DenseMap<const CFGBlock *, std::optional<bool>>;
497+
using ValueTy = MapTy::value_type;
498+
MapTy BlocksToCheck;
499+
BlocksToCheck[&VarBlk] = std::nullopt;
500+
const auto BlockSatisfiesCondition = [](ValueTy Item) {
501+
return Item.getSecond().value_or(false);
502+
};
503+
504+
TransferFunctions TF(VD);
505+
BackwardDataflowWorklist Worklist(*AC.getCFG(), AC);
506+
Worklist.enqueueBlock(&VarBlk);
507+
while (const CFGBlock *B = Worklist.dequeue()) {
508+
// First check the current block.
509+
for (CFGBlock::const_reverse_iterator ri = B->rbegin(), re = B->rend();
510+
ri != re; ++ri) {
511+
if (std::optional<CFGStmt> cs = ri->getAs<CFGStmt>()) {
512+
const Stmt *S = cs->getStmt();
513+
TF.reset();
514+
TF.Visit(const_cast<Stmt *>(S));
515+
if (TF.AllValuesAreNoReturn) {
516+
if (!TF.AllValuesAreNoReturn.value())
517+
return false;
518+
BlocksToCheck[B] = true;
519+
break;
520+
}
521+
}
522+
}
523+
524+
// If all checked blocks satisfy the condition, the check is finished.
525+
if (std::all_of(BlocksToCheck.begin(), BlocksToCheck.end(),
526+
BlockSatisfiesCondition))
527+
return true;
528+
529+
// If this block does not contain the variable definition, check
530+
// its predecessors.
531+
if (!BlocksToCheck[B]) {
532+
Worklist.enqueuePredecessors(B);
533+
BlocksToCheck.erase(B);
534+
for (const auto &PredBlk : B->preds())
535+
if (!BlocksToCheck.contains(PredBlk))
536+
BlocksToCheck[PredBlk] = std::nullopt;
537+
}
538+
}
539+
540+
return false;
541+
}
542+
404543
//===----------------------------------------------------------------------===//
405544
// Check for missing return value.
406545
//===----------------------------------------------------------------------===//
@@ -527,6 +666,17 @@ static ControlFlowKind CheckFallThrough(AnalysisDeclContext &AC) {
527666
HasAbnormalEdge = true;
528667
continue;
529668
}
669+
if (auto *Call = dyn_cast<CallExpr>(S)) {
670+
const Expr *Callee = Call->getCallee();
671+
if (Callee->getType()->isPointerType())
672+
if (auto *DeclRef =
673+
dyn_cast<DeclRefExpr>(Callee->IgnoreParenImpCasts()))
674+
if (auto *VD = dyn_cast<VarDecl>(DeclRef->getDecl()))
675+
if (areAllValuesNoReturn(VD, B, AC)) {
676+
HasAbnormalEdge = true;
677+
continue;
678+
}
679+
}
530680

531681
HasPlainEdge = true;
532682
}

0 commit comments

Comments
 (0)