Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,9 @@ Improvements to Clang's diagnostics
-----------------------------------
- Diagnostics messages now refer to ``structured binding`` instead of ``decomposition``,
to align with `P0615R0 <https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0615r0.html>`_ changing the term. (#GH157880)
- Clang now suppresses runtime behavior warnings for unreachable code in file-scope
variable initializers, matching the behavior for functions. This prevents false
positives for operations in unreachable branches of constant expressions.
- Added a separate diagnostic group ``-Wfunction-effect-redeclarations``, for the more pedantic
diagnostics for function effects (``[[clang::nonblocking]]`` and ``[[clang::nonallocating]]``).
Moved the warning for a missing (though implied) attribute on a redeclaration into this group.
Expand Down
6 changes: 1 addition & 5 deletions clang/include/clang/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -5223,11 +5223,7 @@ class Parser : public CodeCompletionHandler {
/// assignment-expression
/// '{' ...
/// \endverbatim
ExprResult ParseInitializer() {
if (Tok.isNot(tok::l_brace))
return ParseAssignmentExpression();
return ParseBraceInitializer();
}
ExprResult ParseInitializer(Decl *DeclForInitializer = nullptr);

/// MayBeDesignationStart - Return true if the current token might be the
/// start of a designator. If we can tell it is impossible that it is a
Expand Down
10 changes: 10 additions & 0 deletions clang/include/clang/Sema/AnalysisBasedWarnings.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,19 @@
#define LLVM_CLANG_SEMA_ANALYSISBASEDWARNINGS_H

#include "clang/AST/Decl.h"
#include "clang/Sema/ScopeInfo.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/MapVector.h"
#include <memory>

namespace clang {

class AnalysisDeclContext;
class Decl;
class FunctionDecl;
class QualType;
class Sema;
class VarDecl;
namespace sema {
class FunctionScopeInfo;
class SemaPPCallbacks;
Expand Down Expand Up @@ -57,6 +61,8 @@ class AnalysisBasedWarnings {

enum VisitFlag { NotVisited = 0, Visited = 1, Pending = 2 };
llvm::DenseMap<const FunctionDecl*, VisitFlag> VisitedFD;
std::multimap<VarDecl *, PossiblyUnreachableDiag>
VarDeclPossiblyUnreachableDiags;

Policy PolicyOverrides;
void clearOverrides();
Expand Down Expand Up @@ -107,6 +113,10 @@ class AnalysisBasedWarnings {
// Issue warnings that require whole-translation-unit analysis.
void IssueWarnings(TranslationUnitDecl *D);

void registerVarDeclWarning(VarDecl *VD, PossiblyUnreachableDiag PUD);

void issueWarningsForRegisteredVarDecl(VarDecl *VD);

// Gets the default policy which is in effect at the given source location.
Policy getPolicyInEffectAt(SourceLocation Loc);

Expand Down
5 changes: 5 additions & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -6765,6 +6765,11 @@ class Sema final : public SemaBase {
/// suffice, e.g., in a default function argument.
Decl *ManglingContextDecl;

/// Declaration for initializer if one is currently being
/// parsed. Used when an expression has a possibly unreachable
/// diagnostic to reference the declaration as a whole.
VarDecl *DeclForInitializer = nullptr;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would that be ever different from ManglingContextDecl? Can we reuse that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was a fixme: // FIXME: Using the mangling context here is a hack.

So I went ahead and made DeclForInitializer. I think this is better because I can type it as VarDecl and its name makes more sense in this context too.


/// If we are processing a decltype type, a set of call expressions
/// for which we have deferred checking the completeness of the return type.
SmallVector<CallExpr *, 8> DelayedDecltypeCalls;
Expand Down
5 changes: 5 additions & 0 deletions clang/lib/Analysis/AnalysisDeclContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@ Stmt *AnalysisDeclContext::getBody(bool &IsAutosynthesized) const {
return BD->getBody();
else if (const auto *FunTmpl = dyn_cast_or_null<FunctionTemplateDecl>(D))
return FunTmpl->getTemplatedDecl()->getBody();
else if (const auto *VD = dyn_cast_or_null<VarDecl>(D)) {
if (VD->isFileVarDecl()) {
return const_cast<Stmt *>(dyn_cast_or_null<Stmt>(VD->getInit()));
}
}

llvm_unreachable("unknown code decl");
}
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2613,7 +2613,7 @@ Decl *Parser::ParseDeclarationAfterDeclaratorAndAttributes(
}

PreferredType.enterVariableInit(Tok.getLocation(), ThisDecl);
ExprResult Init = ParseInitializer();
ExprResult Init = ParseInitializer(ThisDecl);

// If this is the only decl in (possibly) range based for statement,
// our best guess is that the user meant ':' instead of '='.
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/Parse/ParseDeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3359,7 +3359,7 @@ ExprResult Parser::ParseCXXMemberInitializer(Decl *D, bool IsFunction,
Diag(Tok, diag::err_ms_property_initializer) << PD;
return ExprError();
}
return ParseInitializer();
return ParseInitializer(D);
}

void Parser::SkipCXXMemberSpecification(SourceLocation RecordLoc,
Expand Down
23 changes: 23 additions & 0 deletions clang/lib/Parse/ParseInit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -581,3 +581,26 @@ bool Parser::ParseMicrosoftIfExistsBraceInitializer(ExprVector &InitExprs,

return !trailingComma;
}

ExprResult Parser::ParseInitializer(Decl *DeclForInitializer) {
// Set DeclForInitializer for file-scope variables.
// For constexpr references, set it to suppress runtime warnings.
// For non-constexpr references, don't set it to avoid evaluation issues
// with self-referencing initializers. Local variables (including local
// constexpr) should emit runtime warnings.
if (DeclForInitializer && !Actions.ExprEvalContexts.empty()) {
if (auto *VD = dyn_cast<VarDecl>(DeclForInitializer);
VD && VD->isFileVarDecl() &&
(!VD->getType()->isReferenceType() || VD->isConstexpr()))
Actions.ExprEvalContexts.back().DeclForInitializer = VD;
}

ExprResult init;
if (Tok.isNot(tok::l_brace)) {
init = ParseAssignmentExpression();
} else {
init = ParseBraceInitializer();
}

return init;
}
2 changes: 1 addition & 1 deletion clang/lib/Parse/ParseOpenMP.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ void Parser::ParseOpenMPReductionInitializerForDecl(VarDecl *OmpPrivParm) {
}

PreferredType.enterVariableInit(Tok.getLocation(), OmpPrivParm);
ExprResult Init = ParseInitializer();
ExprResult Init = ParseInitializer(OmpPrivParm);

if (Init.isInvalid()) {
SkipUntil(tok::r_paren, tok::annot_pragma_openmp_end, StopBeforeMatch);
Expand Down
105 changes: 66 additions & 39 deletions clang/lib/Sema/AnalysisBasedWarnings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2734,6 +2734,70 @@ static void flushDiagnostics(Sema &S, const sema::FunctionScopeInfo *fscope) {
S.Diag(D.Loc, D.PD);
}

template <typename Iterator>
static void emitPossiblyUnreachableDiags(Sema &S, AnalysisDeclContext &AC,
std::pair<Iterator, Iterator> PUDs) {

if (PUDs.first == PUDs.second)
return;

for (auto I = PUDs.first; I != PUDs.second; ++I) {
for (const Stmt *S : I->Stmts)
AC.registerForcedBlockExpression(S);
}

if (AC.getCFG()) {
CFGReverseBlockReachabilityAnalysis *Analysis =
AC.getCFGReachablityAnalysis();

for (auto I = PUDs.first; I != PUDs.second; ++I) {
const auto &D = *I;
if (llvm::all_of(D.Stmts, [&](const Stmt *St) {
const CFGBlock *Block = AC.getBlockForRegisteredExpression(St);
// FIXME: We should be able to assert that block is non-null, but
// the CFG analysis can skip potentially-evaluated expressions in
// edge cases; see test/Sema/vla-2.c.
if (Block && Analysis)
if (!Analysis->isReachable(&AC.getCFG()->getEntry(), Block))
return false;
return true;
})) {
S.Diag(D.Loc, D.PD);
}
}
} else {
for (auto I = PUDs.first; I != PUDs.second; ++I)
S.Diag(I->Loc, I->PD);
}
}

void sema::AnalysisBasedWarnings::registerVarDeclWarning(
VarDecl *VD, clang::sema::PossiblyUnreachableDiag PUD) {
VarDeclPossiblyUnreachableDiags.emplace(VD, PUD);
}

void sema::AnalysisBasedWarnings::issueWarningsForRegisteredVarDecl(
VarDecl *VD) {
if (!llvm::is_contained(VarDeclPossiblyUnreachableDiags, VD))
return;

AnalysisDeclContext AC(/*Mgr=*/nullptr, VD);

AC.getCFGBuildOptions().PruneTriviallyFalseEdges = true;
AC.getCFGBuildOptions().AddEHEdges = false;
AC.getCFGBuildOptions().AddInitializers = true;
AC.getCFGBuildOptions().AddImplicitDtors = true;
AC.getCFGBuildOptions().AddTemporaryDtors = true;
AC.getCFGBuildOptions().AddCXXNewAllocator = false;
AC.getCFGBuildOptions().AddCXXDefaultInitExprInCtors = true;

auto Range = VarDeclPossiblyUnreachableDiags.equal_range(VD);
auto SecondRange =
llvm::make_second_range(llvm::make_range(Range.first, Range.second));
emitPossiblyUnreachableDiags(
S, AC, std::make_pair(SecondRange.begin(), SecondRange.end()));
}

// An AST Visitor that calls a callback function on each callable DEFINITION
// that is NOT in a dependent context:
class CallableVisitor : public DynamicRecursiveASTVisitor {
Expand Down Expand Up @@ -2945,45 +3009,8 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
}

// Emit delayed diagnostics.
if (!fscope->PossiblyUnreachableDiags.empty()) {
bool analyzed = false;

// Register the expressions with the CFGBuilder.
for (const auto &D : fscope->PossiblyUnreachableDiags) {
for (const Stmt *S : D.Stmts)
AC.registerForcedBlockExpression(S);
}

if (AC.getCFG()) {
analyzed = true;
for (const auto &D : fscope->PossiblyUnreachableDiags) {
bool AllReachable = true;
for (const Stmt *S : D.Stmts) {
const CFGBlock *block = AC.getBlockForRegisteredExpression(S);
CFGReverseBlockReachabilityAnalysis *cra =
AC.getCFGReachablityAnalysis();
// FIXME: We should be able to assert that block is non-null, but
// the CFG analysis can skip potentially-evaluated expressions in
// edge cases; see test/Sema/vla-2.c.
if (block && cra) {
// Can this block be reached from the entrance?
if (!cra->isReachable(&AC.getCFG()->getEntry(), block)) {
AllReachable = false;
break;
}
}
// If we cannot map to a basic block, assume the statement is
// reachable.
}

if (AllReachable)
S.Diag(D.Loc, D.PD);
}
}

if (!analyzed)
flushDiagnostics(S, fscope);
}
auto &PUDs = fscope->PossiblyUnreachableDiags;
emitPossiblyUnreachableDiags(S, AC, std::make_pair(PUDs.begin(), PUDs.end()));

// Warning: check missing 'return'
if (P.enableCheckFallThrough) {
Expand Down
17 changes: 17 additions & 0 deletions clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
#include "clang/Sema/SemaWasm.h"
#include "clang/Sema/Template.h"
#include "llvm/ADT/STLForwardCompat.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringExtras.h"
Expand Down Expand Up @@ -13118,6 +13119,13 @@ namespace {
if (isa<ParmVarDecl>(OrigDecl))
return;

// Skip checking for file-scope constexpr variables - constant evaluation
// will produce appropriate errors without needing runtime diagnostics.
// Local constexpr should still emit runtime warnings.
if (auto *VD = dyn_cast<VarDecl>(OrigDecl);
VD && VD->isConstexpr() && VD->isFileVarDecl())
return;

E = E->IgnoreParens();

// Skip checking T a = a where T is not a record or reference type.
Expand Down Expand Up @@ -13745,6 +13753,11 @@ void Sema::DiagnoseUniqueObjectDuplication(const VarDecl *VD) {
}

void Sema::AddInitializerToDecl(Decl *RealDecl, Expr *Init, bool DirectInit) {
auto ResetDeclForInitializer = llvm::make_scope_exit([this]() {
if (this->ExprEvalContexts.empty())
this->ExprEvalContexts.back().DeclForInitializer = nullptr;
});

// If there is no declaration, there was an error parsing it. Just ignore
// the initializer.
if (!RealDecl) {
Expand Down Expand Up @@ -15070,6 +15083,10 @@ void Sema::FinalizeDeclaration(Decl *ThisDecl) {
if (!VD)
return;

// Emit any deferred warnings for the variable's initializer, even if the
// variable is invalid
AnalysisWarnings.issueWarningsForRegisteredVarDecl(VD);

// Apply an implicit SectionAttr if '#pragma clang section bss|data|rodata' is active
if (VD->hasGlobalStorage() && VD->isThisDeclarationADefinition() &&
!inTemplateInstantiation() && !VD->hasAttr<SectionAttr>()) {
Expand Down
43 changes: 24 additions & 19 deletions clang/lib/Sema/SemaExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20567,31 +20567,36 @@ void Sema::MarkDeclarationsReferencedInExpr(Expr *E,
}

/// Emit a diagnostic when statements are reachable.
/// FIXME: check for reachability even in expressions for which we don't build a
/// CFG (eg, in the initializer of a global or in a constant expression).
/// For example,
/// namespace { auto *p = new double[3][false ? (1, 2) : 3]; }
bool Sema::DiagIfReachable(SourceLocation Loc, ArrayRef<const Stmt *> Stmts,
const PartialDiagnostic &PD) {
if (!Stmts.empty() && getCurFunctionOrMethodDecl()) {
if (!FunctionScopes.empty())
FunctionScopes.back()->PossiblyUnreachableDiags.push_back(
sema::PossiblyUnreachableDiag(PD, Loc, Stmts));
return true;
}

VarDecl *Decl = ExprEvalContexts.back().DeclForInitializer;
// The initializer of a constexpr variable or of the first declaration of a
// static data member is not syntactically a constant evaluated constant,
// but nonetheless is always required to be a constant expression, so we
// can skip diagnosing.
// FIXME: Using the mangling context here is a hack.
if (auto *VD = dyn_cast_or_null<VarDecl>(
ExprEvalContexts.back().ManglingContextDecl)) {
if (VD->isConstexpr() ||
(VD->isStaticDataMember() && VD->isFirstDecl() && !VD->isInline()))
return false;
// FIXME: For any other kind of variable, we should build a CFG for its
// initializer and check whether the context in question is reachable.
if (Decl &&
(Decl->isConstexpr() || (Decl->isStaticDataMember() &&
Decl->isFirstDecl() && !Decl->isInline())))
return false;

if (Stmts.empty()) {
Diag(Loc, PD);
return true;
}

if (getCurFunction()) {
FunctionScopes.back()->PossiblyUnreachableDiags.push_back(
sema::PossiblyUnreachableDiag(PD, Loc, Stmts));
return true;
}

// For non-constexpr file-scope variables with reachability context (non-empty
// Stmts), build a CFG for the initializer and check whether the context in
// question is reachable.
if (Decl && Decl->isFileVarDecl()) {
AnalysisWarnings.registerVarDeclWarning(
Decl, sema::PossiblyUnreachableDiag(PD, Loc, Stmts));
return true;
}

Diag(Loc, PD);
Expand Down
6 changes: 6 additions & 0 deletions clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6201,6 +6201,10 @@ void Sema::InstantiateVariableInitializer(
currentEvaluationContext().RebuildDefaultArgOrDefaultInit =
parentEvaluationContext().RebuildDefaultArgOrDefaultInit;

// Set DeclForInitializer for this variable so DiagIfReachable can properly
// suppress runtime diagnostics for constexpr/static member variables
currentEvaluationContext().DeclForInitializer = Var;

if (OldVar->getInit()) {
// Instantiate the initializer.
ExprResult Init =
Expand Down Expand Up @@ -6468,6 +6472,8 @@ void Sema::InstantiateVariableDefinition(SourceLocation PointOfInstantiation,
PassToConsumerRAII.Var = Var;
Var->setTemplateSpecializationKind(OldVar->getTemplateSpecializationKind(),
OldVar->getPointOfInstantiation());
// Emit any deferred warnings for the variable's initializer
AnalysisWarnings.issueWarningsForRegisteredVarDecl(Var);
}

// This variable may have local implicit instantiations that need to be
Expand Down
Loading